diff --git a/src/exercises/MCMC_1-intro.jl b/src/exercises/MCMC_1-intro.jl old mode 100755 new mode 100644 index 1680f6c7..737405c5 --- a/src/exercises/MCMC_1-intro.jl +++ b/src/exercises/MCMC_1-intro.jl @@ -1,26 +1,30 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "20" -#> title = "4. MCMC intro" -#> date = "2025-08-06" +#> order = "27" +#> title = "5. MCMC intro" #> tags = ["exercises"] -#> description = "MCMC intro" #> layout = "layout.jlhtml" +#> description = "MCMC intro" #> #> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" +#> 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" @@ -29,7 +33,11 @@ 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. +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 @@ -44,7 +52,7 @@ md""" # ╔═╡ 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. +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. """ @@ -61,11 +69,20 @@ times = [30, 138, 375, 450] 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)) +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""" -Consider now that you find a new fossil of an ancient ancestor of the **seahorses**. +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 @@ -76,68 +93,15 @@ 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""" -!!! questions - - What is *cytochrome C*'s mutation rate `α`? - - What is the seahorse-ancestor fossil's age? +!!! question + - What values are likely for 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" @@ -146,63 +110,117 @@ 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)? +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? +""" -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)`. +# ╔═╡ 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(10) -# Hence, a few mutations (bp = base pair) per My, is here 10 mutations per My on average. +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(10)` for the prior and not `Exponential(1)`, or some other value? + 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(1)` or `Uniform(0, 100)`. When are the results significantly different? + 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""" -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. +**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 # Exponential(10) + α ~ prior_alpha # prior distribution of parameter - num_mutations = zeros(length(ts)) - for i in eachindex(ts) - num_mutations[i] ~ Poisson(α * ts[i]) + 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 num_mutations + return N 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. +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); -# ╔═╡ 8b0bf05f-92a0-4ce7-8042-08c790088688 -mutation_model() # random sample of num_mutations +# ╔═╡ 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(chain[:α], normalized=:probability) +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" @@ -214,25 +232,31 @@ 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,) +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 `(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. + 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 | (num_mutations = observed_mutations) - # errors because there is no `,` in the parentheses +# forgot_comma = mutation_model | (N = observed_mutations) # ╔═╡ c5f0dbb3-fba1-41f2-b7d2-740012603555 md""" -We can verify that for our conditioned model, the values of `num_mutations` has been set as constant: +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 -conditioned_model() # always returns `observed_mutations` +generated_quantities(conditioned_model, conditioned_chain) # always returns `observed_mutations` # ╔═╡ 371a48d5-daea-4d0b-968b-7e3056a65494 md""" @@ -243,33 +267,11 @@ What we're after is our updated belief on the distribution of `α` given the obs - `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.) +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) -# 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." @@ -285,40 +287,47 @@ md""" # ╔═╡ 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**. +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. +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 -sp_alpha = mutation_chain[:α]; +alpha_samples = mutation_chain[:α]; # ╔═╡ 9cf08616-d599-42a3-82e8-e8c98853c1d8 -histogram(sp_alpha) +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 - 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) + 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(sp_alpha) +mean(alpha_samples) # ╔═╡ 5f2f5a78-ca90-4baf-8bf0-ff1fae88d785 -sqrt(var(sp_alpha)) - -# ╔═╡ d9b6e99d-e483-4633-88eb-5a7824716eb8 -std(sp_alpha) +sqrt(var(alpha_samples)) # ╔═╡ bfbf811f-7b2f-473b-b132-0c7e965c8b0d -md"To answer our first question, α is ± normally distributed around 0.75 with a standard deviation of 0.025." +md""" + $α$ is ± normally distributed around 0.75 with a standard deviation of 0.025. +""" # ╔═╡ d2d83d9a-0ed6-431b-8764-397c1bb019c2 md"### Seahorses (extra)" @@ -326,24 +335,25 @@ 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`. +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 weird old fish), but _before_ modern seahorses (450 Ma after the bony fish fossil). +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) - α ~ Exponential(10) - - num_mutations = zeros(length(ts)) - for i in eachindex(ts) - num_mutations[i] ~ Poisson(α * ts[i]) + α ~ 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 (α, num_mutations, fossil_age, horse_mutations) + return N end # ╔═╡ 43509549-f926-478b-a4da-995de443b3a7 @@ -353,10 +363,7 @@ md"Then we simply repeat model instantation, conditioning and sampling:" 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() +horseditioned_model = horse_model | (N = observed_mutations, horse_mutations = 156); # ╔═╡ e4eda27b-cff2-4ef9-bbdc-75ce2b19b10b horse_chain = sample(horseditioned_model, NUTS(), 2000) @@ -367,16 +374,43 @@ md"And we have our posterior distribution of `fossil_age`! It seems like the sea # ╔═╡ 7e5a39c7-cb66-4563-8f58-7245f88c9b85 histogram(horse_chain[:fossil_age]) -# ╔═╡ 960c95f8-4431-4e56-97fc-783bef811891 -mean(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 -# ╔═╡ d308bff0-3ed5-4cb1-b81c-f53f7a1967a1 -std(horse_chain[:fossil_age]) + 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 @@ -386,40 +420,43 @@ std(horse_chain[:fossil_age]) # ╠═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 -# ╟─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 +# ╟─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 +# ╟─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 -# ╠═8b0bf05f-92a0-4ce7-8042-08c790088688 +# ╟─d4dbc185-6087-4862-b6e5-b5e4f51c2866 # ╠═3e4998e7-4981-4945-8bf2-ddb5afcb43b1 -# ╠═a9d54dfc-5337-412f-86b0-deeb5a0b6928 +# ╠═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 @@ -431,10 +468,10 @@ std(horse_chain[:fossil_age]) # ╠═b657217a-6ccc-4a41-b852-df4e39a7a10a # ╠═9cf08616-d599-42a3-82e8-e8c98853c1d8 # ╟─e02c42dc-627c-4bc0-8ebb-fdb2b0f15b64 -# ╠═87e70d5a-7a45-4a3e-b6c4-a894cc78621b +# ╟─87e70d5a-7a45-4a3e-b6c4-a894cc78621b +# ╟─87059440-2919-4f6d-9d32-0df3ce75e2a2 # ╠═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 @@ -442,9 +479,9 @@ std(horse_chain[:fossil_age]) # ╟─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 +# ╟─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 old mode 100755 new mode 100644 index 7bd2ef68..74323354 --- a/src/exercises/MCMC_2-basics.jl +++ b/src/exercises/MCMC_2-basics.jl @@ -1,35 +1,54 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "21" -#> title = "4. MCMC basics" -#> date = "2025-08-06" +#> order = "28" +#> title = "5. MCMC basics" #> tags = ["exercises"] -#> description = "MCMC basics" #> layout = "layout.jlhtml" +#> description = "MCMC basics" #> #> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" +#> 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" -# ╔═╡ 957386c5-775c-47f7-9a38-e1630e548689 +# ╔═╡ 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)`. @@ -38,108 +57,81 @@ We can formulate this as `X ~ Exponential(100)` and `Y ~ Uniform(0, X)`. 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? + 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" +md"### 1: Prior plot" # ╔═╡ 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)) +X_prior = missing -# ╔═╡ 82289137-9c20-4d7a-97b2-56f417831409 -mijn_Y = rand(Uniform(0, mijn_X)) +# ╔═╡ c64b346a-059d-4bbe-b166-80116260e19b +missing # plot above # ╔═╡ 78eb7779-f182-4419-b5d8-79a2f5c5d6da -md"### 2" +md"### 2: Unconditional expected value of Y" # ╔═╡ 3decb2ec-210a-4b2f-842d-6fd40dd3f77b @model function mole() - X ~ Exponential(100) - Y ~ Uniform(0, X) + X ~ missing + Y ~ missing return Y end # ╔═╡ 76dd814d-0d9b-4f7e-aff8-990da57d052b molemodel = mole() -# ╔═╡ 5c989060-40cd-4b27-b96f-3f98a85f5122 -molemodel() # returns Y +# ╔═╡ 61b8ef63-a613-4dcb-8f7a-936e0d59b862 +Y_samples = missing # ╔═╡ 4e8b9000-cb61-4f01-9ba9-17276ad0335e -E_Y = mean([molemodel() for i in 1:2000]) +E_Y = missing # ╔═╡ 8777b133-7d7c-4a85-b89c-2f00093e9984 -md"### 3" +md"### 3: Conditional expected value of X" # ╔═╡ 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. -""" +cond_mole = missing # ╔═╡ 014538da-b5ef-41c8-b799-2c000b4c9134 -molechain = sample(cond_mole, NUTS(), 2000) +molechain = missing -# ╔═╡ b02dc714-e2bb-4ae2-acf9-c37a4389f953 -plot(molechain) +# ╔═╡ 977d0406-f154-4b87-b9c6-cafe4809a4a6 +missing # plot molechain + +# ╔═╡ af4811ce-c6e7-458a-8f89-0562355b3eb0 +X_samplescondY = missing # ╔═╡ fd540765-95e5-4071-81f7-e689b06cad0c -E_Xcond3 = mean(molechain[:X]) +E_XcondY = missing # ╔═╡ 5521933a-a42e-4a67-94b9-84eab52ddf07 -E_X = mean(Exponential(100)) +E_X = missing -# ╔═╡ ca3e730c-a940-4c76-93eb-70ae4aa0e008 -md"### 4" +# ╔═╡ 9403c355-dce8-4aa9-80a3-824ea6193905 +md"### 4: Conditional distribution of X" -# ╔═╡ 159caa6a-2ebf-44bd-87a5-b4ab8b085354 -histogram(molechain[:X], normalized=:probability) +# ╔═╡ bedd99ca-4285-4c3d-87e2-c22d414f8f07 +# histogram -# ╔═╡ eda00c08-de49-4d2d-acc4-ba6e21ff0b11 -md"### 5" +# ╔═╡ 21743f62-c7f5-4171-8277-0667edb71c56 +md"### 5: Conditional distribution of X (with more data)" # ╔═╡ 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 + X ~ missing + # how to deal with a vector of values again... end -# ╔═╡ 1a78d96a-fe57-42a4-9785-003266117ddb +# ╔═╡ 26a245d2-c5b6-484d-bdf7-541948ff06ad 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) +# ╔═╡ dc6aadff-7cff-4a9a-a967-32cd322b2696 +missing # (some lines of code) -# ╔═╡ 2d35375a-cd92-48d1-8779-761641e7b0af -mean(molechain2[:X]) +# ╔═╡ ea2235f0-0cfc-4603-b027-d0a1bc6fa2c1 +# I estimate the tunnel to be about (MISSING) long # ╔═╡ 951d0913-1a52-4d5b-b5bb-168487e50ab2 md"## 2: Potatoes" @@ -148,7 +140,7 @@ md"## 2: Potatoes" 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)`. +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`. @@ -156,84 +148,61 @@ We can formulate this as `N ~ Poisson(10)`, `W ~ Uniform(150, 250)` and `T ~ No 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" +md"### 1: Conditional distribution of N" # ╔═╡ ec479be9-0d8a-4c8d-97c9-6f64d861924c @model function potatoes() - N ~ Poisson(10) - W ~ Uniform(150, 250) - T ~ Normal(N*W, 50) + N ~ missing + W ~ missing + T ~ missing end -# ╔═╡ 4e92d825-481f-430a-94a4-fbdf31b679eb -potato_model = potatoes() - -# ╔═╡ 0ced04f8-322a-42f6-a1b7-1b30faf9024b -potato_cond = potato_model | (T = 1200,) +# ╔═╡ 438893d1-3107-493c-9299-d8813658a698 +potato_model = missing -# ╔═╡ e48a2d96-6d7b-4861-b315-0ae580479eda -potato_chain = sample(potato_cond, PG(10), 2000) +# ╔═╡ dd7d84b0-1f20-4478-96ba-b3f4e9df1d2c +potato_cond = missing -# ╔═╡ ceb2b8fd-aa47-443b-8e5c-74a48eda1174 -plot(potato_chain) +# ╔═╡ accca8d7-8822-49ae-8588-7259da82e37a +potato_chain = missing + # be careful with your choice of sampling algorithm! are all priors continuous? -# ╔═╡ 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. -""" +# ╔═╡ 519a952b-f040-4588-8acd-1cfaab88c4da +# plot chain -# ╔═╡ ad571d35-639c-4239-9e62-42952ddb48d0 -# Probability of having T=1200 with N=7. -mean(potato_chain[:N] .== 7) +# ╔═╡ 57adc714-cdd1-46cf-a4ea-d0fb04924e1e +# histogram of N # ╔═╡ 0fddec58-67d9-4dec-a72d-445675fa46a6 -md"### 2" +md"### 2: Probability" -# ╔═╡ 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) +# ╔═╡ b77d8796-9065-4c61-be21-3dc292b94492 +p_potato1 = missing # ╔═╡ 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,) +md"### 3: Probability (with more data)" -# ╔═╡ 4cd99dce-12df-4e3a-aa91-ff00c61544a8 -pota2_chain = sample(potato_cond2, PG(10), 2000) +# ╔═╡ abffd02e-b917-4e9e-a94d-de197e1b0fc3 +potato_cond2 = missing # alternative conditioned model now also given `W = 220` -# ╔═╡ 48ea9893-16f7-457e-93b0-9f168a2f72f4 -plot(pota2_chain) +# ╔═╡ 5b30d91a-7039-4d2a-bcf7-3a990622fd2a +pota2_chain = missing -# ╔═╡ c3d36685-e1dd-4734-bee9-f7aeebed3473 -p_potato2 = mean(pota2_chain[:N] .== 5) +# ╔═╡ 27939d84-d6e1-47b2-a939-5038c6bdcb48 +# plot chain -# ╔═╡ bdc40079-de7b-489f-9d9b-6e1e2130109f -mean(pota2_chain[:N] .== 4) - -# ╔═╡ 22d009f0-390f-412d-9af8-33d169ac7371 -mean(pota2_chain[:N] .== 6) +# ╔═╡ 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. Let `μ` be the **average** lifespan of your LED lights (in khr or 1000 hours) and `L`ᵢ the lifespan of the `i`-th LED light. +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)`. """ @@ -248,184 +217,62 @@ md""" """ # ╔═╡ 7df97000-5cf0-4a29-b3c3-f867403f4318 -md"### 1" +md"### 1: Unconditional expected value" # ╔═╡ 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 +lights_prior = missing -# ╔═╡ fce3e814-97a7-4e12-a7cc-b5b086dc8340 -E_mu = mean(lights_prior) - -# ╔═╡ a7811e57-2820-4662-8c20-a4d2d7efd642 -rand(lights_prior) +# ╔═╡ db77b90b-33b5-4d70-8229-b4c7d74fa96c +E_mu = missing # ╔═╡ 308cb1f5-e26b-43b2-be4d-9eabd68b3670 -md"### 2" +md"### 2: Choice of distribution" -# ╔═╡ 490cbc67-3e0c-4c79-a83d-52cf493854a2 +# ╔═╡ 738b4097-56e1-4921-9195-2d535c78ae73 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. +!!! note + See theory p. 96 for an overview of elementary distributions. """ -# ╔═╡ 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 +# ╔═╡ d797cb9b-430b-4d1d-8ded-21959a7cb3a9 +# A sensible distribution for the lifespan of a LED-light is (missing) # ╔═╡ cd37e5fc-ae9a-429d-844d-4b450b187b5e -md"### 3" +md"### 3: Conditional expected value" # ╔═╡ 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 + missing 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) +# ╔═╡ 37bf6ca5-e42b-406e-94d6-bcd1e706e2bd +L_obs = [16, 20, 23, 41] -# ╔═╡ 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 +# ╔═╡ c49955c7-7082-4261-b4db-0056b5c637f1 +E_mu_cond = missing # ╔═╡ 9a168680-9d96-466f-ba75-122d6a391501 -md"### 4 🌟🌟🌟" +md"### 4 🌟🌟🌟: Working with censored data" -# ╔═╡ 1f35d962-a249-4be7-9a96-17eb83fca7d8 +# ╔═╡ ab6331a3-676e-45e6-b5be-e9c4154ea071 md""" -!!! hint - You can model the number of lights that still work as a `Binomial` distribution, the success rate of which depends on `μ`. +!!! note + This question is way above the exam's difficulty level. Don't feel bad if you can't find the answer right away! """ -# ╔═╡ 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 +# ╔═╡ 1f35d962-a249-4be7-9a96-17eb83fca7d8 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` - +!!! 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(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) +@model function lights_censored(time_observed) + missing 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[:μ]) +# ╔═╡ 410521d8-f767-4c4e-b19b-25cf31ec0f36 +E_mu_cond🌟 = missing # ╔═╡ 9dc0456b-7fd2-4120-8f9e-3de1984ff516 md"## 4: Fish" @@ -450,7 +297,7 @@ md""" """ # ╔═╡ b477b212-83a2-42f0-a616-52516e152d48 -md"### 1" +md"### 1: Prior distribution given `fs1`" # ╔═╡ 23056f1e-128e-463e-a80f-56299397022e md""" @@ -459,96 +306,35 @@ md""" """ # ╔═╡ 3cd0c888-9f11-47f1-a293-b96aa80ea3b0 -lengthdist = MixtureModel([Normal(90, 15), Normal(60, 10)], [0.3, 0.7]) +lengthdist = missing -# ╔═╡ afcf1260-db4d-4ff1-ac07-978161874e6c -histogram(rand(lengthdist, 10000)) +# ╔═╡ e23756e0-7688-4e3b-ba8d-a827e621e862 +missing # plot # ╔═╡ 8e030a06-5104-4c5b-b1f2-f86464e66502 -md"### 2" +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() - 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 + missing 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]) +# ╔═╡ 819ed364-4bc9-43ec-aa50-b965c8f1c826 +fs1_est = missing # ╔═╡ 952a941a-8703-45a3-aac1-a290a181e8c5 -md"### 3🌟" +md"### 3🌟: Conditional expected value (spicy)" # ╔═╡ 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 + missing 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 +# ╔═╡ 483cbe4d-64d3-4b16-b6fc-e97b21f174a6 +p_fish4_is_species1 = missing # ╔═╡ 30088664-5157-4d99-8584-7a42d0acdfb8 md"## 5: Circleference" @@ -606,76 +392,34 @@ begin end # ╔═╡ 4e96908a-4fc9-429d-bf37-7a569194a038 -scatter([x1, x2, x3], [y1, y2, y3], aspect_ratio=:equal, xlim=[-40, 40], ylim=[-40, 40]) +scatter([x1, x2, x3], [y1, y2, y3]) # ╔═╡ 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*π)) +md"### 1: All points" # ╔═╡ 84d27c98-9513-4ae3-8101-621c083a1b01 @model function circle(σ=0.25) # generate a circle center - xC ~ Uniform(-20, 20) - yC ~ Uniform(-20, 20) + missing # generate a radius - R ~ Uniform(0, 50) + missing # 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), σ) + missing # P2 - x2 ~ Normal(xC + R * cos(θ2), σ) - y2 ~ Normal(yC + R * sin(θ2), σ) + missing # P3 - x3 ~ Normal(xC + R * cos(θ3), σ) - y3 ~ Normal(yC + R * sin(θ3), σ) + missing 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/π +# ╔═╡ 25ea29e7-b391-4bd1-bdbc-1957adb8c993 +circlechain = missing # ╔═╡ 0d04b0e8-3d1a-4281-b175-570148569ef2 begin @@ -690,23 +434,19 @@ begin end # ╔═╡ 0a140d2a-a24b-48df-9af8-7fa5d586a26f -md"### 2" +md"### 2: Some points" # ╔═╡ 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. +circle1 = missing # given one point # ╔═╡ 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. +circle2 = missing # given two points # ╔═╡ 3b5f3623-3d00-4ba4-9182-5bddacc56567 -chain1 = sample(circle1, NUTS(), 100); +chain1 = missing # ╔═╡ c11452bc-f404-4e51-8f68-292f5b538c88 -chain2 = sample(circle2, NUTS(), 100); +chain2 = missing # ╔═╡ c1174a5c-a6a1-46a0-96be-6997e3201dfc begin @@ -736,126 +476,81 @@ end # ╟─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 -# ╟─957386c5-775c-47f7-9a38-e1630e548689 -# ╟─ca8057cc-6d70-4a88-88aa-c494af9659ef +# ╟─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 -# ╠═4ab8aaba-010c-4844-9cf6-627a746c2492 -# ╠═82289137-9c20-4d7a-97b2-56f417831409 +# ╠═c64b346a-059d-4bbe-b166-80116260e19b # ╟─78eb7779-f182-4419-b5d8-79a2f5c5d6da # ╠═3decb2ec-210a-4b2f-842d-6fd40dd3f77b # ╠═76dd814d-0d9b-4f7e-aff8-990da57d052b -# ╠═5c989060-40cd-4b27-b96f-3f98a85f5122 +# ╠═61b8ef63-a613-4dcb-8f7a-936e0d59b862 # ╠═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 +# ╠═977d0406-f154-4b87-b9c6-cafe4809a4a6 +# ╠═af4811ce-c6e7-458a-8f89-0562355b3eb0 # ╠═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 +# ╟─9403c355-dce8-4aa9-80a3-824ea6193905 +# ╠═bedd99ca-4285-4c3d-87e2-c22d414f8f07 +# ╟─21743f62-c7f5-4171-8277-0667edb71c56 # ╠═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 +# ╠═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 -# ╠═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 +# ╠═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 -# ╠═9dd6fa35-37d5-4ab4-ac2c-f4eefdabadd2 -# ╠═57ec05ca-bf37-4012-be9a-d83c1d9f0f8a +# ╠═b77d8796-9065-4c61-be21-3dc292b94492 # ╟─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 +# ╠═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 -# ╠═382db9a6-edcb-497b-9535-b576cd6badb0 -# ╠═fce3e814-97a7-4e12-a7cc-b5b086dc8340 -# ╠═a7811e57-2820-4662-8c20-a4d2d7efd642 +# ╠═db77b90b-33b5-4d70-8229-b4c7d74fa96c # ╟─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 +# ╟─738b4097-56e1-4921-9195-2d535c78ae73 +# ╠═d797cb9b-430b-4d1d-8ded-21959a7cb3a9 # ╟─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 +# ╠═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 -# ╠═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 +# ╠═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 -# ╠═afcf1260-db4d-4ff1-ac07-978161874e6c +# ╠═e23756e0-7688-4e3b-ba8d-a827e621e862 # ╟─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 +# ╠═819ed364-4bc9-43ec-aa50-b965c8f1c826 # ╟─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 +# ╠═483cbe4d-64d3-4b16-b6fc-e97b21f174a6 # ╟─30088664-5157-4d99-8584-7a42d0acdfb8 # ╟─7dd2c189-79c0-4d29-9e17-9c24a78b5791 # ╟─dcbf405c-786c-4226-b35c-dc718452bb61 @@ -865,19 +560,9 @@ end # ╠═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 +# ╠═25ea29e7-b391-4bd1-bdbc-1957adb8c993 # ╠═0d04b0e8-3d1a-4281-b175-570148569ef2 # ╟─0a140d2a-a24b-48df-9af8-7fa5d586a26f # ╠═b7649523-fd40-4fe3-8d86-fc2cb2c8c488 diff --git a/src/exercises/MCMC_3-advanced.jl b/src/exercises/MCMC_3-advanced.jl old mode 100755 new mode 100644 index 2296ea53..7cad13c6 --- a/src/exercises/MCMC_3-advanced.jl +++ b/src/exercises/MCMC_3-advanced.jl @@ -1,16 +1,15 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "22" -#> title = "4. MCMCM advanced" -#> date = "2025-08-06" +#> order = "29" +#> title = "5. MCMC advanced" #> tags = ["exercises"] -#> description = "MCMC advanced" #> layout = "layout.jlhtml" +#> description = "MCMC advanced" #> #> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" +#> name = "Bram Spanoghe" using Markdown using InteractiveUtils @@ -18,7 +17,7 @@ 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 + 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) @@ -28,6 +27,8 @@ macro bind(def, element) 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 @@ -52,9 +53,6 @@ At some known timepoints `ts`, we get noisy observations on the car's vertical p # ╔═╡ 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]; @@ -72,7 +70,7 @@ 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. +with `σ` a noise parameter, of which you only know that it's probably small. """ # ╔═╡ 0bdac14c-db71-47b5-95d5-d83fb1e68cab @@ -100,33 +98,11 @@ md""" # ╔═╡ 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 + missing 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]) +# ╔═╡ fb202b86-c058-4f03-9062-ab282c71d5c4 +missing # histogram of `t_switch` # ╔═╡ 34bf815a-3bd3-49b5-b06d-d4297ca213a8 md"## Petridish peril (inference edition)" @@ -163,57 +139,27 @@ logistic(t, P0, r, K) = K / (1 + (K - P0)/P0 * exp(-r*t)) md"### 1" # ╔═╡ f080f708-a457-40a3-936c-b82d5159975d -dropletdist = MixtureModel([Poisson(10), Poisson(30)], [0.75, 0.25]); +dropletdist = missing # ╔═╡ 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 +@model function petrigrowth() + missing 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)) +# ╔═╡ 3e98c640-4bb0-4b5d-bae0-769133a599a7 +prob_splittable = missing # ╔═╡ 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) +# ╔═╡ 47b15322-0b8c-48f1-b123-271ebae92655 +missing # plot # ╔═╡ 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] -); +dropletdist🌟 = missing # ╔═╡ 4e730df9-f619-464a-b8a3-57448132404b begin @@ -223,31 +169,12 @@ begin 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) +@model function petrigrowth🌟() + missing 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!" +# ╔═╡ 0704ed4d-5b3e-401d-a496-98782cb20b09 +prob_splittable🌟 = missing # ╔═╡ Cell order: # ╟─f84d9259-69c0-4165-8bc0-d924fef18182 @@ -257,7 +184,6 @@ md"Changing to all continuous distributions made the inference much higher quali # ╟─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 @@ -266,11 +192,7 @@ md"Changing to all continuous distributions made the inference much higher quali # ╟─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 +# ╠═fb202b86-c058-4f03-9062-ab282c71d5c4 # ╟─34bf815a-3bd3-49b5-b06d-d4297ca213a8 # ╟─78571fb5-c44f-4e7f-afde-4436db6c945b # ╟─2a84472c-cb6f-4607-9b98-c88cc2744e3d @@ -279,17 +201,11 @@ md"Changing to all continuous distributions made the inference much higher quali # ╟─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 +# ╠═3e98c640-4bb0-4b5d-bae0-769133a599a7 # ╟─0ea95b67-d4da-4c5a-ad2e-05024ad074a3 -# ╠═3d9b73ef-2aae-4c19-bddc-4081927ec92d +# ╠═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 -# ╠═9ac7aa12-d945-4078-ae63-d598d7171112 -# ╟─2f4308df-4451-4ac2-8a31-65cd49a275af +# ╠═0704ed4d-5b3e-401d-a496-98782cb20b09 diff --git a/src/exercises/MCMC_4-review.jl b/src/exercises/MCMC_4-review.jl old mode 100755 new mode 100644 index 7d7594c1..9d8c0049 --- a/src/exercises/MCMC_4-review.jl +++ b/src/exercises/MCMC_4-review.jl @@ -2,20 +2,21 @@ # v0.20.4 #> [frontmatter] -#> order = "23" -#> title = "4. MCMC review" -#> date = "2025-08-06" +#> order = "30" +#> title = "5. MCMC review" #> tags = ["exercises"] -#> description = "MCMC review" #> layout = "layout.jlhtml" +#> description = "MCMC review" #> #> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" +#> 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 @@ -23,35 +24,15 @@ 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" @@ -71,16 +52,20 @@ md""" *The Asian giant hornet (credit: Picture by KENPEI on Wikipedia)* """ -# ╔═╡ be7dbb0f-299a-4925-b4c4-20eb6ce6e4af +# ╔═╡ bfb49d79-772d-4066-bd96-14143a1b5eeb md""" -Consider below the coordinates of marked hornets, as well as their return times. +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(20); +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)) +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""" @@ -88,66 +73,31 @@ md""" 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)) +# ╔═╡ 66862953-aeb2-4310-90bc-673260f0ecc0 +x_nest_sp = missing # vector with possible values of the nest's x-coordinate -# ╔═╡ 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) +# ╔═╡ f522cbcb-112b-4d56-b860-5aee44b1aa2f +y_nest_sp = missing # vector with possible values of the nest's y-coordinate - 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 +# ╔═╡ 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)); + 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(1, 1, 1), label = "True nest location", markershape=:rect) + scatter!(true_location[1:1], true_location[2:2], color = RGB(0, 1, 0), label = "True nest location", markershape = :square) 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 +# ╟─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 -# ╟─be7dbb0f-299a-4925-b4c4-20eb6ce6e4af +# ╟─bfb49d79-772d-4066-bd96-14143a1b5eeb # ╠═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 +# ╠═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 index 38730ff5..83a2f0a1 100644 --- a/src/exercises/calib_fermenter_monod.jl +++ b/src/exercises/calib_fermenter_monod.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "25" -#> title = "5. Calibration fermenter monod" -#> date = "2025-08-06" +#> order = "32" +#> title = "6. Calibration fermenter monod" #> tags = ["exercises"] -#> description = "Calibration fermenter monod" #> layout = "layout.jlhtml" +#> description = "Calibration fermenter monod" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,19 +15,18 @@ 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 - -# ╔═╡ 78a25bef-31e5-45ef-b0ba-b9a8c8a9edeb -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ 16438e07-1b2b-467e-822a-081d19cae92b -using Catalyst, OrdinaryDiffEq, StatsPlots +using Catalyst, OrdinaryDiffEq # ╔═╡ 295caa68-db27-4c9b-bc34-86ab088fec24 -using Turing, StatsBase +using Turing, StatsPlots, StatsBase # ╔═╡ dc6e9bdc-dae0-43aa-b624-f9314d1d9884 using LinearAlgebra, Optim @@ -40,16 +38,12 @@ md""" # ╔═╡ 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: +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} +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 @@ -58,20 +52,16 @@ 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 +# fermenter_monod = @reaction_network begin +# @species missing +# @parameters missing +# missing +# missing +# missing +# end # ╔═╡ 956790e2-6cac-46c9-886e-24d5aceae1c5 -convert(ODESystem, fermenter_monod, combinatoric_ratelaws=false) - -# ╔═╡ 68f9ecb3-15b0-4a53-8864-5dac13a89e95 -parameters(fermenter_monod) +# convert(missing, missing) # ╔═╡ de8ddc14-8f82-403d-8f42-29673ef2a722 md""" @@ -85,7 +75,7 @@ $$\begin{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$: +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 @@ -105,7 +95,6 @@ Make a scatter plot of the measured data for both $S$ and $X$. Use the following """ # ╔═╡ 918fd524-81fa-4aff-a403-37402e47235b -# Uncomment and complete the instruction # begin # missing # missing @@ -115,73 +104,63 @@ Make a scatter plot of the measured data for both $S$ and $X$. Use the following 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$. +- `μ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}$ -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$. -""" +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}$. -# ╔═╡ 7a227eaf-18d0-44f4-ac4b-f529e81c7471 -md""" -Create an `ODEProblem`. Use the aforementioned values as initial values for the problem. +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$. """ -# ╔═╡ 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`. +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 -# Uncomment and complete the instruction -# @model function fermenter_fun(t_meas) +# @model function fermenter_inference(t_meas) # σ_S ~ missing # σ_X ~ missing # μmax ~ missing # Ks ~ missing - # params = missing + # parms = missing # oprob = missing # osol = missing - # S_s ~ missing - # X_s ~ 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 time measurements to the defined function and instantly condition the model with the measurements of $S$ and $X$: +Provide the measurements to the Turing model. """ # ╔═╡ 6a508a62-61b9-4273-8e45-b26f594e8da9 -# fermenter_cond_mod = missing # Uncomment and complete the instruction +# fermenter_inf = missing # ╔═╡ 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. +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_map = missing # Uncomment and complete the instruction +# results_mle = missing # ╔═╡ e1b0ee01-f16c-40e9-a0f9-80072d690936 md""" -Visualize a summary of the optimized parameters. +Visualize a summary of the optimized parameters. Beware that this may take a lot of time... """ # ╔═╡ f2d7daf8-8218-446d-b1d2-e9e05aeadfd9 -# missing # Uncomment and complete the instruction +# missing # ╔═╡ 23d58bb1-d077-402e-8bee-3866c68e069a md""" @@ -189,10 +168,10 @@ 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 +# μmax_opt = missing # ╔═╡ fa77bcbe-2ddc-4113-8f6a-4a18d219da9e -# Ks_opt = missing # Uncomment and complete the instruction +# Ks_opt = missing # ╔═╡ 05d13a48-adc8-4e24-a6e4-be24af2c7a59 md""" @@ -205,7 +184,7 @@ Set up parameter values with optimized parameter values: """ # ╔═╡ 75cf59ed-af8e-4e8a-8ed2-1f3bf4d386d0 -# params_opt = missing # Uncomment and complete the instruction +# params_opt = missing # ╔═╡ 4e8870dc-2da6-4b80-82d6-26c7ceedad7d md""" @@ -213,24 +192,21 @@ 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 +# oprob_opt = missing # ╔═╡ f45e8124-e942-438e-99c5-3032ccc01454 -# osol_opt = missing # Uncomment and complete the instruction +# osol_opt = missing # ╔═╡ 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. +Plot $S$ and $X$ simulated with the optimized parameter values together with the measured data. """ # ╔═╡ d0156099-ad03-4711-ac0f-94882fb78266 -# Uncomment and complete the instruction # begin # missing # missing # missing -# missing -# missing # end # ╔═╡ 257ae1a9-6264-4122-80be-8022b4b7500c @@ -247,12 +223,11 @@ md""" # ╔═╡ 22a6aeb4-559c-4f69-82fd-d021f68e1f17 md""" !!! hint - Think of the meaning of the estimated parameters and their impact on the variables $S$ and $X$. + Think about 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 @@ -262,7 +237,6 @@ md""" # ╟─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 @@ -271,13 +245,9 @@ md""" # ╟─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 +# ╟─5928a9f3-f33c-4689-a6e5-637447f420d6 # ╟─3136b15d-5078-4bcd-954b-e89bcb8aed1b # ╠═6a508a62-61b9-4273-8e45-b26f594e8da9 # ╟─63420055-55f8-4def-8b0e-11ea61483010 diff --git a/src/exercises/calib_intro.jl b/src/exercises/calib_intro.jl index 766e2631..ed13fd6e 100644 --- a/src/exercises/calib_intro.jl +++ b/src/exercises/calib_intro.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.6 +# v0.20.21 #> [frontmatter] -#> order = "24" -#> title = "5. Calibration intro" -#> date = "2025-08-06" +#> order = "31" +#> title = "6. Calibration intro" #> tags = ["exercises"] -#> description = "Calibration intro" #> layout = "layout.jlhtml" +#> description = "Calibration intro" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -15,20 +14,34 @@ 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 - -# ╔═╡ 7f521435-63ac-4178-a4aa-93d9c45fe820 -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ 015050b3-3339-4b1a-ad7d-c358cce73675 -using Catalyst, OrdinaryDiffEq, StatsPlots +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, StatsBase +using Turing, StatsPlots, StatsBase # ╔═╡ 6e227e07-166a-41ce-839a-4c4c72addb23 using LinearAlgebra, Optim @@ -48,23 +61,27 @@ md""" # ╔═╡ 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. +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. +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 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 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, 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. +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 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. +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 with either MLE, MAP or MCMC. To illustrate this concept, we first go through three simple models for grass growth yield. +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 @@ -78,11 +95,11 @@ In this notebook, three different models will be used, each modelling the yield - 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$ +- 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: +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$ | +| | $\mu$ | $W_f$ | $d$ | $W_0$ | |:----------- |:----------:|:-----------:|:------------:|:------------:| | Logistic | 0.07 | 10.0 | | 2.0 | | Exponential | 0.02 | 10.0 | | 2.0 | @@ -99,43 +116,11 @@ 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 +# ╔═╡ 2481cd4f-0efc-4450-ab3d-4a5492597f36 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. +Variables containing the initial condition and parameters values will be defined later in the objective function. """ -# ╔═╡ 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 @@ -143,7 +128,7 @@ md""" # ╔═╡ b2b433ed-0266-4bea-a7e8-32adba542d4c md""" -Assume that the measured grass yields (of a certain plant type) over time are the following: +Assume that the measured grass yields (of a certain plant type) are the following: """ # ╔═╡ 7c966a66-0091-4b81-9a7e-02ccd0d3db10 @@ -157,14 +142,6 @@ 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: @@ -177,76 +154,157 @@ scatter(t_meas, W_meas, title="Grass growth data", xlims=(0, 100), ylims=(0, 14)) -# ╔═╡ ee32ecfe-519a-4603-bdd9-a1452593e31d -savefig("calibration.png") +# ╔═╡ 50e6af5b-04f4-495c-853b-c746fd27254f +md"## Calibration with Turing" -# ╔═╡ d75246d4-e03b-4684-be7d-4bcfb61ed7ef +# ╔═╡ a665740a-ea94-4452-8cbc-a1647319dfce md""" -### Declaration of the Turing model function +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 """ -# ╔═╡ dd3a32f1-bdb6-44a3-acbe-f4269725c9e4 +# ╔═╡ b2f20a9a-0613-4bb4-a1d3-df1620aef105 +md"### The priors" + +# ╔═╡ 6852f166-350e-4434-8304-382c01113ba1 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$. +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. -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())`. +**Note that the second only applies to methods that take prior information into account, which Maximum Likelihood Estimation (MLE) does not.** """ -# ╔═╡ 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 +# ╔═╡ 4f78b2e8-9132-4175-b9ae-9f5979649e90 +md"#### Choice of priors for the grass growth models" -# ╔═╡ 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 +# ╔═╡ 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). +""" -# ╔═╡ 0ccf1f1d-20be-4c19-bfc6-2748970547a1 +# ╔═╡ 237934b7-c89a-4ef6-938f-4629c0fa4c95 md""" -Here is our Turing model function: +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. """ -# ╔═╡ 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 +# ╔═╡ 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 -# ╔═╡ 48c9f616-d298-40da-b917-225abd39b3d9 +# ╔═╡ 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""" -!!! 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 Σ. +#### ̇2) ModelingToolkit based model +""" + +# ╔═╡ 50220e1f-8e42-44da-be03-08c11df967f0 +@variables W(t) - 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`. +# ╔═╡ 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 """ -# ╔═╡ 4987f361-fcce-4453-8872-11d6faf16e86 -rand(Uniform(0, 20)) +# ╔═╡ 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 -# ╔═╡ 3d3db44c-db49-436a-9b6e-9f6dd5d6178b -rand(MvNormal([1.7, 4.5, 3.6], 0.5^2 * I)) +# ╔═╡ 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 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$: +We will provide the measurements to the Turing model: """ -# ╔═╡ 98a71080-e662-4f1a-88fe-3c575386ec77 -growth_log_cond_mod = growth_log_fun(t_meas) | (W_s = W_meas,) +# ╔═╡ 8b6534d6-776b-4285-8498-a9b34051facc +growth_log_inf = growth_log_inference(t_meas) | (W_s = W_meas, ) # ╔═╡ a6972aef-63ad-401c-acf5-6d59f9fc6698 md""" @@ -260,19 +318,32 @@ md""" # ╔═╡ 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. +We will use the MLE (Maximum Likelihood Estimation) method here and store the optimization results in `results_log_mle`. """ -# ╔═╡ dff0f8c6-1fed-4ef7-a6d3-2c8f345ed724 -results_log_mle = optimize(growth_log_cond_mod, MLE(), NelderMead()) +# ╔═╡ 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`: +You can visualize a summary of the optimized parameters by piping them to `coeftable`. Beware that this can take a lot of time... """ -# ╔═╡ 75ee0ba9-c9f9-496e-bb80-d013328c3ad7 -coeftable(results_log_mle) +# ╔═╡ 80e7f6b8-7592-48a3-8587-f1953d1bfcd8 +# results_log_mle |> coeftable # ╔═╡ a1ca7d0e-639c-42d4-be09-5c61a2008f29 md""" @@ -299,7 +370,7 @@ Setting up initial condition with optimized initial condition: """ # ╔═╡ 590b1006-0e37-4668-9b4a-3588fab45696 -u0_opt1_log = [:W => W0_opt1_log] +u0_opt1_log = [:W=>W0_opt1_log] # ╔═╡ 8ff28a2e-185d-4dca-ad3b-a0b1507646d6 md""" @@ -307,7 +378,7 @@ Setting up parameter values with optimized parameter values: """ # ╔═╡ 14d24cbd-3259-41bb-9013-b4fe25a3be4c -params_opt1_log = [:μ => μ_opt1_log, :Wf => Wf_opt1_log] +parms_opt1_log = [:μ=>μ_opt1_log, :Wf=>Wf_opt1_log] # ╔═╡ 6c134677-3ec1-4e0a-88b5-01341a096675 md""" @@ -315,7 +386,7 @@ 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) +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) @@ -328,7 +399,7 @@ Finally, we plot $W$ simulated with the optimized initial value and parameter va # ╔═╡ 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") + xlims=(0, 100), ylims=(0, 14)) scatter!(t_meas, W_meas, label="Yield") end @@ -339,31 +410,23 @@ md""" # ╔═╡ 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. +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_cond_mod, MAP(), NelderMead()) +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`: +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 -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) +# results_log_map |> coeftable # ╔═╡ 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: +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 @@ -386,7 +449,7 @@ Setting up initial condition with optimized initial condition: """ # ╔═╡ a570ebab-64de-4934-b887-77a8e2fb42e8 -u0_opt2_log = [:W => W0_opt2_log] +u0_opt2_log = [:W=>W0_opt2_log] # ╔═╡ feee9cc1-ebe2-4c77-a11e-53ca00864ccf md""" @@ -394,7 +457,7 @@ Setting up parameter values with optimized parameter values: """ # ╔═╡ 73e86176-f314-463c-8821-51c0f3cc1a56 -params_opt2_log = [:μ => μ_opt2_log, :Wf => Wf_opt2_log] +parms_opt2_log = [:μ=>μ_opt2_log, :Wf=>Wf_opt2_log] # ╔═╡ c17c4e00-8688-4f9c-b0ae-741d70601aba md""" @@ -402,7 +465,7 @@ 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) +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) @@ -415,7 +478,7 @@ Finally, we plot $W$ simulated with the optimized initial value and parameter va # ╔═╡ 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") + xlims=(0, 100), ylims=(0, 14)) scatter!(t_meas, W_meas, label="Yield") end @@ -430,36 +493,22 @@ We will use Markov chain Monte Carlo (MCMC) method in combination with the No U- """ # ╔═╡ 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: -""" +results_log_nuts = sample(growth_log_inf, NUTS(), 500) -# ╔═╡ 866402ca-47ce-4a24-a963-34cd15f8483b -plot(results_log_nuts) +# ╔═╡ 78a608a4-0ba7-400e-abb9-a9285c60681c +plot(results_log_nuts) # check convergence # ╔═╡ 19c362cb-2764-41c9-a571-2e8e2bfcde93 -summarize(results_log_nuts) +# 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]) +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[:μ]) +μ_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]) +Wf_opt3_log = mean(results_log_nuts[:Wf]) # ╔═╡ b843ee5e-9618-4b50-93db-77e19b4be366 md""" @@ -472,7 +521,7 @@ Setting up initial condition with optimized initial condition: """ # ╔═╡ ecf4b951-9b5f-441a-89f5-b0ccd040ba02 -u0_opt3_log = [:W => W0_opt3_log] +u0_opt3_log = [:W=>W0_opt3_log] # ╔═╡ b92bf532-5610-4ce8-b469-154b20cc5956 md""" @@ -480,7 +529,7 @@ Setting up parameter values with optimized parameter values: """ # ╔═╡ e629fbd1-a7c7-4dc3-8b7d-bdca380fe5ad -params_opt3_log = [:μ => μ_opt3_log, :Wf => Wf_opt3_log] +parms_opt3_log = [:μ=>μ_opt3_log, :Wf=>Wf_opt3_log] # ╔═╡ a48df980-4847-418e-813d-1875d032ffd9 md""" @@ -488,19 +537,11 @@ 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) +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) -# ╔═╡ 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. @@ -508,16 +549,9 @@ Finally, we plot $W$ simulated with the optimized initial value and parameter va # ╔═╡ 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") + 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 @@ -534,111 +568,100 @@ Calibrate the initial condition and both parameters of the exponential growth mo # ╔═╡ 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: +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 -# ╔═╡ 85c57cd3-bf00-437e-937b-f0c3b62f74ff -parameters(growth_exp) - -# ╔═╡ c6d373f4-f13c-4135-823d-ee8fbeb71b56 +# ╔═╡ d69ed5dd-80cd-427e-9245-e42576a4688d md""" -Create an `ODEProblem`. Use the values in the aforementioned table as initial values for the problem. Use the same `tspan` as before. +Convert the reaction network model into an ODE system to verify. """ -# ╔═╡ 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. -""" +# ╔═╡ 8b429656-1c57-4278-9633-71311f400ed8 +# missing +convert(ODESystem, growth_exp) # ╔═╡ b4300e8a-8052-419b-98c8-0508ebee2393 md""" -Declare the Turing model function. +Declare the Turing model. Try to find sensible prior distributions. """ # ╔═╡ 2c6ae74c-2da4-4867-ad8a-f4e835101d63 -# Uncomment and complete the instruction -# @model function growth_exp_fun(t_meas) +# @model function growth_exp_inference(t_meas, W_meas) # σ_W ~ missing # W0 ~ missing # μ ~ missing # Wf ~ missing # u0_exp = missing -# params_exp = missing +# parms_exp = missing # oprob_exp = missing # osol_exp = missing -# W_s ~ 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 time measurements to the defined function (this results in the Turing model) and instantly condition the Turing model with the measurements of $W$: +Provide the measurements to the Turing model. """ # ╔═╡ fff17cf7-173d-4f64-94a9-4bf46acc882d -# growth_exp_cond_mod = missing # Uncomment and complete the instruction +# 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 both the `MLE` and `MAP` methods and the Nelder-Mead algorithm. Store the optimization results in `results_exp_mle` and `results_exp_map`. +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 # Uncomment and complete the instruction - -# ╔═╡ a7fc5f89-0341-4de2-962b-935c6d2f1f9d -# results_exp_map = missing +# results_exp_mle = missing +results_exp_mle = optimize(growth_exp_inf, MLE(), NelderMead()) # ╔═╡ c81d0140-3f4e-4eb4-8a77-1f48c5e0ecbf md""" -Visualize a summary of the MLE and MAP optimized parameters. +Visualize a summary of the optimized parameters. Beware: this can take a lot of time... """ # ╔═╡ 7456455b-4f31-488f-990f-6ce534038e08 -# missing # For MLE - -# ╔═╡ 4b057098-4dd8-438a-8699-f0f792f70370 -# missing # For MAP +# missing +# results_exp_mle |> coeftable # ╔═╡ 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`. +Get the optimized values and assign them to `W0_opt_exp`, `μ_opt_exp` and `Wf_opt_exp`. """ # ╔═╡ 23b629a6-6866-416a-a768-9617ce6301db -# W0_opt_mle_exp = missing # Uncomment and complete the instruction +# W₀_opt_exp = missing +W0_opt_exp = coef(results_exp_mle)[:W0] # ╔═╡ 8788082d-f5d1-4385-8037-a0d360a841c7 -# μ_opt_mle_exp = missing # Uncomment and complete the instruction +# μ_opt_exp = missing +μ_opt_exp = coef(results_exp_mle)[:μ] # ╔═╡ 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; +# Wf_opt_exp = missing +Wf_opt_exp = coef(results_exp_mle)[:Wf] # ╔═╡ 8f5e1413-227c-44fa-bb2d-3653cbc27e38 md""" @@ -647,47 +670,34 @@ Make a plot of $W$ simulated with the optimized initial condition and parameter # ╔═╡ 8a7f7aab-878e-41b5-b9da-d06747df042e md" -Set up initial condition with the MLE optimized initial condition: +Set up initial condition with optimized initial condition: " # ╔═╡ 30602ff1-041b-4fca-bf8e-55ff57df9e37 -# u0_opt_mle_exp = missing # Uncomment and complete the instruction +# u₀_opt_exp = missing +u0_opt_exp = [:W=>W0_opt_exp] # ╔═╡ 881011be-6434-416f-915b-3333e8dea32f md""" -Set up parameter values with the MLE optimized parameter values: +Set up parameter values with 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; +# 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. Solve it using `Tsit5()` and `saveat=0.5`. +Create an ODEProblem and solve it. Use `Tsit5()` and `saveas=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; +# oprob_opt_exp = missing +oprob_opt_exp = ODEProblem(growth_exp, u0_opt_exp, tspan, parms_opt_exp) # ╔═╡ 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; +# osol_opt_exp = missing +osol_opt_exp = solve(oprob_opt_exp, Tsit5(), saveat=0.5) # ╔═╡ 8e047be9-f0f0-4a75-91b5-c523f55f8c67 md""" @@ -695,13 +705,15 @@ Plot $W$ simulated with the optimized initial value and parameter values togethe """ # ╔═╡ 1a9587aa-2356-48df-abcc-2ce874fa5d24 -# Uncomment and complete the instruction # begin -# missing -# missing -# missing -# missing +# 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""" @@ -712,76 +724,68 @@ Calibrate the initial condition and both parameters of the Gompertz growth model # ╔═╡ 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: +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. """ -# ╔═╡ 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. -""" +# ╔═╡ 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. Take the same priors as before. +Declare the Turing model. Try to find sensible prior distributions. """ -# 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) +# @model function growth_gom_inference(t_meas, W_meas) # σ_W ~ missing # W0 ~ missing # μ ~ missing -# D ~ missing +# d ~ missing # u0_gom = missing -# params_gom = missing +# parms_gom = missing # oprob_gom = missing # osol_gom = missing -# W_s ~ 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 time measurements to the defined function (this results in the Turing model) and instantly condition the Turing model with the measurements of $W$: +Provide the measurements to the Turing model. """ # ╔═╡ cd1cf2f8-9f7f-4ed4-9cb7-1a6efee68ab4 -# growth_gom_cond_mod = missing # Uncomment and complete the instruction +# 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 now with `MAP` method and Nelder-Mead. Store the optimization results in `results_gom_map`. +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_map = missing # Uncomment and complete the instruction +# 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. +Visualize a summary of the optimized parameters. Beware: this can take a lot of time... """ # ╔═╡ 9c239fc6-275c-4d64-9fa2-6fd57295b757 -# missing # Uncomment and complete the instruction +# missing +# results_gom_mle |> coeftable # ╔═╡ dede17f2-655d-4871-b6de-5a32804947dd md""" @@ -789,13 +793,16 @@ Get the optimized values and assign them to `W0_opt_gom`, `μ_opt_gom` and `D_op """ # ╔═╡ e8c3f042-6058-4040-a67e-18563c04ee93 -# W0_opt_gom = missing # Uncomment and complete the instruction +# W₀_opt_gom = missing +W0_opt_gom = coef(results_gom_mle)[:W0] # ╔═╡ 665f4d03-3521-475a-a195-f861fd26bb69 -# μ_opt_gom = missing # Uncomment and complete the instruction +# μ_opt_gom = missing +μ_opt_gom = coef(results_gom_mle)[:μ] # ╔═╡ 13775d58-ac61-431c-a6c9-447c1eec7942 -# D_opt_gom = missing # Uncomment and complete the instruction +# D_opt_gom = missing +d_opt_gom = coef(results_gom_mle)[:d] # ╔═╡ bc92f996-b626-4965-a286-d2c848eb1a21 md""" @@ -808,7 +815,8 @@ Set up initial condition with optimized initial condition: """ # ╔═╡ 23fd9fd9-a13c-4c56-a0b4-daec6f1d2cd8 -# u0_opt_gom = missing # Uncomment and complete the instruction +# u₀_opt_gom = missing +u0_opt_gom = [:W=>W0_opt_gom] # ╔═╡ 6f414d15-af0a-452a-98a1-dc0b7e54d617 md""" @@ -816,18 +824,21 @@ Set up parameter values with optimized parameter values: """ # ╔═╡ 57ee8a12-24df-4598-935c-f5e259b504cb -# params_opt_gom = missing # Uncomment and complete the instruction +# 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 the solver `Tsit5()` and `saveat=0.5`. +Create an ODEProblem and solve it. Use `Tsit5()` and `saveas=0.5`. """ # ╔═╡ 5b9b2e5b-9deb-4ff6-a923-b15b8b08f0c9 -# oprob_opt_gom = missing # Uncomment and complete the instruction +# 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 # Uncomment and complete the instruction +# osol_opt_gom = missing +osol_opt_gom = solve(oprob_opt_gom, Tsit5(), saveat=0.5) # ╔═╡ 52d7975a-5346-447f-9aad-4ecd11b6460a md""" @@ -835,30 +846,33 @@ Finally, we plot $W$ simulated with the optimized initial value and parameter va """ # ╔═╡ e96efd6a-a666-4120-8480-9423e5d82ae1 -# Uncomment and complete the instruction # begin -# missing -# missing +# 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""" -!!! question - Which grass growth model fits best these data? How can you prove this numerically? +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 -# ╠═7f521435-63ac-4178-a4aa-93d9c45fe820 # ╠═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 -# ╟─37da8786-fea0-4c2f-a76f-6e6c68325a78 # ╟─4623369d-8c5a-422d-9e40-0f1dd7586260 # ╟─3cb0a166-ac53-4c3f-9832-e93742040cfb # ╟─987f0a4d-e416-4ceb-adbe-3dcdca9d0996 @@ -867,42 +881,47 @@ md"- Answer: missing" # ╟─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 +# ╟─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 -# ╟─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 +# ╟─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 -# ╟─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 +# ╠═8a9115eb-4044-4cab-a7db-39b5dd86c70d # ╟─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 +# ╠═8b6534d6-776b-4285-8498-a9b34051facc # ╟─a6972aef-63ad-401c-acf5-6d59f9fc6698 # ╟─73e35289-6dc0-4e2e-83eb-b56f83cdbbbf # ╟─47bd729c-4851-42f7-a03f-6ceacd3c717e -# ╠═dff0f8c6-1fed-4ef7-a6d3-2c8f345ed724 +# ╠═f34bb7ac-1ed8-4dd9-b0b9-49bd6e0e1d71 +# ╟─a4dc130a-87ef-4b77-9b62-07f64917e34d # ╟─e55404ab-6762-4f39-bb42-9c195334a214 -# ╠═75ee0ba9-c9f9-496e-bb80-d013328c3ad7 +# ╠═80e7f6b8-7592-48a3-8587-f1953d1bfcd8 # ╟─a1ca7d0e-639c-42d4-be09-5c61a2008f29 # ╠═30399b9a-1d77-4140-9ad3-5eed636a5b99 # ╠═e1b8e4ba-c1f1-48d5-87a2-edce19c9fe7a @@ -922,8 +941,6 @@ md"- Answer: missing" # ╠═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 @@ -941,8 +958,7 @@ md"- Answer: missing" # ╟─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 +# ╠═78a608a4-0ba7-400e-abb9-a9285c60681c # ╠═19c362cb-2764-41c9-a571-2e8e2bfcde93 # ╠═93db47b2-34e8-43b4-beac-b5620fd444e7 # ╠═68c71cd2-6cdc-4b80-b6e7-75bfea344295 @@ -955,58 +971,39 @@ md"- Answer: missing" # ╟─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 +# ╟─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 -# ╠═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 +# ╠═6e85c08e-9a97-4897-9c81-89517f55e254 # ╟─e5081280-d226-4834-8932-c89becd8313c # ╠═c739a908-2353-4e7a-8fbd-f640dc8cabe0 # ╟─1d0383ad-54d6-4ff2-8555-def83bfff0e6 @@ -1030,4 +1027,4 @@ md"- Answer: missing" # ╟─52d7975a-5346-447f-9aad-4ecd11b6460a # ╠═e96efd6a-a666-4120-8480-9423e5d82ae1 # ╟─f0b4772d-a72b-44e0-a3a1-ba9ad4c4dfeb -# ╟─7e98a771-52bb-484e-82ab-2e42e7cb4053 +# ╠═7e98a771-52bb-484e-82ab-2e42e7cb4053 diff --git a/src/exercises/calib_irrigation.jl b/src/exercises/calib_irrigation.jl index e54d5e1c..29028fe9 100644 --- a/src/exercises/calib_irrigation.jl +++ b/src/exercises/calib_irrigation.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "26" -#> title = "5. Calibration irrigation" -#> date = "2025-08-06" +#> order = "33" +#> title = "6. Calibration irrigation" #> tags = ["exercises"] -#> description = "Calibration irrigation" #> layout = "layout.jlhtml" +#> description = "Calibration irrigation" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,28 +15,24 @@ 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 - -# ╔═╡ 6750d246-8e7a-4cfb-810e-d1100aa4fdef -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ 4947b0fd-13be-4f6a-b605-ed35b509d7ff -using Catalyst, OrdinaryDiffEq, StatsPlots +using ModelingToolkit, OrdinaryDiffEq # ╔═╡ 61d14819-ba44-40fe-95a9-9d7b0bf3dc33 -using Turing +using ModelingToolkit: t_nounits as t, D_nounits as D # ╔═╡ f6e77c8d-de11-4b9d-93c6-45bdcfbbbf9b -using StatsBase +using StatsPlots, StatsBase, Turing # ╔═╡ 9345dd8f-0a60-4aaf-a27f-ef8bf860f495 -using LinearAlgebra - -# ╔═╡ ea02aff2-7fb8-4b8f-8d0f-0bb3c6150708 -using Optim +using LinearAlgebra, Optim # ╔═╡ 55d5400d-1777-4918-a030-b94cb9a59f63 md" @@ -60,18 +55,20 @@ $$\begin{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: -""" +# ╔═╡ 9f6ad49c-cfbe-46e2-a2dd-c01bb67eeb61 +# @variables missing -# ╔═╡ 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 +# ╔═╡ 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""" @@ -135,55 +132,42 @@ 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 +# ╔═╡ 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}$. Use `InverseGamma` for the standard deviations of the measurements, `LogNormal` for $k$ and `Uniform` (between 100 and 200) for $Smax$. +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 -# Uncomment and complete the instruction -# @model function irrigation_fun(t_meas) +# @model function irrigation_inference(t_meas) # σ_S1 ~ missing # σ_S2 ~ missing # k ~ missing # Smax ~ missing -# params = missing -# u01 = missing -# oprob1 = missing +# parms = missing +# # For experiment 1: +# u01 = missing +# oprob1 = missing # osol1 = missing -# S1_s1 ~ missing -# S2_s1 ~ missing -# u02 = missing -# oprob2 = missing +# S1_ex1 ~ missing +# S2_ex1 ~ missing +# # For experiment 2: +# u02 = missing +# oprob2 = missing # osol2 = missing -# S1_s2 ~ missing -# S2_s2 ~ missing +# S1_ex2 ~ missing +# S2_ex2 ~ 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: +Instantiate the model and condition it 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 +# irrigation_inf = missing # ╔═╡ f7f47956-7c3b-44cc-bff7-fb7d32af874a md""" @@ -191,15 +175,15 @@ Optimize the priors ($\sigma_{S1}$, $\sigma_{S2}$, $k$ and $S_{max}$). Do this w """ # ╔═╡ 8c254d5a-225b-4772-9fdd-e9f700495fbd -# results_mle = missing # Uncomment and complete the instruction +# results_mle = missing # ╔═╡ f15a1df5-047a-4f46-9419-8492ac1248e0 md""" -Visualize a summary of the optimized parameters. +Visualize a summary of the optimized parameters. Beware that this may take a lot of time... """ # ╔═╡ 00d944e4-2c88-4a5d-b809-69f435df4684 -# missing # Uncomment and complete the instruction +# missing # ╔═╡ 89eb31ef-b24f-44c8-bbe5-19101d859937 md""" @@ -207,10 +191,10 @@ 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 +# k_opt = missing # ╔═╡ 35ab6ee5-fcd7-4dcc-9909-cc918fb1fe80 -# Smax_opt = missing # Uncomment and complete the instruction +# Smax_opt = missing # ╔═╡ 4026773f-ac5b-433e-bd9d-2122242861fd md""" @@ -223,7 +207,7 @@ Set up parameter values with optimized parameter values: """ # ╔═╡ 97d53e48-590a-485b-bcf3-edc6a6124faf -# params_opt = missing # Uncomment and complete the instruction +# params_opt = missing # ╔═╡ dfd2ac98-5cdc-4627-b6cf-71b33c0ff0d4 md""" @@ -231,16 +215,15 @@ Plot the simulation results $S_1$ and $S_2$ for the 1st experiment together with """ # ╔═╡ 95ace332-52c0-46c3-ae28-d038320ed2c8 -# u01 = missing # Uncomment and complete the instruction +# u01 = missing # ╔═╡ 6ae63a13-d5ae-4dfb-b88d-be295b11a472 -# oprob1_opt = missing # Uncomment and complete the instruction +# oprob1_opt = missing # ╔═╡ bc6505ca-a61d-467f-afe6-47792a510ad5 -# osol1_opt = missing # Uncomment and complete the instruction +# osol1_opt = missing # ╔═╡ 67e423ea-e941-45bf-af4f-3fdecb648fbc -# Uncomment and complete the instruction # begin # missing # missing @@ -253,16 +236,15 @@ Plot the simulation results $S_1$ and $S_2$ for the 1st experiment together with """ # ╔═╡ a7040b8e-c240-415b-8a9a-4a1a137398d4 -# u02 = missing # Uncomment and complete the instruction +# u02 = missing # ╔═╡ fe8f4961-68bd-42dc-a3f5-6692e918e241 -# oprob2_opt = missing # Uncomment and complete the instruction +# oprob2_opt = missing # ╔═╡ 7f280230-7846-4529-a2ff-a81a2b9480bf -# osol2_opt = missing # Uncomment and complete the instruction +# osol2_opt = missing # ╔═╡ ad9818a9-ccbe-4645-8b91-0c3fa773632a -# Uncomment and complete the instruction # begin # missing # missing @@ -280,17 +262,18 @@ 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 +# ╠═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 @@ -304,11 +287,7 @@ md"- Answer: missing" # ╠═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 +# ╠═a65a0997-9945-436e-925b-8fc02055f61a # ╟─923d04ce-b4d2-44b0-afff-7062c4628ad0 # ╠═481eb8b9-5de2-4f68-b06a-ec18e054c9f5 # ╟─df933ae8-1f51-4467-93a7-33f153e5e4f8 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/ssa_model_catalyst_intro.jl b/src/exercises/dje_model_catalyst_intro.jl similarity index 85% rename from src/exercises/ssa_model_catalyst_intro.jl rename to src/exercises/dje_model_catalyst_intro.jl index 4545f752..26dfe609 100644 --- a/src/exercises/ssa_model_catalyst_intro.jl +++ b/src/exercises/dje_model_catalyst_intro.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.6 +# v0.20.21 #> [frontmatter] -#> order = "14" -#> title = "2. SSA catalyst intro" -#> date = "2025-02-07" +#> order = "20" +#> title = "3. DJE_model_Catalyst_intro" #> tags = ["exercises"] -#> description = "SSA catalyst intro" #> layout = "layout.jlhtml" +#> description = "Introduction to solving discrete jump problems with Catalyst" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,17 +15,12 @@ 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 +# 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 - -# ╔═╡ 2a1306f3-d811-459d-83ca-98cf62dc2db0 -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ 4e03b93e-f63e-466c-9941-d66e62306010 using PlutoUI; TableOfContents() @@ -35,31 +29,23 @@ using PlutoUI; TableOfContents() using Catalyst # ╔═╡ 0b3921c9-6d6e-4c52-8c21-d883ed493028 -using OrdinaryDiffEq, JumpProcesses, StatsPlots +using JumpProcesses, StatsPlots # ╔═╡ 62b185be-e327-4ef3-af39-819732d107bf md""" -# Introduction to Catalyst (SSA) +# Introduction: Solving discrete jump problems with Catalyst """ # ╔═╡ 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). +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. """ -# ╔═╡ 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. +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: @@ -127,14 +113,9 @@ 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 +## Implementation of the system First we create a *reaction network object*, that we have named `infection_model`, that implements the aforementioned *reactions*. """ @@ -188,12 +169,7 @@ 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. +## Simulating the system as a (Discrete) Jump problem """ # ╔═╡ 78e55389-3431-41de-9f4a-b6b45cb8988b @@ -232,13 +208,20 @@ 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] +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) @@ -258,7 +241,7 @@ We create a DiscreteProblem by calling the `DiscreteProblem` function. Applying """ # ╔═╡ e2f215e7-8d20-4e72-9e0b-cc0ce150e55b -dprob = DiscreteProblem(infection_model, u0, tspan, params) +dprob = DiscreteProblem(infection_model, u0, tspan, parms); # ╔═╡ d061c4e2-8fbb-4ac5-95cf-c37d2916e669 md""" @@ -275,11 +258,12 @@ md""" # ╔═╡ 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. +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, SSAStepper()) +dsol = solve(jprob) +# dsol = solve(jprob, SSAStepper()) # also possible # ╔═╡ 5cfe9878-cbc2-4852-a757-ad8247d1f3d6 md""" @@ -304,7 +288,7 @@ Below is a piece of code that solves the problem a $1000$ times and stores the t begin times = [] # make empty vector while length(times) < 1000 # while statement - dsol2 = solve(jprob, SSAStepper()) # solve the problem + 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 @@ -330,14 +314,13 @@ 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 -# ╠═2a1306f3-d811-459d-83ca-98cf62dc2db0 # ╠═a2582acb-7d17-43ab-b883-d766b1a2c984 # ╠═4e03b93e-f63e-466c-9941-d66e62306010 -# ╟─62b185be-e327-4ef3-af39-819732d107bf +# ╠═9e8fd818-a14f-41cf-b2fd-a7425141b283 +# ╠═0b3921c9-6d6e-4c52-8c21-d883ed493028 # ╟─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 @@ -346,8 +329,6 @@ histogram(times, bins=range(0, 60, length=61)) # ╟─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 @@ -361,8 +342,6 @@ histogram(times, bins=range(0, 60, length=61)) # ╟─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 @@ -371,6 +350,7 @@ histogram(times, bins=range(0, 60, length=61)) # ╟─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 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/exercises.md b/src/exercises/exercises.md index 9b44cae0..d9f7a750 100644 --- a/src/exercises/exercises.md +++ b/src/exercises/exercises.md @@ -39,4 +39,4 @@ Change this to the your current folder so that the Project and Manifest files ar ```julia using Pkg Pkg.activate(".") -``` +``` \ No newline at end of file diff --git a/src/exercises/model_selection_intro.jl b/src/exercises/model_selection_intro.jl index f6f8d857..e5743164 100644 --- a/src/exercises/model_selection_intro.jl +++ b/src/exercises/model_selection_intro.jl @@ -1,21 +1,19 @@ ### A Pluto.jl notebook ### -# v0.20.6 +# v0.20.21 #> [frontmatter] -#> order = "35" -#> title = "7. Model selection intro" -#> date = "2025-08-06" +#> order = "42" +#> title = "8. Model selection intro" #> tags = ["exercises"] -#> description = "Model selection intro" #> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" +#> 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 @@ -28,13 +26,13 @@ using InteractiveUtils using PlutoUI; TableOfContents() # ╔═╡ cdcebbb1-40e0-457f-a6ec-b769f6b1f2e9 -using OrdinaryDiffEq, StatsPlots +using OrdinaryDiffEq # ╔═╡ 22489bd4-ab64-4bf9-ad03-5372ea273935 using Catalyst # ╔═╡ 4ad72a6a-f541-4d0a-8392-56369e29ac96 -using Turing, StatsBase +using Turing, StatsPlots, StatsBase # ╔═╡ 70590d28-058a-44df-8cf6-092d8c87438c using LinearAlgebra, Optim @@ -447,7 +445,7 @@ function posterior(AICs) # AICs vector of AIC values AICmin = minimum(AICs) posterior = zeros(length(AICs)) - for i in eachindex(1:length(AICs)) + for i in 1:length(AICs) posterior[i] = exp((AICmin-AICs[i])/2) end @@ -915,16 +913,16 @@ Use the above implemented function `posterior` to calculate the posterior model # posteriors # ╔═╡ 72dc27a0-0e61-4073-843a-67e1bfd1427a -# md""" -# We can summarize all calculated criteria so far in the following table: +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]) | +| 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""" @@ -1309,7 +1307,7 @@ md""" # ╟─3bf0c957-f08b-4a45-90e0-40d56330c8e0 # ╠═947e26fe-2010-48ad-b927-d4e5186f21e0 # ╠═3506765b-a6cf-41ca-9201-fcc650c6f56d -# ╠═72dc27a0-0e61-4073-843a-67e1bfd1427a +# ╟─72dc27a0-0e61-4073-843a-67e1bfd1427a # ╟─6240553c-0c7c-45f4-b190-ebacf09ab632 # ╠═12f64333-36d0-4e2a-9061-e3dcc7a4ae96 # ╟─95ba48fe-f430-4f47-ab04-f5896d75343b diff --git a/src/exercises/ode_model_anaerobic_fermentation.jl b/src/exercises/ode_model_XTRA_anaerobic_fermentation.jl similarity index 81% rename from src/exercises/ode_model_anaerobic_fermentation.jl rename to src/exercises/ode_model_XTRA_anaerobic_fermentation.jl index 8471bb8e..dec38649 100644 --- a/src/exercises/ode_model_anaerobic_fermentation.jl +++ b/src/exercises/ode_model_XTRA_anaerobic_fermentation.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "10" -#> title = "EXTRA. ODE anaerobic fermentation" -#> date = "2025-02-07" +#> order = "14" +#> title = "2. ODE_model_Xtra_anaerobic_fermentation" #> tags = ["exercises"] -#> description = "ODE model of anaerobic fermentation" #> layout = "layout.jlhtml" +#> description = "Extra exercise on anaerobic fermentation" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,32 +15,29 @@ 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 +# 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 - -# ╔═╡ 3cd7556f-73b8-4176-a779-ad38909f464d -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ 896b4151-e26b-40ee-bfb9-c56dfc4e7048 -using Catalyst +using StatsPlots, PlutoUI; TableOfContents() # ╔═╡ aaf6da21-60cc-478c-b447-3f33aa375240 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ e119ed70-fd81-4978-92d9-696cad71125f -using PlutoUI; TableOfContents() +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 @@ -82,7 +78,7 @@ Create a *reaction network object* model for the aforementioned problem in order 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(..., ..., ...)`! +- 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 @@ -99,7 +95,7 @@ Check out the species. """ # ╔═╡ 86200a5e-55ce-4d58-87ba-fe30f65c9120 -# missing # Uncomment and complete the instruction +missing # ╔═╡ 579318a9-bad9-4392-9420-3f5474ccfff8 md""" @@ -107,7 +103,7 @@ Convert the system to a symbolic differential equation model and inspect your di """ # ╔═╡ bc56d0fb-db19-4f63-9f34-617308da6c8a -# osys1 = missing # Uncomment and complete the instruction +osys1 = missing # ╔═╡ 5983b9d3-5119-47ac-9a0c-8a2755ad23e6 md""" @@ -115,7 +111,7 @@ Initialize a vector `u01` with the initial conditions: """ # ╔═╡ c822fcbc-e8d3-4952-93bc-38a41f1786a7 -# u01 = missing # Uncomment and complete the instruction +u01 = missing # ╔═╡ eb7fda8e-d1db-4bcc-91d6-964a6dcbbe90 md""" @@ -123,15 +119,15 @@ Set the timespan for the simulation: """ # ╔═╡ 124571f0-fe33-44a2-9ab4-f6a418ac4f51 -# tspan1 = missing # Uncomment and complete the instruction +tspan1 = missing # ╔═╡ 703d2eb0-8504-496f-9abe-8c6911a0fdbc md""" -Initialize a vector `params1` with the parameter values: +Initialize a vector `parms1` with the parameter values: """ # ╔═╡ 9dd45f30-9076-45d5-827b-02ae2d73ef97 -# params1 = missing # Uncomment and complete the instruction +parms1 = missing # ╔═╡ 64f85604-9562-4a97-bedd-ea5d28ada096 md""" @@ -139,7 +135,7 @@ Create the ODE problem and store it in `oprob1`: """ # ╔═╡ 04816732-3419-40e3-a927-d6e102949553 -# oprob1 = missing # Uncomment and complete the instruction +oprob1 = missing; # ╔═╡ 9a517512-a7c0-4a92-a104-b19a8bf786c7 md""" @@ -147,7 +143,7 @@ Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `os """ # ╔═╡ 69f9ce3a-0422-499a-8492-ea70de43d587 -# osol1 = missing # Uncomment and complete the instruction +osol1 = missing # ╔═╡ f1c134c9-9127-4ed4-84a9-e1d218a82295 md""" @@ -155,13 +151,17 @@ Plot the results. Use a line width of 2 (`linewidth=...`). """ # ╔═╡ e8eeefd3-f83a-4154-945d-568ac629cd94 -# missing # Uncomment and complete the instruction +missing # ╔═╡ 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? +# ╔═╡ 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 @@ -169,7 +169,8 @@ md"- Answer: missing" # ╔═╡ 192ec8c6-ff60-4584-91f9-d257399bac76 md""" -2. Try to explain the peak in the glucose ($G$) concentration. +!!! question + 2. Try to explain the peak in the glucose ($G$) concentration. """ # ╔═╡ e2681cdb-581d-41d7-93c6-b137ef54c083 @@ -177,7 +178,8 @@ md"- Answer: missing" # ╔═╡ 9f5bf914-7ec6-48ba-8fed-2087f42b37d8 md""" -3. Why is the difference in ethanol ($E$) and $CO_2$ concentration constant? +!!! question + 3. Why is the difference in ethanol ($E$) and $CO_2$ concentration constant? """ # ╔═╡ e50013b7-be37-4fa9-bdd6-2a902443f222 @@ -220,7 +222,7 @@ Convert the system to a symbolic differential equation model and inspect your di """ # ╔═╡ 58b8545f-1699-45b4-91c0-86722668a428 -# osys2 = missing # Uncomment and complete the instruction +osys2 = missing # ╔═╡ f6d58bac-7256-4b57-836d-6d243bf292c0 md""" @@ -228,7 +230,7 @@ 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 +u02 = missing # ╔═╡ b4c4fc78-b6e4-40db-9948-6be83ee57d87 md""" @@ -236,15 +238,15 @@ Make an exact copy of `tspan1` and rename it to `tspan2`: """ # ╔═╡ 4a0e9abf-99e7-4fe6-82ae-aa958ab51dd0 -# tspan2 = missing # Uncomment and complete the instruction +tspan2 = missing # ╔═╡ 0ec39752-6654-452b-98db-eadd8c02b27f md""" -Make a copy of `params1`, rename it to `params2` and supplement it with the new parameter values: +Make a copy of `parms1`, rename it to `parms2` and supplement it with the new parameter values: """ # ╔═╡ 3aea0dbe-1cb3-4b19-bb38-53e58db153dd -# param2 = missing # Uncomment and complete the instruction +parms2 = missing # ╔═╡ 49984e8e-a194-464a-994f-501342800026 md""" @@ -252,7 +254,7 @@ Create the ODE problem and store it in `oprob2`: """ # ╔═╡ 18a3513f-8ecc-4c2f-bb39-5d0cb39a3d92 -# oprob2 = missing # Uncomment and complete the instruction +oprob2 = missing; # ╔═╡ 5318708e-3410-4675-8162-827c0c6e039c md""" @@ -260,7 +262,7 @@ Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `os """ # ╔═╡ e5476343-8376-4a9a-8286-f69e0d023939 -# osol2 = missing # Uncomment and complete the instruction +osol2 = missing # ╔═╡ 29a3cd1b-fd8f-4e5c-9ec7-a69fe328d824 md""" @@ -268,7 +270,7 @@ Plot the results. Use a line width of 2 (`linewidth=...`). If you only want to s """ # ╔═╡ 0c179e6b-bcaf-4e89-8d04-59f24b456127 -# missing # Uncomment and complete the instruction +missing # ╔═╡ e596f49e-1b3a-422e-be9a-f72133f042d5 md""" @@ -288,10 +290,10 @@ Tips: """ # ╔═╡ acc8acb3-f014-4108-80d7-bff893ce07d1 -# missing # Uncomment and complete the instruction +missing # ╔═╡ 053cca09-0e31-44c0-89db-26a581816744 -# osol2[:S][end], ..., ..., ..., ...) # Uncomment and complete the instruction +# osol2[:S][end], ..., ..., ..., ...) # ╔═╡ 83cfd592-896d-42f5-ae2a-b1d0a9f14aac md""" @@ -299,7 +301,7 @@ Create a vector named `u_guess` in the same way as `u02`, but now with the end v """ # ╔═╡ f5a8f97b-7cbb-4eb4-b5ff-82c184b7de85 -# u_guess2 = missing # Uncomment and complete the instruction +u_guess2 = missing # ╔═╡ ce67b1f7-a82d-4fa3-a22b-f8ee3d2f4af0 md""" @@ -307,7 +309,7 @@ Calculate the steady-state values of the species: """ # ╔═╡ 3a014879-883b-4cab-a7ac-0c3a708f5da6 -# Sw2, Iw2, Gw2, Ew2, CO2w2 = missing # Uncomment and complete the instruction +# Sw2, Iw2, Gw2, Ew2, CO2w2 = missing # ╔═╡ d7bde85c-05f1-4794-9e91-c4e5e120f876 md""" @@ -315,7 +317,7 @@ Check ou the steady states: """ # ╔═╡ 876683d7-b3cd-4a06-94b2-d38a1d4a34b6 -# missing +missing # ╔═╡ 55d303ec-29cb-4e8e-ab52-808d881ae0f2 md""" @@ -333,7 +335,7 @@ Create the correct condition. """ # ╔═╡ 166a21f0-46a5-4d7a-9f54-788d7a51a487 -# condition3 = missing # Uncomment and complete the instruction +condition3 = missing # ╔═╡ 5e6c5431-6fad-4272-a539-5e3af83fa226 md""" @@ -341,7 +343,6 @@ 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 @@ -350,8 +351,7 @@ Complete the *reaction network model*. """ # ╔═╡ 66441227-c202-48da-a6ed-58e25ffde3ce -# Uncomment and complete the instruction -# anaerobic_fermentation3_c_com = missing +anaerobic_fermentation3_c_com = missing # ╔═╡ 71b4bd6e-b906-4d88-a9a3-18700528ea12 md""" @@ -359,7 +359,7 @@ Create a new ODE problem. """ # ╔═╡ 16d0d671-464c-4de3-bd73-593e1a73168b -# oprob3 = missing # Uncomment and complete the instruction +oprob3 = missing; # ╔═╡ 66015536-5f23-45b5-8d57-f1d27d604f8c md""" @@ -367,7 +367,7 @@ Solve the new ODE problem. Make a `deepcopy`, use `Tsit5()` and `saveat=0.5`. """ # ╔═╡ 7f0777a2-6eb9-4c6d-9fb7-25a1f32f291d -# osol3 = missing +osol3 = missing # ╔═╡ 124d578f-42ea-488e-af65-312793f104ca md""" @@ -375,7 +375,7 @@ Plot the results. """ # ╔═╡ 9ad3aff4-76ea-452f-bd7b-fc379725df70 -# missing +missing # ╔═╡ e07e1667-2f2c-4086-9818-90dff54296de md""" @@ -386,13 +386,12 @@ Interprete the results. md"- Answer: missing" # ╔═╡ Cell order: +# ╟─bb10266a-1f6c-4fda-b276-6a9cf3a86e90 # ╠═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 +# ╟─cce70a4d-0911-48e1-9cc0-2a69fda6e882 # ╟─c3f85244-e0a8-4808-aa2d-15ab6bbb1b26 # ╟─2e7c2eab-a3bc-4574-a0fd-0a45b59b803b # ╟─35f2015d-9c28-4bc8-83cd-c185757db0dc @@ -415,11 +414,12 @@ md"- Answer: missing" # ╟─f1c134c9-9127-4ed4-84a9-e1d218a82295 # ╠═e8eeefd3-f83a-4154-945d-568ac629cd94 # ╟─622f65d3-0592-419f-8101-bc6f8b8ea5ed -# ╟─39e96a71-7483-4c85-b44d-76cafc3b22dc +# ╟─ad1347a2-9fbf-4681-b82b-4643d7337ed9 +# ╠═39e96a71-7483-4c85-b44d-76cafc3b22dc # ╟─192ec8c6-ff60-4584-91f9-d257399bac76 -# ╟─e2681cdb-581d-41d7-93c6-b137ef54c083 +# ╠═e2681cdb-581d-41d7-93c6-b137ef54c083 # ╟─9f5bf914-7ec6-48ba-8fed-2087f42b37d8 -# ╟─e50013b7-be37-4fa9-bdd6-2a902443f222 +# ╠═e50013b7-be37-4fa9-bdd6-2a902443f222 # ╟─939716cd-08c9-44a0-9217-146f9e312d35 # ╟─194a596b-22de-412e-8ead-94a8c02c98c6 # ╟─76b1d8ba-cc55-48f7-bee4-09f899582e62 @@ -439,7 +439,7 @@ md"- Answer: missing" # ╟─29a3cd1b-fd8f-4e5c-9ec7-a69fe328d824 # ╠═0c179e6b-bcaf-4e89-8d04-59f24b456127 # ╟─e596f49e-1b3a-422e-be9a-f72133f042d5 -# ╟─3505c10d-270c-49fc-a83a-3753974d4d5a +# ╠═3505c10d-270c-49fc-a83a-3753974d4d5a # ╟─5f865fec-09d5-4358-be57-0d568740968d # ╠═acc8acb3-f014-4108-80d7-bff893ce07d1 # ╠═053cca09-0e31-44c0-89db-26a581816744 @@ -464,4 +464,4 @@ md"- Answer: missing" # ╟─124d578f-42ea-488e-af65-312793f104ca # ╠═9ad3aff4-76ea-452f-bd7b-fc379725df70 # ╟─e07e1667-2f2c-4086-9818-90dff54296de -# ╟─91af34c0-9b38-4a62-8387-5e3c60b4b145 +# ╠═91af34c0-9b38-4a62-8387-5e3c60b4b145 diff --git a/src/exercises/ode_model_fermenter_firstorder.jl b/src/exercises/ode_model_XTRA_fermenter_firstorder.jl similarity index 69% rename from src/exercises/ode_model_fermenter_firstorder.jl rename to src/exercises/ode_model_XTRA_fermenter_firstorder.jl index 28ba97d9..c8943e77 100644 --- a/src/exercises/ode_model_fermenter_firstorder.jl +++ b/src/exercises/ode_model_XTRA_fermenter_firstorder.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "6" -#> title = "EXTRA. ODE fermentor first order" -#> date = "2025-02-07" +#> order = "13" +#> title = "2. ODE_model_Xtra_fermenter_firstorder" #> tags = ["exercises"] -#> description = "ODE model of a first order fermentor" #> layout = "layout.jlhtml" +#> description = "Extra exercise on a fermenter with first-order kinetics" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,23 +15,18 @@ 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 +# 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 +using Markdown, InteractiveUtils -# ╔═╡ 539c1823-16d0-4355-97b6-fe9f0b106864 -using InteractiveUtils +# ╔═╡ b33193b3-220e-49fc-acd1-6d1bec3caf88 +using StatsPlots, PlutoUI; TableOfContents() # ╔═╡ e66518ee-b6f6-4cca-a224-30e01cffddbe -using Catalyst - -# ╔═╡ bd648109-f042-42de-9e0e-017b502fab95 -using OrdinaryDiffEq, StatsPlots +using OrdinaryDiffEq, Catalyst # ╔═╡ 7856d878-8586-4cfd-9cf6-d61234450e41 md""" @@ -44,11 +38,16 @@ 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 +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*}$$ -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$. +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 @@ -71,7 +70,7 @@ Convert the system to a symbolic differential equation model and verify, by anal """ # ╔═╡ ec9cb3bd-f5ed-4ab0-9b3d-b875692227ac -# osys = missing # Uncomment and complete the instruction +osys = missing # ╔═╡ 67117a27-dcea-4b43-b962-9ad9fd07f4f4 md""" @@ -81,11 +80,11 @@ The parameter values are $\beta = 0.98$, $Y = 0.80$, $Q = 2.0$, $V = 40.0$ and $ # ╔═╡ d13e6e38-037e-4812-85e9-2c18bed360f6 md""" -Initialize a vector `u₀` with the initial conditions: +Initialize a vector `u0` with the initial conditions: """ # ╔═╡ 4b556cf0-8fad-434d-be56-dc1848d898ae -# u0 = missing # Uncomment and complete the instruction +u0 = missing # ╔═╡ ea55d648-7575-43c3-a385-5f4979996ef2 md""" @@ -93,15 +92,15 @@ Set the timespan for the simulation: """ # ╔═╡ 1365c12e-e662-4858-983b-02ba94cd9f0d -# tspan = missing # Uncomment and complete the instruction +tspan = missing # ╔═╡ 3941bd60-a83c-4f72-84b3-28e28cb845d0 md""" -Initialize a vector `params` with the parameter values: +Initialize a vector `parms` with the parameter values: """ # ╔═╡ d6c1316a-cf96-43d1-854a-f25925cf4a55 -# params = missing # Uncomment and complete the instruction +parms = missing # ╔═╡ eeb8ec6e-154e-4fe9-8b5b-edbe71914985 md""" @@ -109,11 +108,11 @@ Create the *condition* that contains the timepoint for the sudden change in $S_{ """ # ╔═╡ 7ca8efaa-97b6-46f2-b4d3-6ca8aa97dda7 -# condition = missing +condition = missing # ╔═╡ d864bfc3-05b2-483b-9a55-da026931703f md""" -Make a new *reaction system* where the discrete event is included. Name it `fermenter_firstorder2`. +Make a new *reaction system* where the discrete event is included. Name it `fermenter_firstorder_c`. """ # ╔═╡ d758b918-d184-473f-9b9e-ed6da7b0f088 @@ -125,7 +124,7 @@ Complete the new *reaction system*. Name it `fermenter_firstorder_c_com`. """ # ╔═╡ c07b3121-4c27-454c-b9e4-7dfa27371ebd -# fermenter_firstorder_c_com = missing +fermenter_firstorder_c_com = missing # ╔═╡ c6e81f41-a244-48c9-9d18-b3b9e0984fbb md""" @@ -133,7 +132,7 @@ Create the ODE problem and store it in `oprob`: """ # ╔═╡ ed56f8d6-2260-4829-9190-69b60b7d7599 -# oprob = missing +oprob = missing; # ╔═╡ 8b73b16b-7f7d-4d2e-a1c2-7e1adf2336e9 md""" @@ -141,7 +140,7 @@ Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store """ # ╔═╡ 433db8d1-f038-4e7b-9133-90bfeccabd07 -# osol = missing +osol = missing # ╔═╡ fc824241-f718-46a8-b50b-a680470c062b md""" @@ -149,38 +148,34 @@ Plot the results: """ # ╔═╡ e2609226-5f2f-4802-89e3-0efeac740081 -# missing +missing -# ╔═╡ 5fa47281-4c1f-4b8e-ab99-c92f4dc7ec65 +# ╔═╡ 41b9d4bc-280b-4f05-945b-981c9fcddf82 md""" -Interpret the results. Ask yourself the following questions: - -1. Can you clearly see the effect of the increase in $S_{in}$? +!!! 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$? """ -# ╔═╡ ae8e5a4a-59d8-4746-accb-c9b09099bc2b -md"- Answer: missing" - -# ╔═╡ 822ac3d0-9430-4763-9e94-d77b9e77c79c +# ╔═╡ ef932b49-8c77-48b5-86ac-2a4fee404cab 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$? +Answers: +1. missing +2. missing """ -# ╔═╡ 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 +# ╠═b33193b3-220e-49fc-acd1-6d1bec3caf88 +# ╠═e66518ee-b6f6-4cca-a224-30e01cffddbe # ╟─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 @@ -200,7 +195,5 @@ md"- Answer: missing" # ╠═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 +# ╠═41b9d4bc-280b-4f05-945b-981c9fcddf82 +# ╠═ef932b49-8c77-48b5-86ac-2a4fee404cab diff --git a/src/exercises/ode_model_soil_cont_plant_uptake.jl b/src/exercises/ode_model_XTRA_soil_cont_plant_uptake.jl similarity index 78% rename from src/exercises/ode_model_soil_cont_plant_uptake.jl rename to src/exercises/ode_model_XTRA_soil_cont_plant_uptake.jl index ca77cb7b..0b688d7f 100644 --- a/src/exercises/ode_model_soil_cont_plant_uptake.jl +++ b/src/exercises/ode_model_XTRA_soil_cont_plant_uptake.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "8" -#> title = "EXTRA. ODE soil" -#> date = "2025-02-07" +#> order = "15" +#> title = "2. ODE_model_Xtra_soil_contamination" #> tags = ["exercises"] -#> description = "ODE model of soil" #> layout = "layout.jlhtml" +#> description = "Extra exercise on soil contamination with plant uptake" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,29 +15,29 @@ 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 +# 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 - -# ╔═╡ 74a1a82f-8c30-45e5-a2b7-1c1ce4d5c523 -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ 895c1016-a8ce-43ae-8094-5d6ea75a6053 -using Catalyst +using StatsPlots, PlutoUI; TableOfContents() # ╔═╡ 28e9e96c-fce6-4507-97c9-37337a0731bc -using OrdinaryDiffEq, StatsPlots +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$. @@ -75,7 +74,7 @@ Convert the system to a symbolic differential equation model and verify that you """ # ╔═╡ 1a7c3080-5773-44b9-a5c5-bb16f25048a3 -# osys = missing # Uncomment and complete the instruction +osys = missing # ╔═╡ 7c4767ac-ee72-4f46-8699-68f4bfb15d92 md""" @@ -88,7 +87,7 @@ Initialize a vector `u0` with the initial conditions: """ # ╔═╡ 9d4fc31d-32b4-49c7-9e4c-577530199513 -# u0 = missing # Uncomment and complete the instruction +u0 = missing # ╔═╡ 51ffec7c-6033-47a9-b65a-5f9a7ab96fb9 md""" @@ -96,15 +95,15 @@ Set the timespan for the simulation: """ # ╔═╡ 20b43337-58fd-4b23-8e4d-c4fd8234bb5f -# tspan = missing # Uncomment and complete the instruction +tspan = missing # ╔═╡ ad63f799-bc65-4f2c-ba6c-461fa10139d0 md""" -Initialize a vector `param` with the parameter values: +Initialize a vector `parms` with the parameter values: """ # ╔═╡ 01e33f75-fdfe-4983-a3bd-4cf074152390 -# params = missing # Uncomment and complete the instruction +parms = missing # ╔═╡ 0f326aa7-044c-4c6f-be71-acf5c032f796 md""" @@ -112,7 +111,7 @@ Create the ODE problem and store it in `oprob`: """ # ╔═╡ 6f84b532-a718-4d42-9829-91366693b51c -# oprob = missing # Uncomment and complete the instruction +oprob = missing; # ╔═╡ ca65797f-a1dd-42dd-992c-ba067932a018 md""" @@ -120,7 +119,7 @@ Solve the ODE problem. Use `Tsit5()` and `saveat=1.0`. Store the solution in `os """ # ╔═╡ 3d66d40f-f627-4268-890f-ab662c0efdd6 -# osol = missing # Uncomment and complete the instruction +osol = missing # ╔═╡ 22dc63ad-47d5-45c4-8902-e9d7abc0a4f6 md""" @@ -128,11 +127,12 @@ Plot the solutions: """ # ╔═╡ 0f920caa-5993-448c-a449-6449feea121a -# missing # Uncomment and complete the instruction +missing # ╔═╡ 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. +!!! 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 @@ -140,7 +140,8 @@ 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, ...). +!!! 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 @@ -148,24 +149,25 @@ md"- Answer: missing" # ╔═╡ ad11f590-aa18-45f6-99be-571039ccbbae md""" -3. What are the units of the parameters k₁, k₂ and k₃? +!!! 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 -# ╠═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 +# ╠═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 -# ╠═28e9e96c-fce6-4507-97c9-37337a0731bc # ╟─7c4767ac-ee72-4f46-8699-68f4bfb15d92 # ╟─340328cc-78ed-4c6c-a0bf-73fd1be21d21 # ╠═9d4fc31d-32b4-49c7-9e4c-577530199513 @@ -180,8 +182,8 @@ md"- Answer: missing" # ╟─22dc63ad-47d5-45c4-8902-e9d7abc0a4f6 # ╠═0f920caa-5993-448c-a449-6449feea121a # ╟─62c66f8a-6561-4d50-b6d9-4dfc43cef0a8 -# ╟─27c1e08f-5e6a-43e3-b8e3-bba78e093556 +# ╠═27c1e08f-5e6a-43e3-b8e3-bba78e093556 # ╟─73b63510-e1a1-44a6-8902-67ee383e6582 -# ╟─a7068013-ed98-486a-ba59-a57f26d12d1c +# ╠═a7068013-ed98-486a-ba59-a57f26d12d1c # ╟─ad11f590-aa18-45f6-99be-571039ccbbae -# ╟─1ebbac82-068a-4754-8eec-1afa662feb96 +# ╠═1ebbac82-068a-4754-8eec-1afa662feb96 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_water_evap_infil.jl b/src/exercises/ode_model_XTRA_water_evap_infil.jl similarity index 89% rename from src/exercises/ode_model_water_evap_infil.jl rename to src/exercises/ode_model_XTRA_water_evap_infil.jl index fd2bf07e..6d5dcc11 100644 --- a/src/exercises/ode_model_water_evap_infil.jl +++ b/src/exercises/ode_model_XTRA_water_evap_infil.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "9" -#> title = "EXTRA. ODE water evaporation" -#> date = "2025-02-07" +#> order = "16" +#> title = "2. ODE_model_Xtra_evaporation" #> tags = ["exercises"] -#> description = "ODE model of water evaporation" #> layout = "layout.jlhtml" +#> description = "Extra exercise on water evaporation and infiltration in a reservoir" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,34 +15,34 @@ 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 +# 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 - -# ╔═╡ 4071647d-3084-4c8b-9fb7-eca7255253a9 -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ 65571bb8-e260-4a82-b0d2-198e47c56271 -using PlutoUI - -# ╔═╡ 2574879f-28d1-4d30-a1aa-a637dd1b216a -using Catalyst +using StatsPlots, PlutoUI; TableOfContents() # ╔═╡ 6c4b3d09-09c2-4439-9167-63b59b078104 -using OrdinaryDiffEq, StatsPlots +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])); -# ╔═╡ f55e0ca3-07ff-43a1-907f-9ad640227822 +# ╔═╡ d01091d7-2593-482d-b734-01209dde2249 md""" -# Exercise: Water evaporation and infiltration +![](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)$) @@ -147,7 +146,7 @@ Create the ODE problem and store it in `oprob`: """ # ╔═╡ a25d3652-13f5-47ef-9f16-c6698547a734 -# oprob = missing # Uncomment and complete the instruction +# oprob = missing; # Uncomment and complete the instruction # ╔═╡ 451a3c66-5bcc-4161-af81-f89af33b5862 md""" @@ -191,12 +190,20 @@ md""" # ╔═╡ 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 -# ╠═4071647d-3084-4c8b-9fb7-eca7255253a9 # ╠═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 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_birth_death.jl b/src/exercises/ode_model_birth_death.jl index 8706568f..5f91edd5 100644 --- a/src/exercises/ode_model_birth_death.jl +++ b/src/exercises/ode_model_birth_death.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "4" -#> title = "1. ODE birth rate" -#> date = "2025-02-07" +#> order = "10" +#> title = "2. ODE_model_birth_death" #> tags = ["exercises"] -#> description = "ODE model of birth rate" #> layout = "layout.jlhtml" +#> description = "Simple birth-death model for a mice population" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,31 +15,31 @@ 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 +# 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 - -# ╔═╡ 0ab4d97a-0c47-4f6e-a76b-9cec00ad410a -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ 34a2f237-848c-4d07-9cea-fa5505a9e215 -using PlutoUI; TableOfContents() +using StatsPlots, PlutoUI; TableOfContents() # ╔═╡ b721cc4d-43ba-4221-bbd1-15293aaf54b5 -using Catalyst - -# ╔═╡ 47e9c791-99cc-4a78-94b0-e0f5d4e0ecc5 -using OrdinaryDiffEq, StatsPlots +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, @@ -58,7 +57,6 @@ Create a *reaction network object* model for the aforementioned problem in order """ # ╔═╡ 74c3f7ed-d705-4cb9-b52c-e06c9df5ca13 -# Uncomment and complete the instruction # birth_death = @reaction_network begin # missing # end @@ -69,7 +67,7 @@ Convert the system to a symbolic differential equation model and verify, by anal """ # ╔═╡ 124eb20e-6945-4eec-a05a-6b835efbdd2e -# osys = missing # Uncomment and complete the instruction +osys = missing # ╔═╡ 04c645ad-873d-44c1-9bc0-940a412630b9 md""" @@ -84,7 +82,7 @@ First, calculate the birth rate in $mice/day$. """ # ╔═╡ 6b64ec8b-d85e-4a4f-aa3f-f177f5e34180 -# missing # Uncomment and complete the instruction +missing # ╔═╡ 52ee74d2-4dca-47ef-8b97-f826ef31ddf9 md""" @@ -92,7 +90,7 @@ Initialize a vector `u0` with the initial conditions: """ # ╔═╡ 23f282d8-2eeb-43dd-8ba9-84684daca7a4 -# u0 = missing # Uncomment and complete the instruction +u0 = missing # ╔═╡ 30fa6fc6-54e0-4233-9334-b3d15f630489 md""" @@ -100,15 +98,15 @@ Set the timespan for the simulation: """ # ╔═╡ b461a225-bae6-45b8-bb42-54742a72b98f -# tspan = missing # Uncomment and complete the instruction +tspan = missing # ╔═╡ c753d65f-9ed3-4e60-8e36-6e4bca77c19b md""" -Initialize a vector `param` with the parameter values: +Initialize a vector `parms` with the parameter values: """ # ╔═╡ e7942ba9-5434-4b22-b1d4-f18f6227320d -# params = missing # Uncomment and complete the instruction +parms = missing # ╔═╡ 2af7f6b9-9d95-4bf6-af67-449803440639 md""" @@ -116,7 +114,7 @@ Create the ODE problem and store it in `oprob`: """ # ╔═╡ 9432f90b-7141-4518-b6fd-d55a1389e14a -# oprob = missing # Uncomment and complete the instruction +oprob = missing; # ╔═╡ ab197ac7-413b-4305-8352-2723bf9f2aff md""" @@ -124,7 +122,7 @@ Solve the ODE problem. Use `Tsit5()` and `saveat=1.0`. Store the solution in `os """ # ╔═╡ 8e517114-7c74-4d7c-954c-2787d975a1df -# osol = missing # Uncomment and complete the instruction +osol = missing # ╔═╡ 9eaf1ca5-3d65-4263-a307-991bc8bf62d2 md""" @@ -132,13 +130,13 @@ Plot the results: """ # ╔═╡ 83e36e63-7e6b-4c17-8f7d-ed0e07f36fc5 -# missing # Uncomment and complete the instruction +missing # ╔═╡ 5fc64787-795c-4e17-b751-cd3683f8016b md""" -Interpret the results. Ask yourself the following questions: - -1. What is the (approximate) steady state value for $X$? +!!! question + Interpret the results. Ask yourself the following questions: + - What is the (approximate) steady state value for $X$? """ # ╔═╡ f5718c08-c8aa-4d74-b70f-8c08c6decab2 @@ -157,7 +155,7 @@ Create the *condition*. Store it in `condition2`: """ # ╔═╡ 1c5d8daa-5c7e-49ef-a6da-6459a1131320 -# condition2 = missing # Uncomment and complete the instruction +condition2 = missing # ╔═╡ 4d9ba697-5db3-4879-be88-278765e766f1 md""" @@ -165,7 +163,7 @@ Make a new *reaction system* where the discrete event is included. Name it `birt """ # ╔═╡ d40c2598-5783-49c5-916d-296e946de3f6 -# @named birth_death2 = missing # Uncomment and complete the instruction +# @named birth_death2 = missing # ╔═╡ afbc4f00-0b4a-427a-9630-3a279ccbc330 md""" @@ -173,7 +171,7 @@ Complete the new *reaction system*. Name it `birth_death2_com`. """ # ╔═╡ 3dd3febc-fc3f-477f-a5be-335ae90223e3 -# birth_death2_com = missing # Uncomment and complete the instruction +birth_death2_com = missing # ╔═╡ cf05864f-37f0-4fbb-93a5-ef406a2ae014 md""" @@ -181,7 +179,7 @@ Create the ODE problem and store it in `oprob2`: """ # ╔═╡ 65e24cee-06c3-4cb7-8f8c-61e02b7dcd29 -# oprob2 = missing # Uncomment and complete the instruction +oprob2 = missing; # ╔═╡ 9cab94b5-6d2d-4b5a-96f3-3b8eee1c61a1 md""" @@ -189,7 +187,7 @@ Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=1.0`. Store """ # ╔═╡ 00750aa4-6a00-4d33-a0ab-2d7abe952078 -# osol2 = missing # Uncomment and complete the instruction +osol2 = missing # ╔═╡ 694e5b50-f4c9-4ecc-8860-1b217696e1ea md""" @@ -197,38 +195,35 @@ Plot the results: """ # ╔═╡ c69d0e86-0d15-4915-a890-8abd378da59a -# missing # Uncomment and complete the instruction +missing # ╔═╡ 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? +!!! 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"- 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. +Answers: +1. missing +2. missing """ -# ╔═╡ ce733400-9bf4-42b3-8f77-351ef9b1cd92 -md"- Answer: missing" - # ╔═╡ Cell order: +# ╟─c701d64e-640c-473f-b0fa-688024962f28 # ╠═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 +# ╟─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 -# ╠═47e9c791-99cc-4a78-94b0-e0f5d4e0ecc5 # ╟─04c645ad-873d-44c1-9bc0-940a412630b9 # ╟─1cd5136a-f472-4329-980a-b8382d1c04ea # ╠═6b64ec8b-d85e-4a4f-aa3f-f177f5e34180 @@ -245,7 +240,7 @@ md"- Answer: missing" # ╟─9eaf1ca5-3d65-4263-a307-991bc8bf62d2 # ╠═83e36e63-7e6b-4c17-8f7d-ed0e07f36fc5 # ╟─5fc64787-795c-4e17-b751-cd3683f8016b -# ╟─f5718c08-c8aa-4d74-b70f-8c08c6decab2 +# ╠═f5718c08-c8aa-4d74-b70f-8c08c6decab2 # ╟─fe4526c8-4504-4d91-841a-a83aeef55fc7 # ╟─1e869025-c537-4bf1-9c38-93e24b598156 # ╠═1c5d8daa-5c7e-49ef-a6da-6459a1131320 @@ -260,6 +255,4 @@ md"- Answer: missing" # ╟─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 +# ╠═775f2cf3-ffbb-491a-9002-3a39b9c70f3c diff --git a/src/exercises/ode_model_catalyst_intro.jl b/src/exercises/ode_model_catalyst_intro.jl index a64a1c4f..3cffc2cb 100644 --- a/src/exercises/ode_model_catalyst_intro.jl +++ b/src/exercises/ode_model_catalyst_intro.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.6 +# v0.20.13 #> [frontmatter] -#> order = "2" -#> title = "1. ODE catalyst intro" -#> date = "2025-02-07" +#> order = "9" +#> title = "2. ODE_model_Catalyst_intro" #> tags = ["exercises"] -#> description = "ODE catalyst intro" #> layout = "layout.jlhtml" +#> description = "Introduction to Catalyst as an alternative to ModelingToolkit" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -28,17 +27,12 @@ macro bind(def, element) 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 +# 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 - -# ╔═╡ cd166045-7788-4f5c-97d9-d1bedeceedae -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ 3cfe381f-5871-4447-9149-fc023c787b42 using PlutoUI; TableOfContents() @@ -56,8 +50,10 @@ md""" # ╔═╡ 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). -""" +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""" @@ -161,6 +157,8 @@ The following code creates a so called *reaction network object*, that we have n # ╔═╡ 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 @@ -179,8 +177,8 @@ 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 `α`. +!!! 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 @@ -207,7 +205,7 @@ 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: + 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 @@ -215,7 +213,7 @@ You can also get the different species and parameters using `@unpack` followed b # ╔═╡ 64a48327-03ec-46a0-9a76-79c10acb6e20 md""" -The *reaction model* can be converted to a symbolic differential equation model via +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 @@ -223,7 +221,7 @@ osys = convert(ODESystem, infection_model) # ╔═╡ eeb25e50-165b-4e93-8397-5b09fe8e7242 md""" -Note that the model equations are essencially: +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)$$ @@ -259,18 +257,18 @@ parameters(osys) 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. +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 some the following information: +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$ 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$.\ +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 | @@ -283,64 +281,37 @@ Furthermore, we take the following values for the parameters: $\alpha = 0.08\;pe Finally, we want to run our simulation from day $0$ till day $90$. """ -# ╔═╡ 979afe85-b910-44a0-8ac0-6e719cb9157e +# ╔═╡ c4e83ef8-9490-4361-a2a9-5abc45e242be md""" -### Setting initial conditions +### Creating the ODEProblem """ -# ╔═╡ 1ba859fa-46a5-434f-a99c-e710ba85caf8 +# ╔═╡ a7a99fde-c1bd-484c-a7b6-a7d8af809ecc 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: +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] -# ╔═╡ 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] +parms = [:α => 0.08, :β => 1.0e-6, :r => 0.2, :m => 0.4] -# ╔═╡ c4e83ef8-9490-4361-a2a9-5abc45e242be +# ╔═╡ c6d2dd69-8c61-4a40-894f-664b2d2d14be +oprob = ODEProblem(infection_model, u0, tspan, parms); + +# ╔═╡ 050a3786-5ce3-44c2-b2c2-94cb640326aa md""" -### Creating an ODEProblem +!!! 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. """ -# ╔═╡ 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 @@ -348,9 +319,8 @@ md""" # ╔═╡ 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`). +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. """ -# https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/#Full-List-of-Methods # ╔═╡ 142e3e48-bf75-4498-ad0e-9f47cb921045 # osol = solve(oprob) @@ -369,7 +339,7 @@ md""" # ╔═╡ 0af3c166-46b6-455d-af6b-a72c4d2a5ce4 md""" -Finally, we can plot the solution through the plot function. +Finally, we can plot the solution through the `plot` function. """ # ╔═╡ 513c037b-c54c-47fa-b97a-06f69a983386 @@ -385,7 +355,7 @@ 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"`. +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 @@ -432,7 +402,7 @@ md" 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: +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**. @@ -448,7 +418,12 @@ You may have noticed that while using the Pluto notebooks, when you change the v 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$). +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 @@ -456,20 +431,15 @@ 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 +# ╔═╡ b65e948d-b13a-4021-a545-b7912fd86e94 @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] +parms1 = [:α => 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); +# 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); @@ -482,41 +452,36 @@ md""" Now, change the value of $r$ in the `param1` vector and analyze the effect in the plot. """ -# ╔═╡ f6cbafbf-98b4-4286-86d2-fe95821a5ff4 +# ╔═╡ a627e3c0-e29f-4592-9dc6-5cf48f80252d md""" -Try to interpret the results yourself. -Ask yourself the following questions: - -1. What are the trends in the results obtained? +!!! 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. """ -# ╔═╡ 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. +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 reduce by $50\%$ at time $t=14\,days$. We put this in a condition named `condition2`. +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 included in our model. +The discrete time event needs to be now included in our model. """ # ╔═╡ 8752373f-a602-437b-9b3a-2602c8babf87 @@ -524,7 +489,7 @@ The discrete time event needs to be included in our model. # ╔═╡ ee596a6c-29bf-4d18-b687-be943c128aa7 md""" -After that, we need to *complete* our *reaction network model*. +After that, we need to *complete* our *reaction network model*, so that the model can be simulated. """ # ╔═╡ 40f849cb-20f9-4bbe-806e-512abd6f3210 @@ -536,7 +501,7 @@ Then we need to create a new ODE problem. """ # ╔═╡ 4a6f357c-afd7-4b0d-b1d2-15f5c0e4298b -oprob2 = ODEProblem(infection_model2_com, u0, tspan, params) +oprob2 = ODEProblem(infection_model2_com, u0, tspan, parms); # ╔═╡ e76e77be-9f15-4aa5-8506-cdb32a6ec9b1 md""" @@ -552,7 +517,11 @@ Now we can plot the results. """ # ╔═╡ 0af84963-ca64-4958-a544-42d445da5a7c -plot(osol2) +# 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""" @@ -564,21 +533,18 @@ 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? +!!! question + How do we interpret this event? Is the number of deceased and infections reduced? How much? """ -# ╔═╡ a736a17b-ee8e-491b-840a-6319619a8dab -md"- Answer: missing" +# ╔═╡ 6eb368f5-8d43-4fb8-b6eb-c783675d1dd9 +osol2[:D][end]/osol[:D][end] - 1 # The number of deceased is reduced 13% -# ╔═╡ 2ba54805-e901-4281-9b07-cc7137b8c809 -md""" -2. How much less casualties are there compared to not altering the contact rate? -""" +# ╔═╡ 247c3c88-7ba7-4db1-ad56-de1758851ade +osol2[:S][end]/osol[:S][end] - 1 # 6 times less infections with contact measures -# ╔═╡ 20d56d2f-2532-4286-88cc-b42ccb22d246 -md"- Answer: missing" +# ╔═╡ 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""" @@ -589,12 +555,12 @@ Suppose that when the number of infected individuals reaches $1\,000\,000$, then # ╔═╡ 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`. +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 pwc(t)=true + @species thr(t)=1e6 α * β, S + I --> 2I r * m, I --> D r * (1 - m), I --> R @@ -605,11 +571,11 @@ 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$! +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 ~ 1e6*infection_model3.pwc] => [infection_model3.I ~ infection_model3.I - 0.999e6, infection_model3.pwc ~ false] +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""" @@ -633,7 +599,7 @@ Then we need to create a new ODE problem. """ # ╔═╡ e0562699-d065-4ae1-9c39-f9e0bf561fa3 -oprob3 = ODEProblem(infection_model3_c_com, u0, tspan, params) +oprob3 = ODEProblem(infection_model3_c_com, u0, tspan, parms); # ╔═╡ 6b3ea888-1238-46ae-9eaf-52e86c76bc1c md""" @@ -641,7 +607,7 @@ Finally, the ODE problem can be solved. Notice that you need to make a deepcopy """ # ╔═╡ abe11178-7177-418c-98b4-da1afc56842e -osol3 = solve(deepcopy(oprob3), Tsit5(), saveat=0.1) +osol3 = solve(deepcopy(oprob3), Tsit5(), saveat=0.5) # ╔═╡ 23804c2a-d031-4025-92d3-5cdb74f87353 md""" @@ -649,7 +615,10 @@ Now we can plot the results. """ # ╔═╡ 02173eaa-1178-4383-b57a-03e53ce38af8 -plot(osol3) +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""" @@ -661,25 +630,35 @@ 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? +!!! 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? """ -# ╔═╡ 7872701b-8506-4303-b615-32d3c1afa50d -md"- Answer: missing" +# ╔═╡ 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) -# ╔═╡ 0d6c2233-35c5-4257-9dfd-398704080ba3 +# ╔═╡ 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""" -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.) +- Answer: """ -# ╔═╡ 029a2024-1ec7-4af9-9e73-717f13483468 -md"- Answer: missing" +# ╔═╡ 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 -# ╠═cd166045-7788-4f5c-97d9-d1bedeceedae # ╠═e6e748a5-b692-4c7c-b39e-ef0fe9e46c35 # ╠═3cfe381f-5871-4447-9149-fc023c787b42 # ╟─9ea49d7a-e524-11ee-1b68-9d1d71aaba24 @@ -721,19 +700,13 @@ md"- Answer: missing" # ╟─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 +# ╟─c4e83ef8-9490-4361-a2a9-5abc45e242be +# ╟─a7a99fde-c1bd-484c-a7b6-a7d8af809ecc # ╠═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 +# ╟─050a3786-5ce3-44c2-b2c2-94cb640326aa # ╟─3c253bf3-886d-4e86-82ac-7751d23f342f # ╟─14756171-9e8e-4cb0-b7af-74c2d649fe9f # ╠═142e3e48-bf75-4498-ad0e-9f47cb921045 @@ -757,21 +730,19 @@ md"- Answer: missing" # ╟─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 +# ╟─b6baafc2-6d5e-43c3-8ef9-845961cdd20b +# ╠═b65e948d-b13a-4021-a545-b7912fd86e94 # ╠═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 +# ╟─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 @@ -785,9 +756,9 @@ md"- Answer: missing" # ╟─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 +# ╠═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 @@ -807,6 +778,9 @@ md"- Answer: missing" # ╟─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 +# ╠═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_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_monod.jl b/src/exercises/ode_model_fermenter_monod.jl index 9eb647a3..7822089f 100644 --- a/src/exercises/ode_model_fermenter_monod.jl +++ b/src/exercises/ode_model_fermenter_monod.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "7" -#> title = "EXTRA. ODE fermentor monod" -#> date = "2025-02-07" +#> order = "11" +#> title = "2. ODE_model_fermenter_monod" #> tags = ["exercises"] -#> description = "ODE model of a fermentor monod" #> layout = "layout.jlhtml" +#> description = "Fermenter with biomass growing on substrate through Monod kinetics" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,57 +15,60 @@ 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 +# 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 - -# ╔═╡ 539c1823-16d0-4355-97b6-fe9f0b106864 -using InteractiveUtils +using Markdown, InteractiveUtils -# ╔═╡ e66518ee-b6f6-4cca-a224-30e01cffddbe -using Catalyst +# ╔═╡ e6bf6f7e-f56d-4ee1-89ec-9224ef4444a6 +using StatsPlots, PlutoUI; TableOfContents() -# ╔═╡ bd648109-f042-42de-9e0e-017b502fab95 -using OrdinaryDiffEq, StatsPlots +# ╔═╡ 7906cea8-e163-42cf-adbc-7eec7adc5597 +using OrdinaryDiffEq, Catalyst # ╔═╡ 7856d878-8586-4cfd-9cf6-d61234450e41 md" -### Exercise: Fermenter - Monod kinetics +# 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: +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 +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 -where +$$\mu = k\,S = \mu_{max} \, \cfrac{S}{S + K_s}$$ -$$\mu = \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. -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$. +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 +# @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 @@ -77,7 +79,7 @@ Keep in mind that `mm(S, μmax, Ks)` stands for $\mu_{max} \, \cfrac{S}{S + K_s} """ # ╔═╡ ec9cb3bd-f5ed-4ab0-9b3d-b875692227ac -# osys = missing +osys = missing # ╔═╡ 67117a27-dcea-4b43-b962-9ad9fd07f4f4 md""" @@ -90,7 +92,7 @@ Initialize a vector `u0` with the initial conditions: """ # ╔═╡ 4b556cf0-8fad-434d-be56-dc1848d898ae -# u0 = missing # Uncomment and complete the instruction +u0 = missing # ╔═╡ ea55d648-7575-43c3-a385-5f4979996ef2 md""" @@ -98,15 +100,15 @@ Set the timespan for the simulation: """ # ╔═╡ 1365c12e-e662-4858-983b-02ba94cd9f0d -# tspan = missing # Uncomment and complete the instruction +tspan = missing # ╔═╡ 3941bd60-a83c-4f72-84b3-28e28cb845d0 md""" -Initialize a vector `param` with the parameter values: +Initialize a vector `parms` with the parameter values: """ # ╔═╡ d6c1316a-cf96-43d1-854a-f25925cf4a55 -# params = missing # Uncomment and complete the instruction +parms = missing # ╔═╡ 4926b941-c3b6-4804-b4a4-11e13e5186f2 md""" @@ -114,17 +116,17 @@ Create the ODE problem and store it in `oprob`: """ # ╔═╡ ab2a9842-6a9c-46bd-812b-db01629d6a1c -# oprob = missing # Uncomment and complete the instruction +oprob = missing; # ╔═╡ b6a526bd-6ee5-442b-9fb8-3fbe1e280dd4 md""" -#### Part 1 +## 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 +osol1 = missing # ╔═╡ 37ced6e3-b435-4546-a720-a1ec1af23a65 md""" @@ -132,7 +134,7 @@ Plot the results: """ # ╔═╡ d609bed4-94cf-4167-80fe-924501a5835c -# missing # Uncomment and complete the instruction +missing # ╔═╡ 3c638fc4-8ebc-4e26-984e-b4513035287e md""" @@ -141,28 +143,34 @@ Tip: use something like: `(osol1[...][...], osol1[...][...])` """ # ╔═╡ 25ef069e-5a08-4e85-acea-a5b64e0890f6 -# (osol1[...][...], osol1[...][...]) # Uncomment and complete the instruction +# (osol1[...][...], osol1[...][...]) # ╔═╡ 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). +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""" -First, we initialize a vector `u_guess1` with the final values for $S$ and $X$: +Initialize a vector `u_guess1` with the final values for $S$ and $X$: """ # ╔═╡ 8d96d79f-6ddb-4bba-a6ea-821588e13107 -# u_guess1 = missing +u_guess1 = [:S => missing, :X => 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`. +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 -# Seq1, Xeq1 = missing +equil_val_monod = missing + +# ╔═╡ b9b9c5f0-02fa-4544-8865-a07271e8941f +Seq1 = missing + +# ╔═╡ 0ddf9ea1-3f45-48b1-bb9c-5a08df45bd5b +Xeq1 = missing # ╔═╡ 1d5a2118-96e9-49b2-9931-5d4b201cb8f5 md""" @@ -170,29 +178,25 @@ Next, we can just inspect these values: """ # ╔═╡ cb57997e-c3ec-47e0-b9a0-b10aa9f5608d -# missing +missing -# ╔═╡ bb2d06f8-1940-4585-961a-54068da50e91 +# ╔═╡ 7ae28a79-921f-4bda-bdf8-c3122c787f4f 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. +!!! 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$. """ -# ╔═╡ 7c29c97d-dea5-4ea3-b5aa-bdccfe93939c -md"- Answer: missing" - -# ╔═╡ 34018fc4-af3e-4f5a-9f24-a73293af2e85 +# ╔═╡ 92e868a3-0537-496f-9fbd-8272dca9a6a2 md""" -2. What are the steady state values of $S$ and $X$. +Answers: +1. missing +2. missing """ -# ╔═╡ eebef095-ead8-4193-889b-53cbcab84514 -md"- Answer: missing" - # ╔═╡ 9bb450c6-5499-42f6-8356-bdc4985b74e7 md""" -#### Part 2 +## 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$. """ @@ -203,7 +207,7 @@ Create the *condition* that contains the timepoint for the sudden change in $S_{ """ # ╔═╡ c85e505d-99c6-4616-b7c1-42c05b4894fc -# condition2 = missing # Uncomment and complete the instruction +condition2 = missing # ╔═╡ 7c96bead-9f7b-4e84-abb9-9b6651208667 md""" @@ -211,7 +215,7 @@ Make a new *reaction system* where the discrete event is included. Name it `ferm """ # ╔═╡ 439dbeef-55b9-4fa4-aef5-fa0bb5a2ccf1 -# @named fermenter_monod2 = missing # Uncomment and complete the instruction +# @named fermenter_monod2 = missing # ╔═╡ 43614c69-3fb5-4bef-b2a2-9805a5545fb8 md""" @@ -219,7 +223,7 @@ Complete the new *reaction system*. Name it `fermenter_monod2_com`. """ # ╔═╡ 532306b5-2a71-4fcf-93ac-9dc52457a3f9 -# fermenter_monod2_com = missing # Uncomment and complete the instruction +fermenter_monod2_com = missing # ╔═╡ 9e8f4500-0b6a-47f0-a1f5-a74daea9d117 md""" @@ -227,7 +231,7 @@ Create the ODE problem and store it in `oprob2`: """ # ╔═╡ 7af72709-2f82-4971-8342-f02943f947c8 -# oprob2 = missing # Uncomment and complete the instruction +oprob2 = missing; # ╔═╡ e019f797-a6ad-4f8f-8f9e-69db00ed3c39 md""" @@ -235,7 +239,7 @@ Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store """ # ╔═╡ 5f77450b-aa96-41b0-8017-a3d29fd7023a -# osol2 = missing # Uncomment and complete the instruction +osol2 = missing # ╔═╡ 310a78a5-94ce-4a29-b7a1-37831ce5c64e md""" @@ -243,7 +247,7 @@ Plot the results: """ # ╔═╡ 85742dc1-24cb-42d1-a70b-70f1be6b6c1e -# missing +missing # ╔═╡ 7f358845-c9fc-4e77-9882-94506f9338d6 md""" @@ -257,7 +261,7 @@ Tip: use something like: `(osol2[...][...], osol2[...][...])` """ # ╔═╡ 196edf3a-b220-4a54-8137-b136b509617e -# (osol2[...][...], osol2[...][...]) # Uncomment and complete the instruction +# (osol2[...][...], osol2[...][...]) # ╔═╡ 3c7bf6f2-4ffd-4678-a930-94cf1322ba9f md""" @@ -265,15 +269,15 @@ 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 +u_guess2 = missing # ╔═╡ 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}$. +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 -# params_mod = missing # Uncomment and complete the instruction +parms_mod = missing # ╔═╡ 9fe054e0-cf21-49ba-a777-a8200b34b7dd md""" @@ -281,7 +285,13 @@ 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 +eq2 = missing + +# ╔═╡ 7b985909-bc75-4c03-8949-b689e90c9fea +Seq2 = missing + +# ╔═╡ c4417b5c-a7cf-497b-8b7a-2a90ebe428f2 +Xeq2 = missing # ╔═╡ 4ed59602-ad9d-4aae-8a21-83dabbfd3846 md""" @@ -289,37 +299,27 @@ Inspect those values. """ # ╔═╡ 47a63fd8-f805-4c7d-8695-c9ee6550f24f -# missing # Uncomment and complete the instruction +missing -# ╔═╡ 4d962d0f-da41-405e-9438-733d5668cde3 +# ╔═╡ 231e294c-affe-4024-be03-a795cc28d42a md""" -Interpret the results. Ask yourself the following questions: - -1. Can you clearly see the effect of the increase in $S_{in}$? +!!! 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? """ -# ╔═╡ 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 +# ╔═╡ c20abc19-e6ce-4046-a85a-a96657da41c7 md""" -3. Can you explain why $X$ increased permanently? +Answers: +1. missing +2. missing +3. missing """ -# ╔═╡ 8a0e6b54-bf50-4fc8-a7b9-fbfd2deb8d06 -md"- Answer: missing" - # ╔═╡ 53980767-a84f-44f2-a878-2a7d57e0e2ae md""" -#### Part 3 +## Part 3 Suppose that the inlet/outlet flow $Q$ is suddenly doubled at $t = 100\;h$. Simulate the evolution of $S$ and $X$. """ @@ -330,7 +330,7 @@ Create the *condition* that contains the timepoint for the sudden change in $Q$. """ # ╔═╡ 6e771231-abf1-41c0-9aa8-ac7220f2a9cd -# condition3 = missing # Uncomment and complete the instruction +condition3 = missing # ╔═╡ 97b0de4f-c300-44f1-97fc-804d3263d8b5 md""" @@ -338,7 +338,7 @@ Make a new *reaction system* where the discrete event is included. Name it `ferm """ # ╔═╡ 837e27f1-a2d8-4d2c-aaa5-e82b6761e4fd -# @named fermenter_monod3 = missing # Uncomment and complete the instruction +# @named fermenter_monod3 = missing # ╔═╡ d8002843-03c6-4fa4-b8e5-b42eac27588c md""" @@ -346,7 +346,7 @@ Complete the new *reaction system*. Name it `fermenter_monod3_com`. """ # ╔═╡ 508f1dfe-3a92-4d80-b48d-e84a8738f97f -# fermenter_monod3_com =missing # Uncomment and complete the instruction +fermenter_monod3_com = missing # ╔═╡ 5d4c2573-4e57-455b-bdcc-1cee79b08ce2 md""" @@ -354,7 +354,7 @@ Create the ODE problem and store it in `oprob3`: """ # ╔═╡ dd388e88-53af-48d3-800e-09b5c182a83b -# oprob3 = missing # Uncomment and complete the instruction +oprob3 = missing; # ╔═╡ 5133d846-e6a6-4b50-9ce1-cb91cf04cbd1 md""" @@ -362,7 +362,7 @@ Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store """ # ╔═╡ b3bc3348-a524-41e6-9fdb-865055246cd9 -# osol3 = missing # Uncomment and complete the instruction +osol3 = missing # ╔═╡ 39ef225d-3222-4998-bfd4-5ff88f74a0f9 md""" @@ -370,38 +370,35 @@ Plot the results: """ # ╔═╡ 76849cf5-170b-452b-acdd-c4017feaad18 -# missing # Uncomment and complete the instruction +missing -# ╔═╡ 040f040f-aa28-442e-9f44-2a897e22ed4f +# ╔═╡ bcdede6d-f259-4f3f-85d4-99d8faf85030 md""" -Interpret the results. Ask yourself the following questions: - -1. Can you clearly see the effect of doubling of $Q$? +!!! 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? """ -# ╔═╡ 1b792c99-0165-49a3-8466-91082aa514bc -md"- Answer: missing" - -# ╔═╡ f9e232c0-a74b-48b9-854d-f507717f8cdd +# ╔═╡ 0317615c-2663-475b-a2a2-b2886ea07fa4 md""" -2. Can you argue, by means of reasoning, why $S$ increases and $X$ decreases? +Answers: +1. missing +2. missing """ -# ╔═╡ 7f773577-99f7-4aee-b53c-08cd9a25a236 -md"- Answer: missing" - # ╔═╡ Cell order: +# ╟─7856d878-8586-4cfd-9cf6-d61234450e41 # ╠═2e58f4ae-f711-11ee-2598-7f3a6f2e2013 -# ╠═539c1823-16d0-4355-97b6-fe9f0b106864 # ╠═e99680dc-73af-40aa-bf57-a06d3a7372be -# ╟─7856d878-8586-4cfd-9cf6-d61234450e41 +# ╠═e6bf6f7e-f56d-4ee1-89ec-9224ef4444a6 +# ╠═7906cea8-e163-42cf-adbc-7eec7adc5597 +# ╟─7bbab2e5-0ea9-4682-ad7c-a98a5218c7ca # ╟─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 @@ -422,12 +419,12 @@ md"- Answer: missing" # ╠═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 -# ╟─bb2d06f8-1940-4585-961a-54068da50e91 -# ╟─7c29c97d-dea5-4ea3-b5aa-bdccfe93939c -# ╟─34018fc4-af3e-4f5a-9f24-a73293af2e85 -# ╟─eebef095-ead8-4193-889b-53cbcab84514 +# ╟─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 @@ -450,14 +447,12 @@ md"- Answer: missing" # ╠═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 -# ╟─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 +# ╟─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 @@ -471,7 +466,5 @@ md"- Answer: missing" # ╠═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 +# ╟─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 index 80686b6a..7c42b7bc 100644 --- a/src/exercises/ode_model_infection.jl +++ b/src/exercises/ode_model_infection.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "3" -#> title = "1. ODE infection" -#> date = "2025-02-07" +#> order = "12" +#> title = "2. ODE_model_infection" #> tags = ["exercises"] -#> description = "ODE model of infection" #> layout = "layout.jlhtml" +#> description = "Infection model built as a reaction network" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -18,7 +17,7 @@ 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 + 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) @@ -28,35 +27,32 @@ macro bind(def, element) 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 +# 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 - -# ╔═╡ 65b3536b-c9b3-485e-87e1-4f6657381503 -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ e968f6d5-962c-4de8-a9a3-ec116805d5e1 -using PlutoUI; TableOfContents() +using StatsPlots, PlutoUI; TableOfContents() # ╔═╡ e97637b0-446c-44ac-bd08-c56632f9b57f -using Catalyst - -# ╔═╡ c992a16d-80d3-48d6-bf43-2dbfa8e2f081 -using OrdinaryDiffEq, StatsPlots +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 necessay). We shortly summarize some important aspects of the model and give a condensed version of the solution method and the examples. +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 @@ -88,25 +84,20 @@ The **parameters**: 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$. +- **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 *infection reactions* are: +The involved 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 @@ -117,6 +108,11 @@ 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 @@ -158,7 +154,7 @@ osys = convert(ODESystem, infection_model) # ╔═╡ a432a88a-8db3-4729-b9a3-8ca9322fc81e md""" -Getting a list of the differential equations, the state variables and the parameters: +We can get a list of the differential equations, the state variables and the parameters: """ # ╔═╡ 7e268605-8314-4e7b-8c38-a65212e148a9 @@ -173,8 +169,6 @@ 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 @@ -189,15 +183,15 @@ u0 = [:S => 9_999_000.0, :I => 1_000.0, :D => 0.0, :R => 0.0] tspan = (0.0, 90.0) # ╔═╡ a8756838-93d7-4909-84c2-6d89be4fa711 -params = [:α => 0.08, :β => 1.0e-6, :r => 0.2, :m => 0.4] +parms = [:α => 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 +### Creating and solving the ODE problem and plotting results """ # ╔═╡ d3f39b29-71f5-4674-a52f-3ba63279fca7 -oprob = ODEProblem(infection_model, u0, tspan, params) +oprob = ODEProblem(infection_model, u0, tspan, parms) # ╔═╡ 6173506b-8f56-4100-a0a0-117c1ee8aa0f osol = solve(oprob, Tsit5(), saveat=0.5) @@ -207,28 +201,29 @@ 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") # fase plot I vs S +plot(osol, idxs=(:S, :I), xlab="S", ylab="I") # phase plot I vs S # ╔═╡ f9bd32f5-c473-44d4-af9d-4d99032a2d21 -osol.u[end] +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 of between $10$, days and $1$ day contagious ($r$ between $0.1$ and $1.0$, step $0.1$, default value $0.1$). +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 -params1 = [:α => 0.08, :β => 1.0e-6, :r => r, :m => 0.4] +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, params1); +oprob1 = ODEProblem(infection_model, u0, tspan, parms1); # ╔═╡ 298ada89-4581-41c5-a89f-f01ae431c5ec osol1 = solve(oprob1, Tsit5(), saveat=0.5); @@ -258,17 +253,36 @@ condition2 = [14.0] => [infection_model.β ~ infection_model.β/2] infection_model2_com = complete(infection_model2) # ╔═╡ fdea5a92-e115-4dbe-a885-95772857c686 -oprob2 = ODEProblem(infection_model2_com, u0, tspan, params) +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 -plot(osol2) +# 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 @@ -278,14 +292,14 @@ Suppose that when the number of infected individuals reaches $1\,000\,000$, then # ╔═╡ cbf0a768-f6ac-43fb-8d05-e7dac51d4cc0 infection_model3 = @reaction_network begin - @species pwc(t)=true + @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 ~ 1e6*infection_model3.pwc] => [infection_model3.I ~ infection_model3.I - 0.999e6, infection_model3.pwc ~ false] +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) @@ -294,17 +308,49 @@ condition3 = [infection_model3.I ~ 1e6*infection_model3.pwc] => [infection_model infection_model3_c_com = complete(infection_model3_c) # ╔═╡ a41878a2-2fd3-4a2a-ba85-d2a09c67da33 -oprob3 = ODEProblem(infection_model3_c_com, u0, tspan, params) +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 -plot(osol3) +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 @@ -314,7 +360,7 @@ md""" 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\%$. +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. """ @@ -325,15 +371,15 @@ Make a slider for $\alpha$ in the range of $0.08$ and $0.20$ with a step of $0.0 """ # ╔═╡ 85e81822-2ea6-4d05-b879-1791bff255b9 -# missing # Uncomment and complete the instruction +missing # ╔═╡ a2a109e3-ea74-4e74-91d0-6ea12ddf28d4 md" -Initialize vector `params_ex1` with parameter values: +Initialize vector `parms_ex1` with parameter values: " # ╔═╡ 27a4c4f5-0b2b-4d23-9bb0-a1a8ff6cfc4d -# params_ex1 = missing # Uncomment and complete the instruction +parms_ex1 = missing # ╔═╡ e0a8a397-4500-47de-8be3-49d3174648b1 md""" @@ -341,7 +387,7 @@ Create the ODE problem and store it in `oprob_ex1`: """ # ╔═╡ 7955722d-fc35-4cce-8bf2-ee40ee7fbc82 -# oprob_ex1 = missing; # Uncomment and complete the instruction +oprob_ex1 = missing; # ╔═╡ 0590bc0b-45d6-4a8e-8078-af057984523f md""" @@ -349,7 +395,7 @@ Solve the ODE problem and store the solution in `osol_ex1`: """ # ╔═╡ f7b09e96-cf30-4bd0-841b-46621df693ab -# osol_ex1 = missing; # Uncomment and complete the instruction +osol_ex1 = missing; # ╔═╡ e7947165-b244-4ff6-bdf3-61d03aefe696 md""" @@ -357,30 +403,42 @@ Plot the solutions: """ # ╔═╡ adc0aa15-e0a9-4549-9609-967a9dc78b78 -# missing # Uncomment and complete the instruction +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: -1. What are the trends in the obtained results? +!!! question + 1. What are the trends in the obtained results? """ # ╔═╡ 04f89fb3-f856-4206-9871-4d92f2816332 -md"- Answer: missing" +md""" +- Answer: +""" # ╔═╡ a39eeec9-d816-4d35-90ae-e602a24bb056 md""" -2. How can this be explained from the model structure? +!!! question + 2. How can this be explained from the model structure? """ # ╔═╡ fcad8ede-8295-46d6-a637-728b3986a761 -md"- Answer: missing" +md""" +- Answer: +""" # ╔═╡ b3f08d61-187d-4f99-b95f-508ff1b3d52d md""" @@ -414,7 +472,8 @@ Set-up the new *reaction network/model* and name it `infection_med`: # infection_med = @reaction_network begin # Uncomment and complete the instruction # α * β, S + I --> 2I # ..., I --> D -# (..., ...), I --> R +# ..., I --> R +# ..., I --> R # end # ╔═╡ 87b3796d-16c5-4fe9-a32e-d8bf7c754254 @@ -423,7 +482,7 @@ Convert to an ODE system. Check the differential equations and make sure you und """ # ╔═╡ 2a66417b-79a2-4909-bd89-5650c49b9411 -# osys_ex2 = missing # Uncomment and complete the instruction +osys_ex2 = missing # ╔═╡ 35956a99-5729-4bf7-bf09-b2232fc958da md""" @@ -431,7 +490,7 @@ Set-up parameter values: """ # ╔═╡ cc31567f-631b-41b9-aa2d-3408ca951bcf -# params_ex2 = missing # Uncomment and complete the instruction +parms_ex2 = missing # ╔═╡ 82f8c50c-511d-4399-ac87-0890b678c6c1 md""" @@ -439,7 +498,7 @@ Create the ODE problem and store it in `oprob_ex2`: """ # ╔═╡ ec08adb2-561c-4f17-a0b5-019d7b1f1098 -# oprob_ex2 = missing; # Uncomment and complete the instruction +oprob_ex2 = missing; # ╔═╡ c55ec157-6ec8-45db-9a54-0f39aceb9607 md""" @@ -447,7 +506,7 @@ Solve the ODE problem and store the solution in `osol_ex2`: """ # ╔═╡ 67668918-624e-4adf-be5d-6fdbc62555f6 -# osol_ex2 = missing; # Uncomment and complete the instruction +osol_ex2 = missing; # ╔═╡ 832f984a-176d-4187-8476-5026d29a63d8 md""" @@ -455,7 +514,7 @@ Plot the solutions: """ # ╔═╡ 5f9a9838-099d-457f-9402-915f42d0cd33 -# missing # Uncomment and complete the instruction +missing # ╔═╡ ef3ca25d-c110-452d-877a-6304ae6cd5a7 md""" @@ -466,27 +525,22 @@ Change the value of $b$ in the `params_ex2` vector to visualize the effect in th 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? +!!! 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: 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? +- Answer: """ -# ╔═╡ 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 +missing # ...% less deaths for b = 0.2 # ╔═╡ 089707c3-1df6-4799-a87b-0d4872a0267d md""" @@ -505,7 +559,7 @@ 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$. +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. """ @@ -520,7 +574,8 @@ Set-up the new *reaction network/model* and name it `infection_med_vac`: # infection_med_vac = @reaction_network begin # α * β, S + I --> 2I # ..., I --> D -# (..., ...), I --> R +# ..., I --> R +# ..., I --> R # ..., ... --> ... # end @@ -530,7 +585,7 @@ Convert to an ODE system. Check the differential equations and make sure you und """ # ╔═╡ 710fe13c-bd43-4d1e-867e-72330722ac1e -# osys_ex3 = missing # Uncomment and complete the instruction +osys_ex3 = missing # ╔═╡ 2ee20493-2dc2-4bd4-8ec3-78032f1fcc2d md""" @@ -538,7 +593,7 @@ Make a slider and bind it to the variable `v`. Use a range $[0.0, 0.1]$, step si """ # ╔═╡ 2943b515-fb4b-45a2-88dd-d0267ec95b09 -# missing # Uncomment and complete the instruction +missing # ╔═╡ 0274fbd8-c025-4e36-bbb0-f65f21b962c7 md""" @@ -546,7 +601,7 @@ Set-up parameter values: """ # ╔═╡ 34c4d746-45f7-43c4-a0f0-6e570373a35d -# params_ex3 = missing # Uncomment and complete the instruction +parms_ex3 = missing # ╔═╡ 060a5c5b-9834-42e2-98e9-ac23e7403b60 md""" @@ -554,23 +609,23 @@ Create the ODE problem and store it in `oprob_ex3`: """ # ╔═╡ c61ae8b6-2324-43fa-a5ae-274a919af559 -# oprob_ex3 = missing; # Uncomment and complete the instruction +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`. Consider an initial step size in $v$ of $0.01$ and then fine tune with a step size of $0.001$. +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; # Uncomment and complete the instruction +osol_ex3_no_vac = missing; # ╔═╡ 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): +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 # Uncomment and complete the instruction +missing # ╔═╡ 8bb5dafd-6c26-4634-8be4-c2963efba056 md""" @@ -578,7 +633,7 @@ Once you have found the required value of $v$ launch the vaccination programme $ """ # ╔═╡ 26c77206-fe3c-45c5-a627-f2b346c66be7 -# condition_ex3 = missing # Uncomment and complete the instruction +condition_ex3 = missing # ╔═╡ 527e55a8-1744-4c40-a412-054bed6d77f0 md""" @@ -586,7 +641,7 @@ Make a new *reaction system* where the discrete event is included. Name it `infe """ # ╔═╡ ceae9947-ed0f-4975-a944-cc938cf22dde -# @named infection_med_vac_c = missing # Uncomment and complete the instruction +# @named infection_med_vac_c = missing # ╔═╡ 6e767e36-f54e-4472-881a-ab04ad9c9d09 md""" @@ -594,7 +649,7 @@ 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 +infection_med_vac_c_com = missing # ╔═╡ 93ca3970-bebd-4192-bc62-6ca08d58a6c2 md""" @@ -602,7 +657,7 @@ 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 +oprob_ex3_c = missing # ╔═╡ 1bf8bc58-6848-4783-a614-7ac11646de92 md""" @@ -610,7 +665,7 @@ Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store """ # ╔═╡ 8814c42b-16c7-466a-8ad8-58ee1c0921b2 -# osol_ex3 = missing; # Uncomment and complete the instruction +osol_ex3 = missing; # ╔═╡ 25158ea5-1930-4fee-aab4-490b475d8635 md""" @@ -618,23 +673,48 @@ Plot the solutions: """ # ╔═╡ ed9472a0-09c9-4d1c-8f1d-1b7d87b97640 -# missing # Uncomment and complete the instruction +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 -# ╠═65b3536b-c9b3-485e-87e1-4f6657381503 # ╠═989fd8c8-25d9-47b9-ade6-6c7f21a7dceb # ╠═e968f6d5-962c-4de8-a9a3-ec116805d5e1 -# ╟─c54c7e3c-f3d1-40a5-88f7-bedcab3269ba +# ╠═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 -# ╟─aab497eb-c0bd-49e3-a0c1-71e13f5b0ad5 -# ╠═e97637b0-446c-44ac-bd08-c56632f9b57f # ╟─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 @@ -649,7 +729,6 @@ Plot the solutions: # ╠═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 @@ -676,6 +755,10 @@ Plot the solutions: # ╠═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 @@ -685,6 +768,13 @@ Plot the solutions: # ╠═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 @@ -698,10 +788,11 @@ Plot the solutions: # ╟─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 +# ╠═04f89fb3-f856-4206-9871-4d92f2816332 # ╟─a39eeec9-d816-4d35-90ae-e602a24bb056 -# ╟─fcad8ede-8295-46d6-a637-728b3986a761 +# ╠═fcad8ede-8295-46d6-a637-728b3986a761 # ╟─b3f08d61-187d-4f99-b95f-508ff1b3d52d # ╟─08ba4614-e4fa-4203-9e28-c5c4bd9fcdd5 # ╠═e4087441-7d0d-4716-b9fa-eef45e9f3b87 @@ -717,9 +808,7 @@ Plot the solutions: # ╠═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 +# ╠═381d3a54-0e4f-4203-874f-4a4716d481d4 # ╟─dedb1ac1-cb54-416f-a092-12a50080c69f # ╠═a912f449-6c9d-4595-863f-a2ba523bad95 # ╟─089707c3-1df6-4799-a87b-0d4872a0267d @@ -749,3 +838,8 @@ Plot the solutions: # ╠═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_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..5accbc15 --- /dev/null +++ b/src/exercises/ode_model_mtk_intro.jl @@ -0,0 +1,642 @@ +### 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 +# 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() +# 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_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/optim_wastewater_treatment.jl b/src/exercises/optim_wastewater_treatment.jl index 3a1876dc..5b9ca8b3 100644 --- a/src/exercises/optim_wastewater_treatment.jl +++ b/src/exercises/optim_wastewater_treatment.jl @@ -1,35 +1,35 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "27" -#> title = "5. Optimisation wastewater treatment" -#> date = "2025-08-06" +#> order = "34" +#> title = "6. Optimisation wastewater treatment" #> tags = ["exercises"] -#> description = "Optimisation wasterwater treatment" #> layout = "layout.jlhtml" +#> description = "Optimisation wastewater treatment" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" - using Markdown using InteractiveUtils -# ╔═╡ 2b26c3b2-df08-4b24-a08d-23717248c10d +# ╔═╡ 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 - -# ╔═╡ e6c97e37-d062-4b65-96a4-bac0dab220d8 -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ 0407d891-a46d-4deb-a21a-23833acbcb87 -using Catalyst, OrdinaryDiffEq, StatsPlots +using ModelingToolkit, OrdinaryDiffEq # ╔═╡ e48dc930-be03-47b2-b9e3-16e854782aec -using Turing, StatsBase, Optim +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() @@ -41,9 +41,9 @@ md""" # ╔═╡ 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. +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\;[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: +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$$ @@ -52,18 +52,18 @@ The initial concentrations and the parameter values are summarised in the follow | $C_0$ | $X_0$ | |:---------:|:---------:| -| $4.0$ | $0.01$ | +| $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 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. +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 - Simulation +## Part 1 In this part, we will simulate the system with the parameters given above. """ @@ -75,30 +75,43 @@ md""" # ╔═╡ a5f79c20-f62e-4df2-be79-b4f2141ced5e md""" -Create a *reaction network object* model for the aforementioned problem. Name it `wastewater_treatment`. +Model the system by means of ModelingToolkit. +""" -!!! tip - You can use the repressive Michaelis-Menten function `mmr(C, r, Ks)` for $r\cfrac{K_s}{K_s+C}$. +# ╔═╡ c9b86375-8750-4451-bdfe-10716b72d685 +md""" +Define the variables and assign them to their default values. """ # ╔═╡ 8708de16-3532-4352-b211-c092f95c82d3 -# Uncomment and complete the instruction -# wastewater_treatment = @reaction_network begin -# @parameters missing -# @species missing -# missing -# missing -# missing -# missing -# end +# @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""" -Convert the system to a symbolic differential equation model and verify your system of differential equations. +Set up the equations for the change in $C$ and $X$. """ # ╔═╡ fee917dd-7ab5-4fda-b1b7-87ee61e21f19 -# osys = missing # Uncomment and complete the instruction +# 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""" @@ -111,23 +124,23 @@ Initialize a vector `u0` with the initial conditions: """ # ╔═╡ fe02a755-5b00-4d80-a511-fec115b42964 -# u0 = missing # Uncomment and complete the instruction +# u0 = missing # ╔═╡ 67481927-0d03-4da9-af6c-9afa409fc006 md""" -Set the timespan: +Set the timespan to 72 hours: """ # ╔═╡ fadd372a-a665-4b16-9b6d-e32cb7f25d7f -# tspan = missing # Uncomment and complete the instruction +# tspan = missing # ╔═╡ 734e4d51-95a7-464e-9a23-5ad6c8715d65 md""" -Initialize a vector `params` with the parameter values: +Initialize a vector `parms` with the parameter values: """ # ╔═╡ 15ce9889-a437-46c8-9062-74b8d234a8bd -# params = missing # Uncomment and complete the instruction +# parms = missing # ╔═╡ b8a48461-3882-45f6-980c-38d650ac52c7 md""" @@ -140,7 +153,7 @@ Create the ODE problem and store it in `oprob`: """ # ╔═╡ b1e18139-5277-4f06-b1f8-b0f5f11c41d8 -# oprob = missing # Uncomment and complete the instruction +# oprob = missing # ╔═╡ ad6d8fe6-e62f-4c67-8d63-4ee13b928ad0 md""" @@ -148,18 +161,18 @@ Solve the ODE problem. Use `Tsit5()` and `saveat=0.1`. Store the solution in `os """ # ╔═╡ e2ffba9e-aaf2-4540-84cf-8b7297ae9285 -# osol = missing # Uncomment and complete the instruction +# osol = missing # ╔═╡ 70871ee8-b0a4-4a9a-af39-5a63459b55f7 md""" -Plot the results. Use `ylim=(0, 3)` and `lw=2` (or `linewidth=2`) as options. +Plot the results. Use `ylim=(0, 4)` and `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") +# plot!([tspan[1], tspan[2]], [0.28, 0.28], +# linestyle=:dash, linewidth=2, linecolor=:green, label="") # end # ╔═╡ a0e735ad-09c2-4aa8-bc41-b294a9d56ea8 @@ -168,22 +181,21 @@ Check out the end value of the organic waste. """ # ╔═╡ f62898d5-1b8d-4350-8655-78aa3decb2a2 -# missing # Uncomment and complete the instruction +# missing # ╔═╡ 7eb5df9c-a475-4812-81c3-e43484c82242 md""" -## Part 2 - Optimization +## 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\; kg\,m^{-3}$. +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""" -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}$. +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 -# Uncomment and complete the instruction # @model function wastewater_treatment_inference() # q ~ missing # u0 = missing @@ -191,7 +203,7 @@ First, declare the Turing model function. Sample the flow rate $q$ prior from an # params = missing # oprob = missing # osol = missing -# C_d ~ missing +# C ~ missing # end # ╔═╡ b3a40556-0c00-4f6d-8cd9-c5fca79d8bbf @@ -200,31 +212,21 @@ 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 +# missing # ╔═╡ 70cafd87-63f7-4674-ae49-43d422fdeae7 -md""" -Now condition the model with the desired value: -""" +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 `MLE` method and Nelder-Mead. Store the optimization results in `results_mle`. +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 # Uncomment and complete the instruction - -# ╔═╡ 97a00511-93d4-45d6-b320-9bad1b102397 -md""" -Check out the coefficient table. -""" - -# ╔═╡ 64844b47-1578-4e47-9cc7-7f242a583067 -# missing +# results_mle = missing # ╔═╡ 3ee8121e-3e78-4901-a32d-f04d0c6a0996 md""" @@ -232,7 +234,7 @@ 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 +# q_opt = missing # ╔═╡ ceb146c9-a09a-458b-b7d8-3bb7d3de38e0 md""" @@ -240,7 +242,7 @@ Set up parameter values with the optimized parameter value. """ # ╔═╡ e275df05-5c77-4c17-ad2e-503574596c31 -# params_opt = missing # Uncomment and complete the instruction +# parms_opt = missing # ╔═╡ cd515dad-44fb-4af2-b933-805ef76be9b3 md""" @@ -248,28 +250,27 @@ 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 +# oprob_opt = missing # ╔═╡ ec7bf654-b275-4cfd-a819-d82bdc1be93b -# osol_opt = missing # Uncomment and complete the instruction +# osol_opt = missing # ╔═╡ 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}$. +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 -# 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") +# 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$ respect now the limit in the concentration? Draw your conclusion. + Does the value of $C$ now respect the limit in the concentration? Draw your conclusion. """ # ╔═╡ 7c7d99b9-77b9-4c08-a74e-54eaa7d187ec @@ -278,20 +279,26 @@ md""" """ # ╔═╡ Cell order: +# ╟─c1bc698d-41ee-45e6-b17d-29f0d53557a1 # ╠═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 +# ╠═75d55180-bc34-4ff9-891f-cf522aab564e # ╠═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 +# ╟─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 @@ -317,8 +324,6 @@ md""" # ╠═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 @@ -329,4 +334,4 @@ md""" # ╟─82809c26-4cab-405e-8107-a8a43e81f699 # ╠═81429279-4190-41d1-a72a-20da0ce90528 # ╟─6589acfd-1d81-4c10-adea-34ca7fa1ab5d -# ╠═7c7d99b9-77b9-4c08-a74e-54eaa7d187ec +# ╟─7c7d99b9-77b9-4c08-a74e-54eaa7d187ec diff --git a/src/exercises/probabilistic_selection.jl b/src/exercises/probabilistic_selection.jl index 0fa79a9c..99b528cd 100644 --- a/src/exercises/probabilistic_selection.jl +++ b/src/exercises/probabilistic_selection.jl @@ -2,15 +2,12 @@ # v0.20.4 #> [frontmatter] -#> order = "36" -#> title = "7. Probability selection" -#> date = "2025-08-06" +#> order = "43" +#> title = "8. Probability selection" #> tags = ["exercises"] -#> description = "Probability selection" #> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" +#> description = "Probability selection" + using Markdown using InteractiveUtils @@ -27,6 +24,8 @@ macro bind(def, element) 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 @@ -139,20 +138,20 @@ md""" # ╔═╡ 05622800-ccd9-4aa8-9a95-a1fbe34aa76c @model function expon(num_pearls) - μ_exp ~ Uniform(0, 10) + μ_exp ~ missing pearls = zeros(num_pearls) for i in 1:num_pearls - pearls[i] ~ Exponential(μ_exp) + pearls[i] ~ missing end end # ╔═╡ c5b042bb-c1d9-4b0e-a2bd-a2c367019da3 @model function lognorm(num_pearls) - μ_lognorm ~ Uniform(0, log(10)) - σ_lognorm ~ Uniform(0, 1.0) + μ_lognorm ~ missing + σ_lognorm ~ missing pearls = zeros(num_pearls) for i in 1:num_pearls - pearls[i] ~ LogNormal(μ_lognorm, σ_lognorm) + pearls[i] ~ missing end end @@ -160,10 +159,10 @@ end md"Instantiate the models and condition them on the available data." # ╔═╡ 88306e52-d975-4bd5-a8f0-7e9293c9fb82 -expmodel = expon(length(pearlsizes)) | (pearls = pearlsizes,); +expmodel = missing # ╔═╡ a7cf9228-7515-4d53-b689-bc711283fecc -lognormmodel = lognorm(length(pearlsizes)) | (pearls = pearlsizes,); +lognormmodel = missing # ╔═╡ 811ee6ce-bb0f-4bcf-95be-6de95ab9166b md"### Maximum likelihood" @@ -174,27 +173,30 @@ Determine the maximum likelihood estimation (MLE) of the parameter values given """ # ╔═╡ 3b16c753-a2ba-4b59-a776-b35c4dd2e927 -exp_res = optimize(expmodel, MLE(), NelderMead()) +exp_res = missing # ╔═╡ 1ce90419-083a-475a-8334-32d73dafb606 -exp_mean = coef(exp_res)[:μ_exp] +exp_mean = missing # ╔═╡ 654d0ec1-92b0-4220-91a8-607978f1cf4f -lognorm_res = optimize(lognormmodel, MLE(), NelderMead()) +lognorm_res = missing # ╔═╡ 17e90886-1a41-4c19-b93a-96d8bd7a075f -lognorm_mean, lognorm_spread = coef(lognorm_res)[[:μ_lognorm, :σ_lognorm]] +lognorm_mean = missing + +# ╔═╡ 27883caa-5b08-4f93-8d1a-ae3d85faa9d1 +lognorm_spread = missing # ╔═╡ 62f000f3-6f21-4c1e-ab37-29a122b84088 begin histogram(pearlsizes, normalize = :pdf) - plot!(Exponential(exp_mean), linewidth = 3) + # add plot of best fit exponential distribution end # ╔═╡ 7dc215e7-83ef-430e-9f99-24cada961964 begin histogram(pearlsizes, normalize = :pdf) - plot!(LogNormal(lognorm_mean, lognorm_spread), linewidth = 3) + # add plot of best fit LogNormal distribution end # ╔═╡ abffd9fb-dd0a-4145-8b7d-8e233e498735 @@ -252,9 +254,9 @@ end 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) + likelihood_per_point = [missing for pearlsize in pearlsizes] + likelihood = missing + prior = missing evidence_exp += likelihood * prior * Δm end println(evidence_exp) @@ -269,11 +271,11 @@ begin for m in 0.1:Δm:log(10) for s in 0.1:Δs:1.0 likelihood_per_point = [ - pdf(LogNormal(m, s), pearlsize) + missing for pearlsize in pearlsizes ] - likelihood = prod(likelihood_per_point) - prior = pdf(Uniform(0, log(10)), m) * pdf(Uniform(0, 1.0), s) + likelihood = missing + prior = missing evidence_lognorm += likelihood * prior * Δm * Δs end end @@ -295,9 +297,9 @@ P_M_exp = 0.5 P_M_lognorm = 1 - P_M_exp # ╔═╡ 37f977de-f1a9-4a83-85f1-8dadf7910025 -bayes_factor = (evidence_lognorm * P_M_lognorm) / (evidence_exp * P_M_exp) +bayes_factor = missing -# ╔═╡ 4995e5a0-b56d-4aa5-b41d-e264936c3bd9 +# ╔═╡ 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 @@ -309,19 +311,7 @@ P(M_1 \mid D) &= \frac{P(D \mid M_1) \, P(M_1)}{P(D)} \, , """ # ╔═╡ 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) +P_M_exp_cond_D = missing # ╔═╡ 5b34c046-bc87-474d-8c20-b5732c0de219 md"### AIC" @@ -345,13 +335,13 @@ md""" """ # ╔═╡ 2e1fb72c-fea9-4de1-bed5-ee947306bc65 -AIC(num_params, loglikelihood) = 2*num_params - 2 * loglikelihood +AIC(num_params, loglikelihood) = missing # ╔═╡ 3553ed7d-07a0-418a-8d5f-6791e62d9e10 -AIC_exp = AIC(1, exp_res.lp) +AIC_exp = missing # ╔═╡ 257f0675-c5fe-45c5-aba0-ac808efb0ead -AIC_lognorm = AIC(2, lognorm_res.lp) +AIC_lognorm = missing # ╔═╡ 1936f7bb-ad6b-4759-b37e-a9b73a8dabc2 md"### BIC" @@ -369,14 +359,13 @@ md""" """ # ╔═╡ 01648f73-8b75-4158-a7ea-00e55ece7548 -BIC(num_observations, num_params, loglikelihood) = num_params*log(num_observations) - - 2 * loglikelihood +BIC(num_observations, num_params, loglikelihood) = missing # ╔═╡ 1e685af1-6abe-4849-b07b-2ab1da058edc -BIC_exp = BIC(length(pearlsizes), 1, exp_res.lp) +BIC_exp = missing # ╔═╡ 23a7d39c-0c0f-43fb-88be-ddfaba988a2c -BIC_lognorm = BIC(length(pearlsizes), 2, lognorm_res.lp) +BIC_lognorm = missing # ╔═╡ 4cf6a2d0-e58c-4b80-abf9-525097d75f35 md"## Overlapping cells" @@ -426,12 +415,11 @@ The model for one cell is defined as follows: """ # ╔═╡ c8da1bc6-4455-4577-98bb-d7dd41ff4f06 -@model function singlecell(n) - xm ~ Normal(0, 1) - ym ~ Normal(0, 1) +@model function singlecell(n) # n is number of points + xm ~ missing + ym ~ missing - xs ~ filldist(Normal(xm, 1.0), n) - ys ~ filldist(Normal(ym, 1.0), n) + missing end # ╔═╡ 523d6280-7147-4652-b593-cdd802d80b4e @@ -450,36 +438,18 @@ md""" """ # ╔═╡ 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) +@model function doublecell(n) # n is number of points + missing 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,); +singlemodel = missing # ╔═╡ 153ccedb-b437-4906-a2ef-1745b0dbf53e -doublemodel = doublecell(n) | (xs = xs, ys = ys,); +doublemodel = missing # ╔═╡ 29e3dc3a-f2c4-45a6-80a0-01bde4d41d98 md"### Maximum likelihood" @@ -524,16 +494,24 @@ Determine the maximum likelihood estimation (MLE) of the parameter values given # ╔═╡ 8942d992-5084-4c2c-9cbc-c442b0381922 -singleres = optimize(singlemodel, MLE(), NelderMead()) +singleres = missing # ╔═╡ 99a8a4b4-bbcd-4321-ba41-4f307c04af8e -single_xm, single_ym = coef(singleres)[[:xm, :ym]]; +begin + single_xm = missing + single_ym = missing +end # ╔═╡ 51ce7374-c7d8-4a32-815d-1c2ff9ba9970 -doubleres = optimize(doublemodel, MLE(), NelderMead()) +doubleres = missing # ╔═╡ 5a0860a2-8a61-4abb-acaf-3ea3cad0c286 -double_xm1, double_xm2, double_ym1, double_ym2 = coef(doubleres)[[:xm1, :xm2, :ym1, :ym2]]; +begin + double_xm1 = missing + double_xm2 = missing + double_ym1 = missing + double_ym2 = missing +end # ╔═╡ 73d259c6-f776-4586-a24f-3ecc368e28ae md"Visualise the results" @@ -557,10 +535,10 @@ md"### AIC" 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) +AIC_single = missing # ╔═╡ 40077482-7c98-4ca6-b441-ed3ebd893369 -AIC_double = AIC(4, doubleres.lp) +AIC_double = missing # ╔═╡ Cell order: # ╟─fb07436c-cc5d-4721-8a80-d7f7201721d7 @@ -595,6 +573,7 @@ AIC_double = AIC(4, doubleres.lp) # ╠═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 @@ -613,11 +592,8 @@ AIC_double = AIC(4, doubleres.lp) # ╠═8c63219d-e520-40b7-a4b3-c6e2ffc7b17e # ╠═84f99a01-f3e5-4197-998d-2bdec0d53fb1 # ╠═37f977de-f1a9-4a83-85f1-8dadf7910025 -# ╟─4995e5a0-b56d-4aa5-b41d-e264936c3bd9 +# ╟─a7fd0d4d-e0de-4f4b-84a8-64c94a300fba # ╠═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 @@ -647,10 +623,7 @@ AIC_double = AIC(4, doubleres.lp) # ╟─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 diff --git a/src/exercises/probmod_1-intro.jl b/src/exercises/probmod_1-intro.jl index 80110e02..28295e81 100644 --- a/src/exercises/probmod_1-intro.jl +++ b/src/exercises/probmod_1-intro.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.6 +# v0.20.21 #> [frontmatter] -#> order = "16" -#> title = "3. ProbMod intro" -#> date = "2025-03-07" +#> order = "23" +#> title = "4. ProbMod intro" #> tags = ["exercises"] -#> description = "Introduction to the sampling practicals" #> layout = "layout.jlhtml" +#> description = "Introduction to the sampling practicals" #> #> [[frontmatter.author]] #> name = "Bram Spanoghe" @@ -15,438 +14,522 @@ using Markdown using InteractiveUtils -# ╔═╡ f7cb8633-3b78-4203-92e0-37150d5dcd18 +# 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") -# ╔═╡ 8307092d-368d-441d-8315-3dc312026534 +# ╔═╡ 0ede7402-6b92-456c-aaf9-89e1b272d63d using Turing, StatsPlots -# ╔═╡ d86c5cc5-5353-4b04-b1d2-06527c85b2db +# ╔═╡ 59ce4733-40b6-4102-892c-13b7f46d9d11 using PlutoUI; TableOfContents() -# ╔═╡ aeb0aef0-b2ee-11ef-3cca-7f80b487ea17 +# ╔═╡ 866cdda4-1863-4a8b-95fc-2e65849f6346 md"# Sampling notebook #1: Intro" -# ╔═╡ 30957e05-85b8-4106-9635-82a5c11d9825 +# ╔═╡ e4b9d0c5-776a-4fa0-ad95-3ab632e22609 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. +To start off, let's load the required packages. """ -# ╔═╡ 9f89e350-199f-4875-947b-61df653ffc19 -md"## Problem" +# ╔═╡ 139b8552-bb44-4547-9c05-f5d435683e31 +md"## Problem description" -# ╔═╡ 2e544855-16b8-4794-9ba7-70a1e7209dd2 +# ╔═╡ 1785baee-b26b-4b2a-a3de-38efea3eb7b5 md""" -Let's go back to the circle throw example from the theory. +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**. +""" -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. +# ╔═╡ 2adece60-5f0d-408f-82eb-51bf047a6d0f +md""" +![Problem illustration](https://i.imgur.com/7TDkD08.png) +> **Figure 1**: An illustration of the problem. +""" -```math -\begin{align} +# ╔═╡ e1ad094e-d10d-4cca-9b83-f653e02f4d58 +md"## The model" -A_{circle} &= π -\\ A_{square} &= (1 - (-1))^2 = 4 -\\ P_{inside} &= \frac{A_{circle}}{A_{square}} = \frac{π}{4} +# ╔═╡ 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. +""" -\end{align} -``` +# ╔═╡ 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$. """ -# ╔═╡ 88b3087c-f78a-4df4-a9dc-5696bf4052d5 +# ╔═╡ 03f4935b-ac2b-4f73-9cee-ea04f700df83 +md"### Mathematical description" + +# ╔═╡ 543cea22-f592-41ef-a6d2-7d35063c387f md""" -This means if we can estimate this probability, we can estimate π easily! +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} - -P_{inside} &= \frac{π}{4} -\\ π &= 4 \, P_{inside} - +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. """ -# ╔═╡ 78f872c6-013b-4ad1-966f-9e0ce3288019 +# ╔═╡ ee9cd05c-7fa5-4412-b3b4-32b0a1774c87 md""" -However simple the problem, this probability is not simple to calculate by hand: - +Checking for droplet collision then comes down to finding the time $t_f$ the droplet has travelled a distance $x_f$ ```math -P_{inside} = \int_{0}^{1} \, \int_0^{\sqrt{1 - y^2}} dx \, dy +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] \, . ``` """ -# ╔═╡ 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 +# ╔═╡ af5a6f7d-b017-4cb6-b54a-a0defbfe7cdf 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. +![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! """ -# ╔═╡ ccdcfa65-a02f-4110-a664-36090c3291d8 -md"## Explanation" - -# ╔═╡ 43e8f794-fec2-4ce4-9306-2fc8a9565343 -md"### Defining the model" +# ╔═╡ e8821ac2-baa3-4674-868c-3a5b8593cc95 +md"### The variables" -# ╔═╡ ed788556-1627-4e9a-b901-e532272a8265 +# ╔═╡ 4811e1af-9ba4-49b3-a3a1-5d8ff8d5af92 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. +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. """ -# ╔═╡ 802e2769-613c-4ab6-b0e5-38173c220042 +# ╔═╡ 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""" -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. +!!! 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). """ -# ╔═╡ 1ee1ca1b-93dc-4c5e-a1c5-79dc8b365226 -md"This translates to the following in Turing" +# ╔═╡ c070f3c9-dd80-49f9-b279-c77f46316116 +md"## Defining the model" -# ╔═╡ 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) +# ╔═╡ 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. +""" -# ╔═╡ 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." +# ╔═╡ 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:" -# ╔═╡ 1df2ac6a-fa56-47e2-8c10-2a5d4da3a475 -sp_inside = sp_dists .<= 1 # the circle has a radius of 1 +# ╔═╡ 07c7645a-2b56-4089-90c9-736d7794a109 +const g = 9.8 -# ╔═╡ 418e1de5-74ad-47d5-8500-4996a86c8e53 +# ╔═╡ 43582e68-1289-4d5a-8895-dc06ec2b9b5b 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`. + 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. +""" - While that approach is also perfectly fine, it would have made visualising the distribution of distances more difficult. +# ╔═╡ 40f4edce-13c4-4063-98f4-23fd948c58dd +md""" +Our model can be defined as follows: """ -# ╔═╡ 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) +# ╔═╡ 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 -# ╔═╡ 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 +# ╔═╡ 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." -# ╔═╡ 73278ad6-00f2-4e57-8c17-4e0a47be5d57 -4*prob_inside +# ╔═╡ 92d8ccc8-cd5e-4d0b-a8bf-4be301784a18 +splash_model = splash(1) -# ╔═╡ 19162953-a1d6-4b3b-962e-947e36b032c5 +# ╔═╡ 6e1d6cfb-47ed-4c0f-bc5f-c327c58ae128 md""" !!! note - How many samples do you need to get that beautiful `3.14` consistently? How about the yet even more charming `3.1415`? + 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. """ -# ╔═╡ c79118af-a437-4979-a857-a6c84e1f789a -md"### Sampling everything with `sample`" +# ╔═╡ 4e2bf07a-5f7c-4f33-b7e7-b481a6f8cef7 +md"## Sampling the model" -# ╔═╡ 55206a66-fda0-4299-95f0-6dcc32287f3a +# ╔═╡ cda45ee9-612e-4b2e-aca7-5da34a23dd02 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_. +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. """ -# ╔═╡ 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. -""" +# ╔═╡ 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:" -# ╔═╡ 8ef27c2f-31fe-4411-9d2f-a65041e07641 -dist_chain = sample(dist_model, Prior(), n_samples) +# ╔═╡ 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 +); -# ╔═╡ 469a3fad-b491-4ee6-88a9-cfc47c78700a +# ╔═╡ 7e67c4bc-e146-484a-8284-50127c3fbea1 +chain + +# ╔═╡ f648fd3a-057d-4e73-9331-e87c60c014a7 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$ + 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$). """ -# ╔═╡ 95d50ada-1d2b-47af-8447-c6a465e59025 -md""" -The samples are visualised below: -""" +# ╔═╡ 1efb24f2-b988-437b-a7b7-2b3b7746dc07 +md"### Getting the model output" -# ╔═╡ ee6a2c1d-5522-4870-a0e1-664f8ccd5d8f -scatter(dist_chain[:x], dist_chain[:y], aspect_ratio = :equal, label = "Dart locations") +# ╔═╡ 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:" -# ╔═╡ 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`:" +# ╔═╡ 071fc405-7a33-4dab-a69a-1e68a0fb35ad +y_f_samples = generated_quantities(splash_model, chain) -# ╔═╡ 3994fc44-5fda-47dc-8cc6-e2550d9c237d -dist_chain[:x] # or dist_chain["x"] +# ╔═╡ 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:" -# ╔═╡ eb4b6d56-1e41-4e75-914b-8e7444afb288 -md"This can be useful for making plots, for example." +# ╔═╡ 45d44cf5-2289-4947-8d4e-1dcddb5447de +histogram(y_f_samples, bins = 15) -# ╔═╡ 8c3f32ac-c4fd-416f-a798-bbfe9977e5fc -md"#### The `generated_quantities` function" +# ╔═╡ 8d179748-40aa-4e88-b277-9234fe938251 +md"Checking whether the droplet altitude matches our face's can be done with a simple inequality operation:" -# ╔═╡ 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." +# ╔═╡ 633a2b4c-379e-4e18-bbcd-b450930196a5 +face_hit_samples = [1.5 <= y_f <= 1.7 for y_f in y_f_samples] -# ╔═╡ 0dcc8d6f-3ab2-429d-bcf4-f022ea5d0124 -sp_dists_alt = generated_quantities(dist_model, dist_chain) +# ╔═╡ 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." -# ╔═╡ 5a8adeb4-46cf-486f-adda-24661a79b2e9 -sp_inside_alt = sp_dists_alt .<= 1 +# ╔═╡ e1f9d1e9-8a0c-4b28-ab67-a89e3287b50f +mean(face_hit_samples) -# ╔═╡ 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. +# ╔═╡ 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.""" -# ╔═╡ ab230de4-767f-43e8-8bdc-054234852715 -md"### For-loops for many variables" +# ╔═╡ 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)` +""" -# ╔═╡ 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. +# ╔═╡ 3067af6e-df69-48a0-a024-a6e219dcfc7c +md"### Getting the stochastic variables" -Consider the circle example again but using a loop this time, which can easily be generalized to $n$ dimensions: -" +# ╔═╡ 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$." -# ╔═╡ 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 +# ╔═╡ 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):" - dist = sqrt(sum(coords.^2)) +# ╔═╡ dad51c0a-b93f-4637-b095-244fd5513838 +begin + v0_samples = chain["v0"] + α_samples = chain[:α] end -# ╔═╡ c50b7bb9-57e9-4f86-a0fa-0a68488b2fa9 -distloop_model = distances_loop(); +# ╔═╡ 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: +""" -# ╔═╡ 330531d8-c0cf-4d0f-84b3-42acf9e30b39 -sp_loop = [distloop_model() for i in 1:n_samples] +# ╔═╡ 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 +``` +""" -# ╔═╡ 9d786f74-73de-4fe3-8842-fa394641ce29 -4*mean(sp_loop .<= 1) +# ╔═╡ c9df5202-37f1-4e93-ab94-546ac7bb9444 +md""" +!!! note + You can also use this equation to find $y(x_f)$ in the Turing model. +""" -# ╔═╡ 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:" +# ╔═╡ 4faf9589-a815-4223-92f9-34264fb22401 +md"First we define our trajectory function in function of $x$ and our stochastic variables." -# ╔═╡ eb295f23-1afd-4966-a55d-ba10b3f9b72b -loop_chain = sample(distloop_model, Prior(), n_samples); +# ╔═╡ 577df19a-7da2-40b1-b1d4-38c3aec16ae7 +y(x, α, v0) = sin(α)/cos(α)*x - g/2*(x / (cos(α) * v0))^2 -# ╔═╡ f62cf74f-4572-4374-84c6-79b502feacb4 -loop_chain["coords[1]"] +# ╔═╡ 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." -# ╔═╡ 6faecd7e-fa99-4616-86c5-2561985f98a1 -md"### Working with Distributions" +# ╔═╡ 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) +]; -# ╔═╡ 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. -""" +# ╔═╡ 00572e1a-d1e6-4c84-b1b2-b53404087dd8 +md"Finally, we plot all sampled trajectories." -# ╔═╡ 996e876a-8e2d-44d4-bd3a-7a8d4fd8c6ea +# ╔═╡ 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 - Turing automatically loads Distributions into the workspace, so "`using Distributions`" is not necessary when Turing has been loaded. + 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). """ -# ╔═╡ e8e65be5-b6b2-4fd6-bfb3-c277433b7206 -md"Considering the humble example of `X ~ Exponential(10)`, let's do some plotting, sampling and calculating." +# ╔═╡ 1de58e6a-4ee2-4d67-b97d-e63bbb9ed057 +md"## Sampling alternative" -# ╔═╡ 398e15bb-7cc4-4f61-919b-9edf790cacd3 -plot(Exponential(10)) +# ╔═╡ 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." -# ╔═╡ 58a6ffb1-0fef-4edb-9fbe-de5b399b013d -spX = rand(Exponential(10), n_samples) +# ╔═╡ 771b9cb1-d809-46de-9380-2db73ccd014d +# get a sample of the output 500 times +y_f_samples_alternative = [splash_model() for _ in 1:500] -# ╔═╡ 6b3ac000-d31b-46ef-89bd-830c2cae8cdb -histogram(spX) +# ╔═╡ 0f1cccd8-937d-4fc2-8657-c9967db483b8 +histogram(y_f_samples_alternative, bins = 15) # yup, that's the same distribution -# ╔═╡ 84b9eb6b-d18a-4394-afff-3e49293aa1d4 -pdf(Exponential(10), 0) +# ╔═╡ 7806eca0-a2ad-4910-8d6e-edc50073b37b +md"## For-loops for many variables" -# ╔═╡ b577685b-00fb-4869-b930-1ec6cd163f7d -cdf(Exponential(10), 20) +# ╔═╡ 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. -# ╔═╡ 03ebb0a1-4fff-4d87-8d86-4d409d09363d -md"Just for fun, we can work out the circle example again without Turing." +This can be done using a **for-loop** as follows: +""" -# ╔═╡ 48fe2927-8c79-4482-8edc-21d8e0819619 -begin +# ╔═╡ 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) -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 + return total_mass end -sp_inside_noturing = sp_dists_noturing .<= 1 -4 * mean(sp_inside_noturing) +# ╔═╡ 2cbe2750-38c1-4e7f-8d03-9bed7c16861a +mass_model = splash_mass(); -end +# ╔═╡ 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) -# ╔═╡ 5acc6791-7cb6-4a31-a28d-649e761329ee -md"## The essentials" +# ╔═╡ befb8f05-6ab7-4594-9cf1-71df47c1df03 +md"## Essentials" -# ╔═╡ f3a73486-485a-4c69-b3db-2153b5a06bd8 +# ╔═╡ 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. +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. +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 +# ╔═╡ 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) - @model function distances() - x ~ Uniform(-1, 1) - y ~ Uniform(-1, 1) - dist = sqrt(x^2 + y^2) - return dist +# ╔═╡ 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 - 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 + # 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: -# ╟─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 +# ╟─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 index 07efe6d2..a21c2c7c 100644 --- a/src/exercises/probmod_2-basics.jl +++ b/src/exercises/probmod_2-basics.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "17" -#> title = "3. ProbMod basics" -#> date = "2025-03-07" +#> order = "24" +#> title = "4. ProbMod basics" #> tags = ["exercises"] -#> description = "Basic sampling exercises" #> layout = "layout.jlhtml" +#> description = "Basic sampling exercises" #> #> [[frontmatter.author]] #> name = "Bram Spanoghe" @@ -15,7 +14,9 @@ using Markdown using InteractiveUtils -# ╔═╡ 84d9771c-75b1-4d22-a1cd-b9078866a836 +# ╔═╡ 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 @@ -56,11 +57,17 @@ end # ╔═╡ 42c18a70-efb3-436b-83bb-b586280d4a4e dpmodel = doublepoisson(); +# ╔═╡ a21b2f44-c8d7-4838-926f-8fa39c36884e +dpchain = missing + # ╔═╡ b22a18d5-f70b-42a5-a09e-3de515148a6d -spY = missing +Y_samples = missing + +# ╔═╡ a11629df-31d2-4ac7-bf8e-80b910cab2fb +missing # plot of X # ╔═╡ 34bd60df-272f-49d7-9346-fb4d125fe89b -histogram(spY) +missing # histogram of Y # ╔═╡ 13989ba4-bcf8-4fdd-8aee-ab58c8905bc9 md"### 2: Probabilities" @@ -70,9 +77,9 @@ 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` + ✅ `Y_samples .< 1` compares every element of `Y_samples` to `1` - ❌ `spY < 1` compares an entire vector with a single number → errors :( + ❌ `Y_samples < 1` compares an entire vector with a single number → errors :( """ # ╔═╡ 39e4f3eb-b7a9-4ece-846f-eb02dbd77860 @@ -89,11 +96,11 @@ 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)$). + 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 -spX = missing; +X_samples = missing; # ╔═╡ aedd0fe8-da3e-4463-b0cf-7c4f9a22db52 varXcondY = missing @@ -104,53 +111,15 @@ 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 +# ╔═╡ 19e16f77-ea34-45f6-83eb-8d256e5fd10d 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! +!!! 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""" -## 3: Dice +## 2: Dice """ # ╔═╡ 187854bb-9e30-454d-9e03-cccf77aebb6b @@ -174,7 +143,7 @@ md"### 1: Watercube" # ╔═╡ 4fefa78b-746d-4e25-baee-d791eb22d930 md""" -!!! hint +!!! 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 """ @@ -189,11 +158,11 @@ md""" return dicesum end -# ╔═╡ 02bc37e2-5cf6-404a-b3fe-b2120671adb2 -watermodel = watercube() +# ╔═╡ 5fe1080e-8c2c-4c6b-84b4-3857dc1f399e +watercube_samples = missing # samples of dicesum for watercube # ╔═╡ b5886255-5c1d-4d84-b7ee-6c690fa526dc -p_watercube_kills = missing +p_watercube_kills = missing # probability that watercube kills the monster # ╔═╡ 84e06162-e3f1-4fd7-baf6-5095172413d2 missing # histogram @@ -205,15 +174,12 @@ md"### 2: Dirtprism" @model function dirtprism() # check the "For-loops for many variables" section from the intro notebook! - dicesum = missing # consider the `sum` function + dicesum = missing return dicesum end -# ╔═╡ 83afb3c1-9e6a-4d18-b0c7-05ed0173df40 -dirtmodel = dirtprism() - # ╔═╡ d9152416-8a7b-480c-ba9f-7ab15404b7a6 -p_dirtprism_kills = missing +p_dirtprism_kills = missing # probability that dirtprism kills the monster # ╔═╡ 90e058d7-b3fe-4c42-a652-3c42bf9d851a missing # histogram @@ -225,17 +191,17 @@ md"### 3: Comparison" p_watercube_is_better = missing # ╔═╡ 34f3014f-f4d4-43d1-b46f-bdca73aee33f -md"## 4: Super eggs" +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, go down as the chicken gets older. +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 12. +- 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)`. """ @@ -244,7 +210,7 @@ You can make the following assumptions 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. + 1. Compare the distributions of double eggs for 1-year old and 5-year old chickens. """ # ╔═╡ 98fcfcef-bf63-4eae-a325-ed4cef6d4fdd @@ -252,7 +218,7 @@ md"### 1: Probability" # ╔═╡ a0341046-c14a-494d-a9bd-a60c207c9e76 md""" -!!! hint +!!! 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. @@ -276,7 +242,7 @@ missing # histogram 1 missing # histogram 2 # ╔═╡ ff06c070-50a2-43d0-9729-1c47e728ff52 -md"## 5: Birthdays" +md"## 4: Birthdays" # ╔═╡ 6ac2238a-16fd-4a8d-b779-8627d87367ed md""" @@ -286,7 +252,7 @@ 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? + 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 @@ -308,7 +274,7 @@ end # ╔═╡ Cell order: # ╟─e4cb065e-12c6-4f1c-8497-1013fa9411d6 -# ╠═84d9771c-75b1-4d22-a1cd-b9078866a836 +# ╠═7d4d4d20-b323-11ef-0926-b14785cb9ab5 # ╠═4cfd4721-e29a-4270-8d15-021bcc966eb1 # ╠═73c2a5db-4019-4d91-b5f1-7ba378cb8c84 # ╟─7026f66f-9076-4aef-ada9-198450ef5da6 @@ -316,7 +282,9 @@ end # ╟─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 @@ -328,28 +296,18 @@ end # ╠═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 +# ╟─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 -# ╠═02bc37e2-5cf6-404a-b3fe-b2120671adb2 +# ╠═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 -# ╠═83afb3c1-9e6a-4d18-b0c7-05ed0173df40 # ╠═d9152416-8a7b-480c-ba9f-7ab15404b7a6 # ╠═90e058d7-b3fe-4c42-a652-3c42bf9d851a # ╟─49790a8f-9f53-4ba7-9543-d6a879b520e0 diff --git a/src/exercises/probmod_3-advanced.jl b/src/exercises/probmod_3-advanced.jl index ac95b401..232b1aab 100644 --- a/src/exercises/probmod_3-advanced.jl +++ b/src/exercises/probmod_3-advanced.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "18" -#> title = "3. ProbMod advanced" -#> date = "2025-03-07" +#> order = "25" +#> title = "4. ProbMod advanced" #> tags = ["exercises"] -#> description = "Advanced sampling exercises" #> layout = "layout.jlhtml" +#> description = "Advanced sampling exercises" #> #> [[frontmatter.author]] #> name = "Bram Spanoghe" @@ -18,7 +17,7 @@ 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 + 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) @@ -27,7 +26,9 @@ macro bind(def, element) #! format: on end -# ╔═╡ df95b3d0-9f2d-4267-ad8c-837c7848a871 +# ╔═╡ 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 @@ -63,7 +64,7 @@ Bacteria follow **logistic growth**, and you can use the following assumptions: 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? + 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. """ @@ -83,44 +84,20 @@ md""" """ # ╔═╡ eb6dc4e7-e779-4bfc-b865-3defa3894181 -dropletdist = missing; +missing # plot # ╔═╡ 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 +@model function petrigrowth(t) + missing + return splittable 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 @@ -130,9 +107,12 @@ 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. + 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 @@ -141,7 +121,7 @@ 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. +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. """ @@ -152,27 +132,28 @@ begin 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 + 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 -# ╔═╡ bb8e4cd6-6106-4d3f-9e35-0dfd8b2c45f5 +# ╔═╡ 4cb55330-8a8a-4622-af4a-ab6f0b643123 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: +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} +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. + 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 @@ -180,27 +161,15 @@ 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 +estimated_force = missing # ╔═╡ b28cfbae-2fac-4b38-b234-53f71e381bcd pointmass_force = missing # (doesn't require Turing, only maths) @@ -209,26 +178,22 @@ pointmass_force = missing # (doesn't require Turing, only maths) 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 +@bind n Slider(10:10:200, show_value = true) # ╔═╡ 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 +force_samples = missing + # multiple samples of your estimated force, using `n` samples # ╔═╡ 45f0813b-c4c9-4f13-8d66-1e58293c4422 -missing # histogram +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 -# ╠═df95b3d0-9f2d-4267-ad8c-837c7848a871 +# ╠═886c7932-da4b-45cc-ba73-8047389e4895 # ╠═80bc0e86-5ad3-4d61-9600-8dc05b86599d # ╠═fe68a4dd-038c-4f94-a4f0-48933ff2fa87 # ╟─116b840c-e766-4ff6-aafa-0977fb122992 @@ -239,31 +204,25 @@ missing # histogram # ╟─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 +# ╠═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 -# ╟─bb8e4cd6-6106-4d3f-9e35-0dfd8b2c45f5 +# ╟─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 -# ╠═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 +# ╠═f6a67c1e-3677-4e51-b6dd-5d11a5146ea7 diff --git a/src/exercises/probmod_4-review.jl b/src/exercises/probmod_4-review.jl index f1c846f2..42ee15b9 100644 --- a/src/exercises/probmod_4-review.jl +++ b/src/exercises/probmod_4-review.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "19" -#> title = "3. ProbMod review" -#> date = "2025-03-07" +#> order = "26" +#> title = "4. ProbMod review" #> tags = ["exercises"] -#> description = "Review sampling exercise" #> layout = "layout.jlhtml" +#> description = "Review sampling exercise" #> #> [[frontmatter.author]] #> name = "Bram Spanoghe" @@ -15,7 +14,9 @@ using Markdown using InteractiveUtils -# ╔═╡ 432217b3-d4c0-4472-9350-28649d7ad958 +# ╔═╡ 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 @@ -26,7 +27,7 @@ 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). +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? """ @@ -35,8 +36,8 @@ The experiment is as follows: consider a floor with parallel lines all a distanc 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) +html""" + """ # ╔═╡ d71cf8dc-99e5-48e0-9abe-2242a6ccc30b @@ -65,7 +66,7 @@ md""" """ # ╔═╡ Cell order: -# ╠═432217b3-d4c0-4472-9350-28649d7ad958 +# ╠═882b942c-a5ae-445d-a946-6eb8cddc423e # ╠═aa2737db-f89f-4e08-9abe-0a86e8880c19 # ╟─5be87389-1a97-481e-bec6-1d781f016077 # ╟─a6435b94-f1af-4609-abfc-93d88730d023 diff --git a/src/exercises/sde_model_aging.jl b/src/exercises/sde_model_aging_mtk.jl similarity index 56% rename from src/exercises/sde_model_aging.jl rename to src/exercises/sde_model_aging_mtk.jl index 9ce24bb8..3ea77a93 100644 --- a/src/exercises/sde_model_aging.jl +++ b/src/exercises/sde_model_aging_mtk.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "12" -#> title = "2. SDE aging" -#> date = "2025-02-07" +#> order = "18" +#> title = "3. SDE_model_aging" #> tags = ["exercises"] -#> description = "SDE aging" #> layout = "layout.jlhtml" +#> description = "Aging with saturated repair, modelled as an SDE" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,32 +15,32 @@ 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 +# 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 - -# ╔═╡ 7a0d5c7f-fb37-4b38-9736-32e81c8014c4 -using InteractiveUtils - -# ╔═╡ 68588061-fedd-4f19-9e89-39b1bd791e33 -using Catalyst +using Markdown, InteractiveUtils # ╔═╡ 1d0a8fb2-dab9-44ae-a230-f3e92cf3cd6a -using OrdinaryDiffEq, StochasticDiffEq, StatsPlots +using ModelingToolkit, StochasticDiffEq + +# ╔═╡ 7a9fa5bc-7bb3-436e-b796-b946c855fe0a +using ModelingToolkit: t_nounits as t, D_nounits as D # ╔═╡ 22e0eda2-a922-48bb-9b6f-ec0c6dfeb524 -using Distributions +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**. @@ -53,65 +52,92 @@ Hence, the model that we could adopt in order to predict the number of senescent A possible model is the following differential equation: -$$\cfrac{dX}{dt} = \mu t - \beta \cfrac{X}{X + \kappa}$$ +$$\cfrac{dX}{dt} = \mu t - \beta \cfrac{X}{X + \kappa} + \cfrac{dW}{dt}$$ -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. +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 +## 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`. +Implement the above ODE using MTK. -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. +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`. """ -# ╔═╡ 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 +# ╔═╡ 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. +""" -# ╔═╡ 51edb519-2086-45a5-a666-b5a58c51b5e8 +# ╔═╡ 705d5c4f-6a7b-4067-80ec-8fb5ccaedb18 md""" -Convert this *reaction model* into a symbolic differential equation model and verify that you get the correct differential equation as mentioned above. +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`. """ -# ╔═╡ b3dd11e4-1209-46f3-a47a-07b54c5e9d77 -# osys = missing # Uncomment and complete the instruction +# ╔═╡ 4e5b490e-67a8-40de-a49f-9f88dcb4b558 +missing # ╔═╡ 0524da26-35cb-4846-b7ef-f5842ae5db74 md""" -#### Setting initial condition, time span and parameters. +## 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. +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 # Uncomment and complete the instruction +u0 = missing # ╔═╡ ab8bef21-8c4f-4962-b9cb-e87ac1db049a -# tspan = missing # Uncomment and complete the instruction +tspan = missing # ╔═╡ ab302108-e9a7-4941-a762-c0dc109c9b1d -# parms = missing # Uncomment and complete the instruction +parms = missing # ╔═╡ 3934ba8f-49c9-4f0c-8373-c8010f397d66 md""" -#### Simulating the system as an SDE problem +## Simulating the system as an SDE problem """ # ╔═╡ 45fceec8-a641-43ed-9a17-72b795e09297 @@ -120,7 +146,7 @@ Create the SDE problem. """ # ╔═╡ 7d164cdf-fd63-4c85-b168-3279d6d658eb -# sprob = missing # Uncomment and complete the instruction +sprob_scs = missing # ╔═╡ 9f9a616a-4353-4f9a-8da5-effe4d214ed0 md""" @@ -128,7 +154,7 @@ 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 +ssol_scs = missing # ╔═╡ 91881bc3-a1f0-48f7-82e8-7d7acea2f4de md""" @@ -136,7 +162,7 @@ Plot the solutions. Use the option `ylim=(0, 6)` in order to limit the range of """ # ╔═╡ 7128ae66-c576-435b-8e33-4221f469ba1c -# missing # Uncomment and complete the instruction +missing # ╔═╡ 1bce56da-0c35-41df-adbd-b5dee116be11 md""" @@ -145,7 +171,7 @@ Execute the cell, where the SDE problem is being solved, a few times and watch t # ╔═╡ 7ca7c177-40b3-42c0-a57d-2f7eb874abdf md""" -#### Simulating the system as an EnsembleProblem. +## Simulating the system as an EnsembleProblem. """ # ╔═╡ 33debdb3-2008-4055-a778-600bf7409361 @@ -159,15 +185,18 @@ Create an `EnsembleProblem` based on `sprob`. """ # ╔═╡ 1f25730d-d93e-462a-ac02-f83381633849 -# esprob = missing # Uncomment and complete the instruction +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 options `save_everystep=true`, and `trajectories=100`. +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 = missing # Uncomment and complete the instruction +essol_scs = missing # ╔═╡ c048528f-58a2-415d-a338-fc87302367b8 md""" @@ -175,11 +204,11 @@ Plot the solutions. Use the option `ylim=(0, 6)` in order to limit the range of """ # ╔═╡ 147adea6-e7ed-4f7b-b753-127e41500109 -# missing # Uncomment and complete the instruction +missing # ╔═╡ 1db274cd-3b01-4f45-8aa0-ffe6878a1dd1 md""" -#### Distribution of ages at 5 trillion senescent cells +## Distribution of ages at 5 trillion senescent cells """ # ╔═╡ c024dd3a-8fa8-4dbe-9669-0f338416a6af @@ -190,33 +219,32 @@ Set up a histogram that shows the distribution of ages once the 5 trillion senes # ╔═╡ 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)` + - 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 -# Uncomment and complete the instruction # begin -# times = [] # make empty vector -# for missing # for loop from 1 to 100, default step is 1 +# 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 -# missing -# if missing # if index is a valid index -# missing # append time to vector times +# 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)`. +Make a histogram with the array `times`. Use `bins=range(0, 120, length=121)` or `bins=0:120`. """ # ╔═╡ 17a51f9e-0adf-4802-8d13-c43eb7801bc7 -# missing # Uncomment and complete the instruction +missing # ╔═╡ dad744a4-0fd2-406d-a600-429ad7619efd md""" @@ -224,7 +252,7 @@ Check the mean. """ # ╔═╡ a41d2ab4-ffad-4b27-9811-ab57c51526cb -# missing # Uncomment and complete the instruction +missing # ╔═╡ 98ca03e1-9c4e-40db-9dd7-68f1898c1ef7 md""" @@ -232,7 +260,7 @@ Check the standard deviation. """ # ╔═╡ 7ac54e2a-9c8e-4743-a1a0-38490b04f759 -# missing # Uncomment and complete the instruction +missing # ╔═╡ 0f9118f5-93f4-42f8-a0cf-754ce9ce9dbe md""" @@ -240,7 +268,7 @@ Check the minimum value. """ # ╔═╡ a07565f7-31d9-4c50-8345-5777adb8a77a -# missing # Uncomment and complete the instruction +missing # ╔═╡ 6f4253ce-e686-4061-ae13-9b35065464d9 md""" @@ -248,56 +276,49 @@ Check the maximum value. """ # ╔═╡ 70628044-aaa5-46d6-a2df-deabbd8f2df8 -# missing # Uncomment and complete the instruction +missing -# ╔═╡ b10a73a1-63ae-4a15-9a83-394f6ed4e36e +# ╔═╡ 23178795-d7fb-4a23-9649-a1ee9d1706d6 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? +!!! 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"- Answer: missing" - -# ╔═╡ 88c5c0f5-5bbf-4c54-9a9a-bf6369a2e895 md""" -2. What is the effect of halving the damage rate $\mu$? +Answers: +1. missing +2. missing +3. missing +4. missing """ -# ╔═╡ 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: +# ╟─a176adcc-bde0-4a25-aa80-33379e05e63f # ╠═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 +# ╠═7a9fa5bc-7bb3-436e-b796-b946c855fe0a # ╠═22e0eda2-a922-48bb-9b6f-ec0c6dfeb524 -# ╟─a176adcc-bde0-4a25-aa80-33379e05e63f +# ╟─387a7268-8b9d-4e23-b033-d9b697400fed # ╟─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 +# ╟─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 @@ -333,11 +354,5 @@ md"- Answer: missing" # ╠═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 +# ╟─23178795-d7fb-4a23-9649-a1ee9d1706d6 +# ╠═a4f5a40e-c4ec-45ef-b44b-b64b71717c25 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/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 diff --git a/src/exercises/sens_bitrophic_model.jl b/src/exercises/sens_bitrophic_model.jl old mode 100755 new mode 100644 index 70dcd656..37d816ee --- a/src/exercises/sens_bitrophic_model.jl +++ b/src/exercises/sens_bitrophic_model.jl @@ -2,12 +2,11 @@ # v0.20.4 #> [frontmatter] -#> order = "30" -#> title = "6. Sensitivity bitrophic model" -#> date = "2025-08-06" +#> order = "37" +#> title = "7. Sensitivity bitrophic model" #> tags = ["exercises"] -#> description = "Sensitivity bitrophic model" #> layout = "layout.jlhtml" +#> description = "Sensitivity bitrophic model" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,6 +15,8 @@ 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 diff --git a/src/exercises/sens_fermenter_monod.jl b/src/exercises/sens_fermenter_monod.jl old mode 100755 new mode 100644 index 45ed8e27..55fb2d38 --- a/src/exercises/sens_fermenter_monod.jl +++ b/src/exercises/sens_fermenter_monod.jl @@ -1,53 +1,58 @@ ### A Pluto.jl notebook ### -# v0.20.4 +# v0.20.21 #> [frontmatter] -#> order = "29" -#> title = "6. Sensitivity fermenter monod" -#> date = "2025-08-06" +#> order = "36" +#> title = "7. Sensitivity fermenter monod" #> tags = ["exercises"] -#> description = "Sensitivity fermenter monod" #> 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 - -# ╔═╡ 03ae0690-06a0-4276-9f00-d07b206fe124 -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ a355b0ba-baaf-49f4-a5dc-965364a884f0 -using Catalyst +using Catalyst, OrdinaryDiffEq # ╔═╡ 00fd6d49-f561-42e9-9413-d33af92f83dc -using OrdinaryDiffEq, StatsPlots +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 +# 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: +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} +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: @@ -55,12 +60,13 @@ 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 + @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 @@ -80,96 +86,122 @@ $$\begin{eqnarray*} 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}$. +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 +# osys = missing + +# ╔═╡ 1ee7359c-2757-44cd-8650-09b762cf30d0 +md""" +## Goals of this exercise +""" # ╔═╡ 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$: +Compute the following in a timespan of `(0.0, 100.0)`$\,h$: -- The sensitivities of $S$ and $X$ wrt. $\mu_{max}$, $K_s$ and $S_{in}$. +- 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$ 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}$. +- 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 `param` with the parameter values: +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 # Uncomment and complete the instruction +# u0 = missing # in principle not necessary since we will use the default # ╔═╡ 3fdc6b17-cdeb-4dc5-8886-9d3a62caac8d -# tspan = missing # Uncomment and complete the instruction +# tspan = missing -# ╔═╡ 0139da85-02e3-4021-9b39-84af7e68d428 +# ╔═╡ 201dfb54-2056-4846-a64c-ff4951dc084d 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. +For practical reasons, we will define the time step size as `dt = 0.5`. """ -# ╔═╡ 0f995929-4d2b-4a7a-8da1-04e4d501385f -# μmax = missing # Uncomment and complete the instruction +# ╔═╡ 5627068f-6442-43bb-bacb-bcba917372f3 +# dt = missing -# ╔═╡ 262e8346-df6d-49bf-9186-92f5afb421e0 -# Ks = missing # Uncomment and complete the instruction +# ╔═╡ 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. +""" -# ╔═╡ baa777d2-abb8-45f8-87aa-b3b17c8dc07c -# Sin = missing # Uncomment and complete the instruction +# ╔═╡ 0f995929-4d2b-4a7a-8da1-04e4d501385f +begin + # μmax_val = missing + # Ks_val = missing + # Sin_val = missing +end; # ╔═╡ 79b0eb65-5a0f-40b3-aa97-4088421c562e -# params = missing # Uncomment and complete the instruction +# 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=0.5`, and store the solution in `osol`. Finally plot the results. +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; # Uncomment and complete the instruction +# oprob = missing # ╔═╡ 89a31c32-88a4-479f-a688-ffcb75ee8e91 -# osol = missing # Uncomment and complete the instruction +# osol = missing # ╔═╡ 51a9b7e6-8ad9-477d-9596-ffd614df2c79 -# missing # Uncomment and complete the instruction +# 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 (with the values for which we want to calculate the sensitivity), and that returns the outputs. +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 -# 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 +# function fermenter_monod_sim(parms) +# 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$. +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(params) = missing # Uncomment and complete the instruction +# fermenter_monod_sim_S(parms) = missing # ╔═╡ 3ae5bd00-2e06-4789-aab3-d897824d5e29 -# fermenter_monod_sim_X(params) = missing # Uncomment and complete the instruction +# fermenter_monod_sim_X(params) = missing + +# ╔═╡ 6913fb1b-1986-4b37-819e-a6a7bd91f1a5 +md""" +### Compute the outputs +""" # ╔═╡ 4bd2bcca-9c42-4333-b062-2aaa9f7be3fe md""" @@ -177,7 +209,7 @@ Make the time vector. """ # ╔═╡ fbd98975-aa32-46ae-8db0-0e65cdf48309 -# t_vals = missing # Uncomment and complete the instruction +# t_vals = missing # ╔═╡ 93791eb3-1eaa-4146-90b5-c4811fb3485b md""" @@ -185,21 +217,31 @@ 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 +# S_sim = missing # ╔═╡ 95bc683c-f6e6-4b42-b90b-b5a863edd4d5 -# X_sim = missing # Uncomment and complete the instruction +# 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 # Uncomment and complete the instruction +# sens_S = missing # ╔═╡ 49a94b9a-a543-495e-b4f1-c8579e59304d -# sens_X = missing # Uncomment and complete the instruction +# sens_X = missing # ╔═╡ 9cace6c1-e678-4dd7-8705-92a55eb32fa9 md""" @@ -207,20 +249,23 @@ 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; +# 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; +# end + +# ╔═╡ f4d0aaaa-bb3f-4335-901a-dbbcde643c78 +md""" +#### Normalized sensitivities +""" # ╔═╡ 5bf3a62d-d2aa-4653-8ee8-e90caa9504e8 md""" @@ -228,7 +273,6 @@ 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 @@ -236,197 +280,399 @@ Compute the normalized sensitivities. # 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; +# end + +# ╔═╡ 7f0b9e63-2856-4601-9f14-c20c6b1b3707 +md""" +### Plotting + questions +""" # ╔═╡ 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=...`). +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 # Uncomment and complete the instruction - -# ╔═╡ 02314e9f-6186-4c97-9b28-03a4a1a15668 -maximum(sens_X_on_Sin_rel) +# missing # ╔═╡ d41375ef-6958-4705-a417-4c6a491232ee md""" -Interpret your results. Try to answer the following question(s): +!!! 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? """ -# ╔═╡ f6be14f3-e0d0-41dc-bc7b-54174b1ea5ea +# ╔═╡ 39c36eb4-5f15-4577-9b84-116e29d7891d md""" -!!! question - Which output variable, $S$ or $X$, is most sensitive to $S_{in}$ in steady state? +Answers: +- missing +- missing """ -# ╔═╡ b2583ee2-412b-4a0a-a4d0-fe476e0510e5 -md"- Answer: 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=...`). +""" -# ╔═╡ dbbf694b-942a-4757-a255-f67b208ba03b +# ╔═╡ c0223da4-9959-48d0-b607-633b2e82986c +# missing + +# ╔═╡ ff86a29f-9308-473b-aa1c-dfd4af8179c7 md""" -!!! question - Why is the sensitivity function of $S$ wrt. $S_{in}$ at first positive but then becomes zero? +!!! 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? """ -# ╔═╡ f10ac0f2-6b14-4805-b649-56c63f5b523a -md"- Answer: missing" -# ╔═╡ be89600a-4927-4afc-9813-d8a70adb2852 +# ╔═╡ 430494da-402e-409d-85fc-c87cdfa3b427 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=...`). +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. +=# -# ╔═╡ c0223da4-9959-48d0-b607-633b2e82986c -# missing # Uncomment and complete the instruction +# ╔═╡ 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=...`). +""" -# ╔═╡ 260ede0e-2584-4fe9-ae92-69990ca8f854 +# ╔═╡ 53134149-0bf7-41c1-9b35-e5037744211f +# missing + +# ╔═╡ 355ca6a7-466b-4969-ab48-28e2257f9810 md""" -Interpret your results. Try to answer the following question(s): +!!! 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? """ -# ╔═╡ ff86a29f-9308-473b-aa1c-dfd4af8179c7 +# ╔═╡ 9a18a8bd-27d3-4771-a844-f6b65c7c8918 md""" -!!! question - Which parameter, $\mu_{max}$, $K_s$ or $S_{in}$, affects the output $S$ the most in steady state? Why is this? +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. +=# -# ╔═╡ 555228b2-8075-49ac-a7ef-f51aa51dd95d -md"- Answer: missing" +# ╔═╡ 6536f76a-0f6d-4155-ab3b-38e2e3189886 +md""" +## Monte Carlo error propagation +""" -# ╔═╡ 6704e43b-30bc-4168-93db-fb853c9761fa +# ╔═╡ 9fd59148-6993-4598-af95-ac85b6a4bb45 md""" -!!! question - Why is the sensitivity function of $S$ wrt. $\mu_{max}$ negative? +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%. """ -# ╔═╡ 4d2094d8-98b3-4ac9-9612-942b5cd18ce0 -md"- Answer: missing" +# ╔═╡ 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. +""" -# ╔═╡ dc8ef7cb-ae97-401c-9a25-12439e4363c6 +# ╔═╡ ecc28b8e-0eaf-489f-8104-7c6383d4e01f 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?* +!!! note + Instead of creating a new ODEProblem everytime, you can simply remake an old ODEProblem by using `new_prob = remake(oprob, p = [:Ks => Ks])`. """ -# ╔═╡ 66712fb2-50ea-41be-bcca-305319d158e9 -md"- Answer: missing" +# ╔═╡ 26ae811d-11fd-44d8-b328-79381fa7472c +# @model function monod_deviation() +# Ks_dev ~ missing # 20% standard deviation +# new_prob = missing +# return missing +# end -# ╔═╡ 16a84fdb-8ce2-45b9-bfb7-7f4e1284a1d7 +# ╔═╡ 62791c48-5e38-46fe-8336-3a21003f3fe3 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=...`). +Now get sample 100 solutions from your monod_deviation model. """ -# ╔═╡ 53134149-0bf7-41c1-9b35-e5037744211f +# ╔═╡ 9e049653-347a-4198-90fd-23f15c404d0c # missing -# ╔═╡ 0355bbf6-853d-4bd8-b32f-98fdbd2b761c +# ╔═╡ f11827e5-de69-4a0c-b385-2c6ea031bb79 md""" -Interpret your results. Try to answer the following question(s): +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`. """ -# ╔═╡ 355ca6a7-466b-4969-ab48-28e2257f9810 +# ╔═╡ 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""" -!!! question - Which parameter, $\mu_{max}$, $K_s$ or $S_{in}$, affects the output $X$ the most in steady state? +#### Conditioning on the measurements """ -# ╔═╡ 4e08baf1-eeb6-49be-9751-204dd9ae1103 -md"- Answer: missing" +# ╔═╡ 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 -# ╔═╡ 182e0c2b-9aa4-4514-96e8-b6a9f53b371b +# 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""" -!!! question - Why is the sensitivity function of $X$ wrt. $\mu_{max}$ positive? How does this compare to substrate $S$? +### MCMC """ -# ╔═╡ 05ec320e-ac87-45af-904e-5d69639e1f27 -md"- Answer: missing" +# ╔═╡ b8cd84de-57e4-4d8b-92da-e7c5b6d627bd +# monod_chain = missing -# ╔═╡ 22e98c77-0e9f-444a-a2f0-b940940996b7 +# ╔═╡ e76a7bf7-4d8d-4f2f-8272-b63e49cd4ae9 md""" -!!! question - Why is the sensitivity function of $X$ wrt. $K_s$ negative? Compare it to that of substrate $S$. +Plot the chain to check if it properly converged to the posterior distribution. """ -# ╔═╡ 91795413-c9a2-43cb-a7b0-d9e055e1cf94 -md"- Answer: missing" +# ╔═╡ 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 -# ╠═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 +# ╠═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 -# ╠═262e8346-df6d-49bf-9186-92f5afb421e0 -# ╠═baa777d2-abb8-45f8-87aa-b3b17c8dc07c # ╠═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 -# ╠═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 +# ╠═39c36eb4-5f15-4577-9b84-116e29d7891d # ╟─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 +# ╟─430494da-402e-409d-85fc-c87cdfa3b427 # ╟─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 +# ╟─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 old mode 100755 new mode 100644 index 67556bce..93484dd1 --- a/src/exercises/sens_insuline.jl +++ b/src/exercises/sens_insuline.jl @@ -2,12 +2,11 @@ # v0.20.4 #> [frontmatter] -#> order = "31" -#> title = "6. Sensitivity insuline" -#> date = "2025-08-06" +#> order = "38" +#> title = "7. Sensitivity insuline" #> tags = ["exercises"] -#> description = "Sensitivity insuline" #> layout = "layout.jlhtml" +#> description = "Sensitivity insuline" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -28,6 +27,8 @@ macro bind(def, element) 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 diff --git a/src/exercises/sens_intro.jl b/src/exercises/sens_intro.jl old mode 100755 new mode 100644 index 83382742..19899bd0 --- a/src/exercises/sens_intro.jl +++ b/src/exercises/sens_intro.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.6 +# v0.19.46 #> [frontmatter] -#> order = "28" -#> title = "6. Sensitivity intro" -#> date = "2025-08-06" +#> order = "35" +#> title = "7. Sensitivity intro" #> tags = ["exercises"] -#> description = "Sensitivity intro" #> layout = "layout.jlhtml" +#> description = "Sensitivity intro" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,6 +15,8 @@ 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 @@ -24,9 +25,6 @@ using Markdown # ╔═╡ 489b5399-fe4c-481a-834f-0101bbe28cea using InteractiveUtils -# ╔═╡ ab4a1911-461e-4878-9258-931fc2f1ea06 -using PlutoUI; TableOfContents() - # ╔═╡ 22489bd4-ab64-4bf9-ad03-5372ea273935 using Catalyst @@ -48,26 +46,25 @@ md""" # ╔═╡ 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. +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_i$ is to changes in parameter $\theta_j$ is given by the partial derivative +The sensitivity function that measures how sensitive output $y_j$ is to changes in parameter $\theta_i$ is given by the partial derivative -$$S_{ij} = \cfrac{\partial \hat{y}_i(\theta)}{\partial \theta_j}\tag{1}$$ +$$\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? +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" +md""" +Answer: missing +""" # ╔═╡ a6a97279-0b64-45c3-8312-c22b1a8425d0 md""" @@ -78,23 +75,23 @@ $$\cfrac{\partial \hat{y}_i(\theta)}{\partial \theta_j} \approx \cfrac{\hat{y}_i # ╔═╡ 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. +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: +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}$$ +$$\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. +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 these concepts, we first consider three simple models describing the growth of grass. +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 @@ -110,7 +107,7 @@ In this notebook, three different models will be used, each modelling the yield - 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: +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$ | |:----------- |:----------:|:-----------:|:------------:| @@ -123,7 +120,7 @@ We will use an initial condition of $W_0 = 2.0$ for each and a simulation time o # ╔═╡ 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. +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. @@ -132,9 +129,8 @@ We will illustrate how to compute the local normalized sensitivity functions for # ╔═╡ 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. + +We will start by modelling our system and simulating using the aforementioned parameters values, initial condition and timespan. """ # ╔═╡ 5492210b-6ee6-4baf-b37e-d21358cdeb60 @@ -146,6 +142,8 @@ Implementation of the system: 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 @@ -188,8 +186,8 @@ 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_log, u0_log, tspan, params_log) +# Also possible: # oprob_log = ODEProblem(growth_mod_log, [], tspan, []) # ╔═╡ ac235d86-1d93-4944-aa89-1b4fd38f0e6e @@ -210,7 +208,7 @@ In order to compute the local sensitivity functions, we will need to load the `F # ╔═╡ 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). +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 @@ -218,14 +216,14 @@ 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]); + 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. +Next we will need to make a function based on the solution function that returns a single output. """ # ╔═╡ d0f3197f-3094-4f6e-b33c-e5b74e0947f6 @@ -238,15 +236,12 @@ 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: +# Alternative: # 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. +Compute the single output with the given parameter values. """ # ╔═╡ 99864b21-3db0-4d60-a84e-8e96db4de4ae @@ -254,7 +249,7 @@ 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. +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 @@ -262,9 +257,9 @@ 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$. +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 @@ -286,7 +281,7 @@ 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. +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 @@ -297,22 +292,32 @@ md""" Notice that in the `label` option there is no comma separating the labels. """ -# ╔═╡ 04a1d1ad-53a9-4146-be09-65aa422e3730 +# ╔═╡ 88aa13f6-e16c-40cd-ac10-3ede8b9fb429 +# ╠═╡ disabled = true +#=╠═╡ 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. -""" +#### Alternative -# ╔═╡ 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? +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!(...)`. """ + ╠═╡ =# -# ╔═╡ 3a1bf6da-b2e9-4c86-a83d-abe4de5a0483 +# ╔═╡ 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""" -- Answer: missing +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 @@ -325,12 +330,6 @@ 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`. @@ -343,6 +342,14 @@ Create a *reaction network object* for the exponential growth model. Name it `gr # @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""" @@ -351,6 +358,7 @@ Convert the system to a symbolic differential equation model (name it: `osys_exp # ╔═╡ 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""" @@ -359,6 +367,7 @@ 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""" @@ -371,10 +380,10 @@ For the sake of clarity, we will use the variables `μ_exp` and `Wf_exp` to stor """ # ╔═╡ 9a705b3f-3c45-4896-9b89-e9c37d47fd89 -# μ_exp = missing +μ_exp = 0.02 # ╔═╡ 55a13ebc-09e7-433c-83a8-9b6805e2fbec -# Wf_exp = missing +Wf_exp = 10.0 # ╔═╡ f1293675-b4d0-4827-8714-65a593ecc00e md""" @@ -383,6 +392,7 @@ 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""" @@ -390,7 +400,8 @@ 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 = missing # Uncomment and complete the instruction +oprob_exp = ODEProblem(growth_exp, u0_exp, tspan, params_exp) # ╔═╡ f58825d2-55fc-43f1-b164-9555bf9f5b84 md""" @@ -399,6 +410,7 @@ Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `os # ╔═╡ 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""" @@ -407,22 +419,27 @@ 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 (the values for which we want the sensitivity), and that returns the outputs. +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) - # μ, Wf = missing - # u0_exp = missing - # tspan = missing - # oprob_exp = missing; - # osol_exp = missing - # return missing +# 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""" @@ -431,6 +448,7 @@ 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""" @@ -439,6 +457,7 @@ 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""" @@ -447,14 +466,16 @@ 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 ouputt. +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""" @@ -463,9 +484,11 @@ 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""" @@ -474,9 +497,11 @@ 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""" @@ -485,6 +510,7 @@ 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" @@ -499,12 +525,6 @@ 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`. @@ -517,6 +537,14 @@ Create a *reaction network object* for the Gompertz growth model. Name it `growt # @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""" @@ -525,6 +553,7 @@ Convert the system to a symbolic differential equation model (name it: `osys_gom # ╔═╡ 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""" @@ -533,6 +562,7 @@ 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""" @@ -545,10 +575,10 @@ For the sake of clarity, we will use the variables `μ_gom` and `D_gom` to store """ # ╔═╡ edd76a4e-e541-4a83-923f-6f593d8fadad -# μ_gom = missing +μ_gom = 0.09 # ╔═╡ 05f1ae6c-1229-4924-9c41-9554d6ca4261 -# D_gom = missing +D_gom = 0.04 # ╔═╡ 70df05e7-4527-4087-a762-942a33e89f74 md""" @@ -557,6 +587,7 @@ 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""" @@ -564,7 +595,8 @@ 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 = missing # Uncomment and complete the instruction +oprob_gom = ODEProblem(growth_gom, u0_gom, tspan, params_gom) # ╔═╡ 30c35371-d106-4673-8569-d07b39edbdfe md""" @@ -573,6 +605,7 @@ Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `os # ╔═╡ 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""" @@ -581,22 +614,27 @@ 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 (the values for which we want the sensitivity), and that returns the outputs. +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) - # μ, D = missing - # u0_gom = missing - # tspan = missing - # oprob_gom = missing; - # osol_gom = missing - # return missing +# 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""" @@ -605,6 +643,7 @@ 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""" @@ -613,6 +652,7 @@ 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""" @@ -621,6 +661,7 @@ 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""" @@ -629,6 +670,7 @@ 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""" @@ -637,9 +679,11 @@ 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""" @@ -648,9 +692,11 @@ 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" @@ -659,6 +705,7 @@ 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" @@ -672,13 +719,12 @@ Draw your conclusions: # ╠═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 +# ╟─e76aedec-69f2-4301-b015-6960e4503c42 # ╟─a6a97279-0b64-45c3-8312-c22b1a8425d0 # ╟─25d3ef62-382c-455c-92bb-fadfc650c5a6 # ╟─2a544bef-d7fe-4300-bc59-69e6f7429304 @@ -713,7 +759,6 @@ Draw your conclusions: # ╠═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 @@ -727,12 +772,11 @@ Draw your conclusions: # ╟─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 -# ╟─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 @@ -769,9 +813,8 @@ Draw your conclusions: # ╠═7231e54e-b293-48fc-b1f0-273f45f51539 # ╟─0524a976-9d4d-4ef9-b088-bdd9102415e6 # ╠═33031b53-6f2d-4256-a0aa-0ba97beaa7af -# ╠═22b58c97-1aa2-49c4-8a0a-488c63014a90 +# ╟─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 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 old mode 100755 new mode 100644 index c23c9939..fcfc6b2b --- a/src/exercises/uncert_bitrophic_model.jl +++ b/src/exercises/uncert_bitrophic_model.jl @@ -2,12 +2,11 @@ # v0.20.4 #> [frontmatter] -#> order = "34" -#> title = "6. Uncertainty bithropic model" -#> date = "2025-08-06" +#> order = "41" +#> title = "7. Uncertainty bitrophic model" #> tags = ["exercises"] -#> description = "Uncertainty bithropic model" #> layout = "layout.jlhtml" +#> description = "Uncertainty bitrophic model" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,6 +15,8 @@ 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 diff --git a/src/exercises/uncert_fermenter_monod.jl b/src/exercises/uncert_fermenter_monod.jl old mode 100755 new mode 100644 index d47fe862..a18375a1 --- a/src/exercises/uncert_fermenter_monod.jl +++ b/src/exercises/uncert_fermenter_monod.jl @@ -2,12 +2,11 @@ # v0.20.4 #> [frontmatter] -#> order = "33" -#> title = "6. Uncertainty fermenter monod" -#> date = "2025-08-06" +#> order = "40" +#> title = "7. Uncertainty fermenter monod" #> tags = ["exercises"] -#> description = "Uncertainty fermenter monod" #> layout = "layout.jlhtml" +#> description = "Uncertainty fermenter monod" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,6 +15,8 @@ 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 diff --git a/src/exercises/uncert_intro.jl b/src/exercises/uncert_intro.jl old mode 100755 new mode 100644 index 54ed23f7..f676d283 --- a/src/exercises/uncert_intro.jl +++ b/src/exercises/uncert_intro.jl @@ -1,13 +1,12 @@ ### A Pluto.jl notebook ### -# v0.20.6 +# v0.20.21 #> [frontmatter] -#> order = "32" -#> title = "6. Uncertainty intro" -#> date = "2025-08-06" +#> order = "39" +#> title = "7. Uncertainty intro" #> tags = ["exercises"] -#> description = "Uncertainty intro" #> layout = "layout.jlhtml" +#> description = "Uncertainty intro" #> #> [[frontmatter.author]] #> name = "Gauthier Vanhaelewyn" @@ -16,40 +15,43 @@ 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 - -# ╔═╡ 47790920-74d2-4f21-b0f2-45dc19d25156 -using InteractiveUtils +using Markdown, InteractiveUtils # ╔═╡ 83f3d978-aefa-40c1-a647-b26e837aeed6 -using Catalyst - -# ╔═╡ 73d5f921-82e4-4160-a3c4-fc1935f4c58d -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 4260699d-4db2-4ac7-b8c8-7bc43fbc1c59 -using PlutoUI; TableOfContents() +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" +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" +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. -" +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" @@ -58,44 +60,49 @@ Uncertainty in model parameters manifests as variability in the predicted outcom # ╔═╡ 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. +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" +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: +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$ and $\mu$ parameters. The table below show some typical values for the parameters together with their uncertainties: +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$ | -|:----------- |:----------:|:-----------:| -| Logistic | 0.07$\pm$0.02 | 10.0$\pm$0.15 | -| Exponential | 0.02$\pm$0.01 | 10.0$\pm$0.15 | +| | $\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 model. +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` 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. +- 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. """ -# ╔═╡ 9d3192bc-ee69-44a1-9273-06b8a55c60ef +# ╔═╡ 87272d30-d95a-4b36-84ef-84bb9363c19e md""" -## Uncertainty analysis of the logistic growth model +### 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. +# ╔═╡ 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 @@ -105,6 +112,8 @@ 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 @@ -118,7 +127,7 @@ 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`: +In order to use uncertainties in the parameter values, we will need to load the package `Measurements` (see above). """ # ╔═╡ 1a67db96-6973-446d-aa5b-253dd0008a73 @@ -139,41 +148,35 @@ 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: +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. -""" +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$. -# ╔═╡ 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. +The last case study is the true case because, in reality, all uncertainties will contribute simultaneously. """ # ╔═╡ b4c76f21-9839-4db5-b952-43869d3a1efe md""" -### Study case 1 +### Case study 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**: +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 -params1_uncert_log = [:μ => 0.07±0.02, :Wf => 10.0] +parms1_uncert_log = [:μ => 0.07±0.02, :Wf => 10.0] # ╔═╡ c1a0702c-6fa5-47d7-bd72-6bfcbdeba93f -md""" +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); +oprob1_uncert_log = ODEProblem(growth_mod_log, u0_log, tspan, parms1_uncert_log) # ╔═╡ 5fa951d3-23ee-4510-873c-8158df4f2faf md""" @@ -192,22 +195,125 @@ Plot the results (simulation of the output variable $W$ and uncertainty band): 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""" -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. +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""" -### Study case 2 +### Case study 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**: +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 -params2_uncert_log = [:μ => 0.07, :Wf => 10.0±1.5] +parms2_uncert_log = [:μ => 0.07, :Wf => 10.0±1.5] # ╔═╡ 9e969176-f197-482f-9e2e-8b6d89de8f03 md""" @@ -215,7 +321,7 @@ 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); +oprob2_uncert_log = ODEProblem(growth_mod_log, u0_log, tspan, parms2_uncert_log) # ╔═╡ 6d69f64e-8e3a-46e5-bb2d-bfb6f55e129e md""" @@ -235,21 +341,21 @@ 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. +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""" -### Study case 3 -""" +md" +### Case study 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**: +We initialize a vector `parms_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] +parms_uncert_log = [:μ => 0.07±0.02, :Wf => 10.0±1.5] # ╔═╡ d48c378e-df1c-45f7-9c3a-c5e9640e25a5 md""" @@ -257,7 +363,7 @@ 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); +oprob_uncert_log = ODEProblem(growth_mod_log, u0_log, tspan, parms_uncert_log) # ╔═╡ de96646b-b752-4f72-ac78-2a503478bf50 md""" @@ -277,42 +383,50 @@ 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$. +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 - Uncertainty analysis of the exponential growth model +### 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""" +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 + @species W(t)=2.0 + @parameters μ Wf + μ*Wf, 0 --> W + μ, W --> 0 end # ╔═╡ 52991cca-0365-48fb-b157-62a71c5573ee -md""" +md" The vector `u0_exp` with the initial condition is: -""" +" # ╔═╡ f7241453-755e-466c-a963-a504bc9ea446 -# u0_exp = missing +u0_exp = [:W => 2.0] # ╔═╡ 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. +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 -# params_uncert_exp = missing # Uncomment and complete the instruction +# parms_uncert_exp = missing # ╔═╡ c25160d9-471a-414c-be99-10aa2efbb6d4 md""" @@ -320,15 +434,15 @@ 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 +# oprob_uncert_exp = missing # ╔═╡ bd120dbc-827a-499a-a11c-25423ec5f397 -md""" +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 +# osol_uncert_exp = missing # ╔═╡ 8059e00b-8e9c-4c11-a484-bea2a0bf155e md""" @@ -336,24 +450,91 @@ Plot the results (simulation of the output variable $W$ and uncertainty band): """ # ╔═╡ a89677f3-602b-4ef4-8f1a-ab203584f8df -# missing # Uncomment and complete the instruction +# 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`: """ -# ╔═╡ 1a3c8230-17ff-433e-9db5-807eb0dcb9a3 -md"- missing" +# ╔═╡ 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 -# ╠═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 +# ╠═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 @@ -361,19 +542,18 @@ md"- missing" # ╟─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 -# ╠═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 @@ -384,6 +564,28 @@ md"- missing" # ╟─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 @@ -404,6 +606,7 @@ md"- missing" # ╟─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 @@ -417,5 +620,18 @@ md"- missing" # ╠═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 +# ╠═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