Skip to content

Commit eaec868

Browse files
authored
Merge pull request #678 from SciML/improve_noise_scaling_implementation
[v14 ready] Improve noise scaling implementation
2 parents 7b8c789 + b4560e8 commit eaec868

File tree

11 files changed

+380
-196
lines changed

11 files changed

+380
-196
lines changed

HISTORY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ rn = @reaction_network begin
2222
@parameters η
2323
k, 2X --> X2, [noise_scaling=η]
2424
end
25-
getnoisescaling(rn)
25+
get_noise_scaling(rn)
2626
```
27+
- `SDEProblem` no longer takes the `noise_scaling` argument (see above for new approach to handle noise scaling).
2728
- Changed fields of internal `Reaction` structure. `ReactionSystems`s saved using `serialize` on previous Catalyst versions cannot be loaded using this (or later) versions.
2829
- Simulation of spatial ODEs now supported. For full details, please see https://github.com/SciML/Catalyst.jl/pull/644 and upcoming documentation. Note that these methods are currently considered alpha, with the interface and approach changing even in non-breaking Catalyst releases.
2930
- LatticeReactionSystem structure represents a spatial reaction network:

docs/src/catalyst_applications/advanced_simulations.md

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,9 @@ plot(sol)
292292

293293
## Scaling the noise magnitude in the chemical Langevin equations
294294
When using the CLE to generate SDEs from a CRN, it can sometimes be desirable to
295-
scale the magnitude of the noise terms. This can be done by introducing a *noise
296-
scaling parameter*. First, we simulate a simple two-state CRN model using the
295+
scale the magnitude of the noise terms. Here, each reaction of the system generates a separate noise term in the CLE. If you require identical scaling for all reactions, the `@default_noise_scaling` option can be used. Else, you can supply a `noise_scaling` metadata for each individual reaction, describing how to scale the noise for that reaction.
296+
297+
We begin with considering the first approach. First, we simulate a simple two-state CRN model using the
297298
CLE:
298299
```@example ex3
299300
using Catalyst, StochasticDiffEq, Plots
@@ -306,58 +307,61 @@ tspan = (0.0, 10.0)
306307
p_1 = [:k1 => 1.0, :k2 => 1.0]
307308
308309
sprob_1 = SDEProblem(rn_1, u0, tspan, p_1)
309-
sol_1 = solve(sprob_1)
310+
sol_1 = solve(sprob_1, ImplicitEM())
310311
plot(sol_1; idxs = :X1, ylimit = (0.0, 20.0))
311312
```
312-
Here we can see that the `X` concentration fluctuates around a steady state of $X≈10.0$.
313-
314-
Next, we wish to introduce a noise scaling parameter ,`η`. This will scale the
315-
noise magnitude so that for $η=0.0$ the system lacks noise (and its SDE
316-
simulations are identical to its ODE simulations) and for $η=1.0$ noise is not
317-
scaled (and SDE simulations are identical to as if no noise scaling was used).
318-
Setting $η<1.0$ will reduce noise and $η>1.0$ will increase noise. The syntax
319-
for setting a noise scaling parameter `η` is
313+
Here we can see that the $X$ concentration fluctuates around a steady state of $X≈10.0$.
314+
315+
Next, we wish increase the amount of noise by a factor 2. To do so, we use the `@default_noise_scaling` option, to which we provide the desired scaling
320316
```@example ex3
321317
rn_2 = @reaction_network begin
322-
@parameters η
318+
@default_noise_scaling 2
323319
(k1,k2), X1 <--> X2
324320
end
325-
u0 = [:X1 => 10.0, :X2 => 10.0]
326-
tspan = (0.0, 10.0)
327-
p_2 = [:k1 => 1.0, :k2 => 1.0, :η => 0.1]
328-
329-
sprob_2 = SDEProblem(rn_2, u0, tspan, p_2; noise_scaling = (@parameters η)[1])
330321
```
331-
Here, we first need to add `η` as a parameter to the system using the
332-
`@parameters η` option. Next, we pass the `noise_scaling = (@parameters η)[1]`
333-
argument to the `SDEProblem`. We can now simulate our system and confirm that
334-
noise is reduced:
322+
If we re-simulate the system we see that the amount of noise have increased:
335323
```@example ex3
336-
sol_2 = solve(sprob_2)
337-
plot(sol_2; idxs = :X1, ylimit = (0.0, 20.0))
324+
sprob_1 = SDEProblem(rn_2, u0, tspan, p_1)
325+
sol_1 = solve(sprob_1, ImplicitEM())
326+
plot(sol_1; idxs = :X1, ylimit = (0.0, 20.0))
338327
```
339328

340-
Finally, it is possible to set individual noise scaling parameters for each
341-
reaction of the system. Our model has two reactions (`X1 --> X2` and `X2 -->
342-
X1`) so we will use two noise scaling parameters, `η1` and `η2`. We use the
343-
following syntax:
329+
It is possible to scale the amount of noise using any expression. A common use of this is to set a parameter which determines the amount of noise. Here we create a parameter $η$, and uses its value to scale the noise.
344330
```@example ex3
331+
using Catalyst, StochasticDiffEq, Plots
332+
345333
rn_3 = @reaction_network begin
346-
@parameters η1 η2
334+
@parameters η
335+
@default_noise_scaling η
347336
(k1,k2), X1 <--> X2
348337
end
349338
u0 = [:X1 => 10.0, :X2 => 10.0]
350339
tspan = (0.0, 10.0)
351-
p_3 = [:k1 => 1.0, :k2 => 1.0, :η1 => 0.1, :η2 => 1.0]
340+
p_3 = [:k1 => 1.0, :k2 => 1.0, :η => 0.2]
352341
353-
sprob_3 = SDEProblem(rn_3, u0, tspan, p_3; noise_scaling = @parameters η1 η2)
342+
sprob_3 = SDEProblem(rn_3, u0, tspan, p_3)
343+
sol_3 = solve(sprob_3, ImplicitEM())
344+
plot(sol_3; idxs = :X1, ylimit = (0.0, 20.0))
354345
```
355-
plotting the results, we see that we have less fluctuation than for the first
356-
simulation, but more as compared to the second one (which is as expected):
346+
Here we saw how, by setting a small $η$ value, the amount of noise was reduced.
347+
348+
It is possible to use a different noise scaling expression for each reaction. Here, each reaction's noise scaling expression is provided using the `noise_scaling` metadata. In the following example, we use this to tune the noise of for both reactions involving the species $Y$.
349+
357350
```@example ex3
358-
sol_3 = solve(sprob_3)
359-
plot(sol_3; idxs = :X1, ylimit = (0.0, 20.0))
351+
rn_4 = @reaction_network begin
352+
(p, d), 0 <--> X
353+
(p, d), 0 <--> Y, ([noise_scaling=0.0, noise_scaling=0.0])
354+
end
355+
356+
u0_4 = [:X => 10.0, :Y => 10.0]
357+
tspan = (0.0, 10.0)
358+
p_4 = [p => 10.0, d => 1.]
359+
360+
sprob_4 = SDEProblem(rn_4, u0_4, tspan, p_4)
361+
sol_4 = solve(sprob_4, ImplicitEM())
362+
plot(sol_4; ylimit = (0.0, 20.0))
360363
```
364+
Here, we not that there is n fluctuation in the value of $Y$. If the `@default_noise_scaling` option is used, its value is used for all reactions for which the `noise_scaling` metadata is unused. If `@default_noise_scaling` is not used, the default noise scaling value is `1.0` (i.e. no scaling).
361365

362366
## Useful plotting options
363367
Catalyst, just like DifferentialEquations, uses the Plots package for all

src/Catalyst.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export make_empty_network, addspecies!, addparam!, addreaction!, reactionparamsm
9191
export dependants, dependents, substoichmat, prodstoichmat, netstoichmat
9292
export conservationlaws, conservedquantities, conservedequations, conservationlaw_constants
9393
export isequivalent
94+
export set_default_noise_scaling, get_noise_scaling, has_noise_scaling
9495

9596
# depreciated functions to remove in future releases
9697
export params, numparams

src/expression_utils.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,19 @@ function is_escaped_expr(expr)
9393
return (expr isa Expr) && (expr.head == :escape) && (length(expr.args) == 1)
9494
end
9595

96+
97+
### Generic Expression Handling ###
98+
99+
# Convert an expression that is a vector with symbols that have values assigned using `=`
100+
# (e.g. :([a=1.0, b=2.0])) to a vector where the assignment instead uses symbols and pairs
101+
# (e.g. :([a=>1.0, b=>2.0])). Used to e.g. convert default reaction metadata to a form that can be
102+
# evaluated as actual code.
103+
function expr_equal_vector_to_pairs(expr_vec)
104+
pair_vector = :([])
105+
foreach(arg -> push!(pair_vector.args, arg.args[1] => arg.args[2]), expr_vec.args)
106+
return pair_vector
107+
end
108+
96109
### Old Stuff ###
97110

98111
#This will be called whenever a function stored in funcdict is called.

src/reaction_network.jl

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ const forbidden_variables_error = let
123123
end
124124

125125
# Declares the keys used for various options.
126-
const option_keys = (:species, :parameters, :variables, :ivs, :compounds, :observables)
126+
const option_keys = (:species, :parameters, :variables, :ivs, :compounds, :observables, :default_noise_scaling)
127127

128128
### The @species macro, basically a copy of the @variables macro. ###
129129
macro species(ex...)
@@ -361,7 +361,10 @@ function make_reaction_system(ex::Expr; name = :(gensym(:ReactionSystem)))
361361
option_lines))
362362

363363
# Reads options.
364+
default_reaction_metadata = :([])
364365
compound_expr, compound_species = read_compound_options(options)
366+
check_default_noise_scaling!(default_reaction_metadata, options)
367+
default_reaction_metadata = expr_equal_vector_to_pairs(default_reaction_metadata)
365368

366369
# Parses reactions, species, and parameters.
367370
reactions = get_reactions(reaction_lines)
@@ -422,9 +425,12 @@ function make_reaction_system(ex::Expr; name = :(gensym(:ReactionSystem)))
422425
$observed_vars
423426
$comps
424427

425-
Catalyst.make_ReactionSystem_internal($rxexprs, $tiv, setdiff(union($spssym, $varssym, $compssym), $obs_syms),
426-
$pssym; name = $name, spatial_ivs = $sivs,
427-
observed = $observed_eqs)
428+
Catalyst.remake_ReactionSystem_internal(
429+
Catalyst.make_ReactionSystem_internal(
430+
$rxexprs, $tiv, setdiff(union($spssym, $varssym, $compssym), $obs_syms),
431+
$pssym; name = $name, spatial_ivs = $sivs, observed = $observed_eqs);
432+
default_reaction_metadata = $default_reaction_metadata
433+
)
428434
end
429435
end
430436

@@ -637,7 +643,6 @@ end
637643

638644
# Takes a reaction line and creates reaction(s) from it and pushes those to the reaction array.
639645
# Used to create multiple reactions from, for instance, `k, (X,Y) --> 0`.
640-
# Handles metadata, e.g. `1.0, Z --> 0, [noisescaling=η]`.
641646
function push_reactions!(reactions::Vector{ReactionStruct}, sub_line::ExprValues, prod_line::ExprValues,
642647
rate::ExprValues, metadata::ExprValues, arrow::Symbol)
643648
# The rates, substrates, products, and metadata may be in a tupple form (e.g. `k, (X,Y) --> 0`).
@@ -651,7 +656,7 @@ function push_reactions!(reactions::Vector{ReactionStruct}, sub_line::ExprValues
651656
for i in 1:maximum(lengs)
652657
# If the `only_use_rate` metadata was not provided, this has to be infered from the arrow used.
653658
metadata_i = get_tup_arg(metadata, i)
654-
if all(arg.args[1] != :only_use_rate for arg in metadata_i.args)
659+
if all(arg -> arg.args[1] != :only_use_rate, metadata_i.args)
655660
push!(metadata_i.args, :(only_use_rate = $(in(arrow, pure_rate_arrows))))
656661
end
657662

@@ -798,6 +803,17 @@ function make_observed_eqs(observables_expr)
798803
end
799804
end
800805

806+
# Checks if the `@default_noise_scaling` option is used. If so, read its input and adds it as a
807+
# default metadata value to the `default_reaction_metadata` vector.
808+
function check_default_noise_scaling!(default_reaction_metadata, options)
809+
if haskey(options, :default_noise_scaling)
810+
if (length(options[:default_noise_scaling].args) != 3) # Becasue of how expressions are, 3 is used.
811+
error("@default_noise_scaling should only have a single input, this appears not to be the case: \"$(options[:default_noise_scaling])\"")
812+
end
813+
push!(default_reaction_metadata.args, :(noise_scaling=$(options[:default_noise_scaling].args[3])))
814+
end
815+
end
816+
801817
### Functionality for expanding function call to custom and specific functions ###
802818

803819
#Recursively traverses an expression and replaces special function call like "hill(...)" with the actual corresponding expression.

0 commit comments

Comments
 (0)