11``` @meta
2- EditURL = "<unknown> /../tutorials/stochastic_simulation/anderson.jl"
2+ EditURL = "../../.. /../tutorials/stochastic_simulation/anderson.jl"
33```
44
55# Simulating Stochastic Reaction Systems
@@ -217,7 +217,7 @@ Let us use parameters $\beta$ to represent the effective contact rate, and
217217$\gamma$ to represent the recovery rate.
218218
219219```` @example anderson
220- β = 0.05*10.0
220+ β = 0.05*10.0/1000
221221γ = 0.25
222222````
223223
@@ -228,7 +228,7 @@ to the rates given by the anonymous functions passed to `add_clock!`.
228228
229229```` @example anderson
230230rs = make_reactionsystem("SIR", [990, 10, 0])
231- add_clock!(rs, "infection", (x) -> β*x[2]/sum(x) *x[1], [-1,1,0])
231+ add_clock!(rs, "infection", (x) -> β*x[2]*x[1], [-1,1,0])
232232add_clock!(rs, "recovery", (x) -> γ*x[2], [0,-1,1])
233233````
234234
@@ -250,3 +250,140 @@ df_out = select(rs.df_output, Not(:clock));
250250plot(df_out[!,:time], Matrix(df_out[:,[:X1,:X2,:X3]]), label = ["S" "I" "R"])
251251````
252252
253+ ## Stochastic Petri Net
254+
255+ Stochastic Petri nets (SPN) are a mathematical language to describe distributed systems which evolve according
256+ to a stochastic trajectory. There are many ways to define them, and for a comprehensive overview of their modeling
257+ power, we reccomend [ Haas (2002)] ( https://link.springer.com/book/10.1007/b97265 ) . We will implement
258+ a very simple SPN to set up a state transition system. Our SPN is nearly identical to the category
259+ of Petri net proposed by [ Kock (2023)] ( https://arxiv.org/abs/2005.05108 ) , with the addition of a rate
260+ parameter associated with each transition. When we assume that overall transition rates occur according to the
261+ mass action law multiplied by the rate constant associated with that transition, we will be able to
262+ produce a ` ReactionSystem ` that can be simulated using the code above.
263+
264+ ```` @example anderson
265+ @aagent struct StochasticPetriNet
266+ P::Vector{Symbol}
267+ T::Vector{Symbol}
268+ I::Int
269+ O::Int
270+
271+ ip::Vector{Symbol}
272+ it::Vector{Symbol}
273+ op::Vector{Symbol}
274+ ot::Vector{Symbol}
275+
276+ rate::Dict{Symbol,Float64}
277+ end
278+ ````
279+
280+ The ` StochasticPetriNet ` has objects corresponding to (Sets) of places, transitions, input and output arcs. There
281+ are mappings (Functions) which indicate which place or transition each input (output) arc is connected to. For example
282+ ` ip ` is of length ` I ` , such that each input arc identifies which place is is connected to (likewise for ` it ` , but for
283+ transitions). Instead of arc multiplicites, we duplicate arcs, which has the same effect, and simplifies the code.
284+
285+ We write a helper function to construct SPNs. It is only responsible for checking our input makes sense.
286+
287+ ```` @example anderson
288+ function make_stochasticpetrinet(name, P, T, I, O, ip, it, op, ot, rate)
289+ @assert length(T) == length(rate)
290+ @assert all([p ∈ P for p in ip])
291+ @assert all([t ∈ T for t in it])
292+ @assert all([p ∈ P for p in op])
293+ @assert all([t ∈ T for t in ot])
294+ @assert I == length(ip)
295+ @assert I == length(it)
296+ @assert O == length(op)
297+ @assert O == length(ot)
298+ StochasticPetriNet(name, P, T, I, O, ip, it, op, ot, rate)
299+ end
300+ ````
301+
302+ The structural components of the SIR model are all in the SPN generated below. Note that there are two output arcs
303+ from the "infection" transition, to the "I" compartment. This is the same as having a single arc of multiplicity 2,
304+ we model arcs "individually" here only to make the code cleaner and more readable.
305+
306+ ```` @example anderson
307+ sir_spn = make_stochasticpetrinet(
308+ "SIR", [:S,:I,:R], [:inf,:rec],
309+ 3, 3,
310+ [:S,:I,:I], [:inf,:inf,:rec],
311+ [:I,:I,:R], [:inf,:inf,:rec],
312+ Dict((:inf => β), (:rec => γ))
313+ )
314+ ````
315+
316+ Now we can write a function which generates a ` ReactionSystem ` from our SPN, assuming the law of mass action.
317+ The argument ` X0 ` is the initial marking.
318+
319+ Note that in this simple example, we do not check the logical "enabling rules" for each transition, we directly
320+ compute the current rate/intensity. Because the net assumes the law of mass action, the computed rate will
321+ equal zero when the transition is not enabled, but this is not true of more general SPNs. A complete implementation
322+ would compute enabling rules from input arcs, and require the user to specify the rate as a ` Function ` that computed
323+ the intensity of that transition if the enabling rule for that transition evaluated to ` true ` . We would also
324+ want to apply the "consumption" of input tokens and the "production" of output tokens seperately, rather
325+ than compute the difference of consumption and production as the overall difference, as done here.
326+
327+ ```` @example anderson
328+ function generate_reaction_system(spn::StochasticPetriNet, X0)
329+
330+ mass_action_rs = make_reactionsystem(getname(spn), X0)
331+
332+ # for each transition, we must make a stochastic clock in the reaction system
333+ for t in spn.T
334+ # get the vector of preconditions (number of times each place is an input for this transition)
335+ precond = zeros(Int, length(spn.P))
336+ # get a vector of input indices
337+ precond_ix = Int[]
338+ for i in eachindex(spn.it)
339+ if spn.it[i] != t
340+ continue
341+ else
342+ push!(precond_ix, findfirst(isequal(spn.ip[i]), spn.P))
343+ precond[precond_ix[end]] += 1
344+ end
345+ end
346+ # get the vector of postconditions (number of times each places is an output for this transition)
347+ postcond = zeros(Int, length(spn.P))
348+ for i in eachindex(spn.ot)
349+ if spn.ot[i] != t
350+ continue
351+ else
352+ postcond[findfirst(isequal(spn.op[i]), spn.P)] += 1
353+ end
354+ end
355+ # total change to the marking as a result of transition t
356+ change = postcond - precond
357+ # add a stochastic clock to the reaction system for transition t
358+ add_clock!(
359+ mass_action_rs, String(t), (x) -> prod(x[precond_ix])*spn.rate[t], change
360+ )
361+ end
362+
363+ return mass_action_rs
364+ end
365+ ````
366+
367+ Now we can generate the reaction system which implements the stochastic dynamics of the SIR model from
368+ the Petri net representing the structural constraints of the SIR model. In this way, we seperate specification
369+ of structure from specification of dynamics. We use the same initial condition as before.
370+
371+ ```` @example anderson
372+ x0 = [990, 10, 0]
373+ sir_rs = generate_reaction_system(sir_spn, x0)
374+ ````
375+
376+ We now run another simulation.
377+
378+ ```` @example anderson
379+ simulate(sir_rs, floatmax(Float64))
380+ ````
381+
382+ We can make another plot. Although the parameters are the same, the stochastic trajectory should look a little different,
383+ due to the randomness in the two driving Poisson processes.
384+
385+ ```` @example anderson
386+ df_out = select(sir_rs.df_output, Not(:clock));
387+ plot(df_out[!,:time], Matrix(df_out[:,[:X1,:X2,:X3]]), label = ["S" "I" "R"])
388+ ````
389+
0 commit comments