|
| 1 | +# Generating ReactionSystems Programmatically |
| 2 | +This tutorial shows how to programmatically construct a `ReactionSystem` corresponding to the chemistry underlying the [Smoluchowski coagulation model](https://en.wikipedia.org/wiki/Smoluchowski_coagulation_equation) using [ModelingToolkit](https://mtk.sciml.ai/stable/)/[Catalyst](https://catalyst.sciml.ai/dev/). A jump process version of the model is then constructed from the `ReactionSystem`, and compared to the model's analytical solution obtained by the [method of Scott](https://journals.ametsoc.org/view/journals/atsc/25/1/1520-0469_1968_025_0054_asocdc_2_0_co_2.xml) (see also [3](https://doi.org/10.1006/jcph.2002.7017)). |
| 3 | + |
| 4 | +The Smoluchowski coagulation equation describes a system of reactions in which monomers may collide to form dimers, monomers and dimers may collide to form trimers, and so on. This models a variety of chemical/physical processes, including polymerization and flocculation. |
| 5 | + |
| 6 | +We begin by importing some necessary packages. |
| 7 | +```julia |
| 8 | +using ModelingToolkit, Catalyst, LinearAlgebra |
| 9 | +using DiffEqBase, DiffEqJump |
| 10 | +using Plots, SpecialFunctions |
| 11 | +``` |
| 12 | +Suppose the maximum cluster size is `N`. We assume an initial concentration of monomers, `Nₒ`, and let `uₒ` denote the initial number of monomers in the system. We have `nr` total reactions, and label by `V` the bulk volume of the system (which plays an important role in the calculation of rate laws since we have bimolecular reactions). Our basic parameters are then |
| 13 | +```julia |
| 14 | +## Parameter |
| 15 | +N = 10 # maximum cluster size |
| 16 | +Vₒ = (4π/3)*(10e-06*100)^3 # volume of a monomers in cm³ |
| 17 | +Nₒ = 1e-06/Vₒ # initial conc. = (No. of init. monomers) / bulk volume |
| 18 | +uₒ = 10000 # No. of monomers initially |
| 19 | +V = uₒ/Nₒ # Bulk volume of system in cm³ |
| 20 | + |
| 21 | +integ(x) = Int(floor(x)) |
| 22 | +n = integ(N/2) |
| 23 | +nr = N%2 == 0 ? (n*(n + 1) - n) : (n*(n + 1)) # No. of forward reactions |
| 24 | +``` |
| 25 | +The [Smoluchowski coagulation equation](https://en.wikipedia.org/wiki/Smoluchowski_coagulation_equation) Wikipedia page illustrates the set of possible reactions that can occur. We can easily enumerate the `pair`s of multimer reactants that can combine when allowing a maximal cluster size of `N` monomers. We initialize the volumes of the reactant multimers as `volᵢ` and `volⱼ` |
| 26 | + |
| 27 | +```julia |
| 28 | +# possible pairs of reactant multimers |
| 29 | +pair = [] |
| 30 | +for i = 2:N |
| 31 | + push!(pair,[1:integ(i/2) i .- (1:integ(i/2))]) |
| 32 | +end |
| 33 | +pair = vcat(pair...) |
| 34 | +vᵢ = @view pair[:,1] # Reactant 1 indices |
| 35 | +vⱼ = @view pair[:,2] # Reactant 2 indices |
| 36 | +volᵢ = Vₒ*vᵢ # cm⁻³ |
| 37 | +volⱼ = Vₒ*vⱼ # cm⁻³ |
| 38 | +sum_vᵢvⱼ = @. vᵢ + vⱼ # Product index |
| 39 | +``` |
| 40 | +We next specify the rates (i.e. kernel) at which reactants collide to form products. For simplicity, we allow a user-selected additive kernel or constant kernel. The constants(`B` and `C`) are adopted from Scott's paper [2](https://journals.ametsoc.org/view/journals/atsc/25/1/1520-0469_1968_025_0054_asocdc_2_0_co_2.xml) |
| 41 | +```julia |
| 42 | +# set i to 1 for additive kernel, 2 for constant |
| 43 | +i = 1 |
| 44 | +if i==1 |
| 45 | + B = 1.53e03 # s⁻¹ |
| 46 | + kv = @. B*(volᵢ + volⱼ)/V # dividing by volume as its a bi-molecular reaction chain |
| 47 | +elseif i==2 |
| 48 | + C = 1.84e-04 # cm³ s⁻¹ |
| 49 | + kv = fill(C/V, nr) |
| 50 | +end |
| 51 | +``` |
| 52 | +We'll store the reaction rates in `pars` as `Pair`s, and set the initial condition that only monomers are present at ``t=0`` in `u₀map`. |
| 53 | +```julia |
| 54 | +# state variables are X, pars stores rate parameters for each rx |
| 55 | +@parameters t |
| 56 | +@variables k[1:nr] X[collect(1:N)](t) |
| 57 | +pars = Pair.(k, kv) |
| 58 | + |
| 59 | +# time-span |
| 60 | +if i == 1 |
| 61 | + tspan = (0. ,2000.) |
| 62 | +elseif i == 2 |
| 63 | + tspan = (0. ,350.) |
| 64 | +end |
| 65 | + |
| 66 | + # initial condition of monomers |
| 67 | +u₀ = zeros(Int64, N) |
| 68 | +u₀[1] = uₒ |
| 69 | +u₀map = Pair.(X, u₀) # map variable to its initial value |
| 70 | +``` |
| 71 | +Here we generate the reactions programmatically. We systematically create Catalyst `Reaction`s for each possible reaction shown in the figure on [Wikipedia](https://en.wikipedia.org/wiki/Smoluchowski_coagulation_equation). When `vᵢ[n] == vⱼ[n]`, we set the stoichiometric coefficient of the reactant multimer to two. |
| 72 | +```julia |
| 73 | +# vector to store the Reactions in |
| 74 | +rx = [] |
| 75 | +for n = 1:nr |
| 76 | + # for clusters of the same size, double the rate |
| 77 | + if (vᵢ[n] == vⱼ[n]) |
| 78 | + push!(rx, Reaction(k[n], [X[vᵢ[n]]], [X[sum_vᵢvⱼ[n]]], [2], [1])) |
| 79 | + else |
| 80 | + push!(rx, Reaction(k[n], [X[vᵢ[n]], X[vⱼ[n]]], [X[sum_vᵢvⱼ[n]]], |
| 81 | + [1, 1], [1])) |
| 82 | + end |
| 83 | +end |
| 84 | +rs = ReactionSystem(rx, t, X, k) |
| 85 | +``` |
| 86 | +We now convert the `ReactionSystem` into a `JumpSystem`, and solve it using Gillespie's direct method. For details on other possible solvers (SSAs), see the [DifferentialEquations.jl](https://diffeq.sciml.ai/latest/types/jump_types/) documentation |
| 87 | +```julia |
| 88 | +# solving the system |
| 89 | +jumpsys = convert(JumpSystem, rs) |
| 90 | +dprob = DiscreteProblem(jumpsys, u₀map, tspan, pars) |
| 91 | +jprob = JumpProblem(jumpsys, dprob, Direct(), save_positions=(false,false)) |
| 92 | +jsol = solve(jprob, SSAStepper(), saveat = tspan[2]/30) |
| 93 | +``` |
| 94 | +Lets check the results for the first three polymers/cluster sizes. We compare to the analytical solution for this system: |
| 95 | +```julia |
| 96 | +# Results for first three polymers...i.e. monomers, dimers and trimers |
| 97 | +v_res = [1;2;3] |
| 98 | + |
| 99 | +# comparison with analytical solution |
| 100 | +# we only plot the stochastic solution at a small number of points |
| 101 | +# to ease distinguishing it from the exact solution |
| 102 | +t = jsol.t |
| 103 | +sol = zeros(length(v_res), length(t)) |
| 104 | +if i == 1 |
| 105 | + ϕ = @. 1 - exp(-B*Nₒ*Vₒ*t) |
| 106 | + for j in v_res |
| 107 | + sol[j,:] = @. Nₒ*(1 - ϕ)*(((j*ϕ)^(j-1))/gamma(j+1))*exp(-j*ϕ) |
| 108 | + end |
| 109 | +elseif i == 2 |
| 110 | + ϕ = @. (C*Nₒ*t) |
| 111 | + for j in v_res |
| 112 | + sol[j,:] = @. 4Nₒ*((ϕ^(j-1))/((ϕ + 2)^(j+1))) |
| 113 | + end |
| 114 | +end |
| 115 | + |
| 116 | +# plotting normalised concentration vs analytical solution |
| 117 | +default(lw=2, xlabel="Time (sec)") |
| 118 | +scatter(ϕ, jsol(t)[1,:]/uₒ, label="X1 (monomers)", markercolor=:blue) |
| 119 | +plot!(ϕ, sol[1,:]/Nₒ, line = (:dot,4,:blue), label="Analytical sol--X1") |
| 120 | + |
| 121 | +scatter!(ϕ, jsol(t)[2,:]/uₒ, label="X2 (dimers)", markercolor=:orange) |
| 122 | +plot!(ϕ, sol[2,:]/Nₒ, line = (:dot,4,:orange), label="Analytical sol--X2") |
| 123 | + |
| 124 | +scatter!(ϕ, jsol(t)[3,:]/uₒ, label="X3 (trimers)", markercolor=:purple) |
| 125 | +plot!(ϕ, sol[3,:]/Nₒ, line = (:dot,4,:purple), label="Analytical sol--X3", |
| 126 | + ylabel = "Normalized Concentration") |
| 127 | +``` |
| 128 | +For the **additive kernel** we find |
| 129 | + |
| 130 | + |
| 131 | + |
| 132 | +## Sources |
| 133 | +1. https://en.wikipedia.org/wiki/Smoluchowski_coagulation_equation |
| 134 | +2. Scott, W. T. (1968). Analytic Studies of Cloud Droplet Coalescence I, Journal of Atmospheric Sciences, 25(1), 54-65. Retrieved Feb 18, 2021, from https://journals.ametsoc.org/view/journals/atsc/25/1/1520-0469_1968_025_0054_asocdc_2_0_co_2.xml |
| 135 | +3. Ian J. Laurenzi, John D. Bartels, Scott L. Diamond, A General Algorithm for Exact Simulation of Multicomponent Aggregation Processes, Journal of Computational Physics, Volume 177, Issue 2, 2002, Pages 418-449, ISSN 0021-9991, https://doi.org/10.1006/jcph.2002.7017. |
0 commit comments