From 45a8b7a2550f1ed386aa51c304fce4fbc07db120 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 20 Apr 2024 14:12:38 -0400 Subject: [PATCH 001/446] save progress --- test/meta/mtk_problem_inputs.jl | 182 ++++++++++++++++++ test/network_analysis/conservation_laws.jl | 22 +++ .../reactionsystem.jl | 62 ++++++ 3 files changed, 266 insertions(+) diff --git a/test/meta/mtk_problem_inputs.jl b/test/meta/mtk_problem_inputs.jl index c1f86ca900..154db865c3 100644 --- a/test/meta/mtk_problem_inputs.jl +++ b/test/meta/mtk_problem_inputs.jl @@ -158,6 +158,188 @@ let end end +### Vector Species/Parameters Tests ### + +begin + # Declares the model (with vector species/parameters, with/without default values, and observables). + @species X(t)[1:2] Y(t)[1:2] = [10.0, 20.0] XY(t)[1:2] + @parameters p[1:2] d[1:2] = [0.2, 0.5] + rxs = [ + Reaction(p[1], [], [X[1]]), + Reaction(p[2], [], [X[2]]), + Reaction(d[1], [X[1]], []), + Reaction(d[2], [X[2]], []), + Reaction(p[1], [], [Y[1]]), + Reaction(p[2], [], [Y[2]]), + Reaction(d[1], [Y[1]], []), + Reaction(d[2], [Y[2]], []) + ] + observed = [XY[1] ~ X[1] + Y[1], XY[2] ~ X[2] + Y[2]] + @named model_vec = ReactionSystem(rxs, t; observed) + model_vec = complete(model_vec) + + # Declares various u0 versions. + u0_alts_vec = [ + # Vectors not providing default values. + [X => [1.0, 2.0]], + [X[1] => 1.0, X[2] => 2.0], + [model_vec.X => [1.0, 2.0]], + [model_vec.X[1] => 1.0, model_vec.X[2] => 2.0], + [:X => [1.0, 2.0]], + # Vectors providing default values. + [X => [1.0, 2.0], Y => [10.0, 20.0]], + [X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0], + [model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]], + [model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0], + [:X => [1.0, 2.0], :Y => [10.0, 20.0]], + # Dicts not providing default values. + Dict([X => [1.0, 2.0]]), + Dict([X[1] => 1.0, X[2] => 2.0]), + Dict([model_vec.X => [1.0, 2.0]]), + Dict([model_vec.X[1] => 1.0, model_vec.X[2] => 2.0]), + Dict([:X => [1.0, 2.0]]), + # Dicts providing default values. + Dict([X => [1.0, 2.0], Y => [10.0, 20.0]]), + Dict([X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0]), + Dict([model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]]), + Dict([model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0]), + Dict([:X => [1.0, 2.0], :Y => [10.0, 20.0]]), + # Tuples not providing default values. + (X => [1.0, 2.0]), + (X[1] => 1.0, X[2] => 2.0), + (model_vec.X => [1.0, 2.0]), + (model_vec.X[1] => 1.0, model_vec.X[2] => 2.0), + (:X => [1.0, 2.0]), + # Tuples providing default values. + (X => [1.0, 2.0], Y => [10.0, 20.0]), + (X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0), + (model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]), + (model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0), + (:X => [1.0, 2.0], :Y => [10.0, 20.0]), + ] + + # Declares various ps versions. + p_alts_vec = [ + # Vectors not providing default values. + [p => [1.0, 2.0]], + [p[1] => 1.0, p[2] => 2.0], + [model_vec.p => [1.0, 2.0]], + [model_vec.p[1] => 1.0, model_vec.p[2] => 2.0], + [:p => [1.0, 2.0]], + # Vectors providing default values. + [p => [4.0, 5.0], d => [0.2, 0.5]], + [p[1] => 4.0, p[2] => 5.0, d[1] => 10.0, d[2] => 20.0], + [model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]], + [model_vec.p[1] => 4.0, model_vec.p[2] => 5.0, model_vec.d[1] => 10.0, model_vec.d[2] => 20.0], + [:p => [4.0, 5.0], :d => [0.2, 0.5]], + # Dicts not providing default values. + Dict([p => [1.0, 2.0]]), + Dict([p[1] => 1.0, p[2] => 2.0]), + Dict([model_vec.p => [1.0, 2.0]]), + Dict([model_vec.p[1] => 1.0, model_vec.p[2] => 2.0]), + Dict([:p => [1.0, 2.0]]), + # Dicts providing default values. + Dict([p => [4.0, 5.0], d => [0.2, 0.5]]), + Dict([p[1] => 4.0, p[2] => 5.0, d[1] => 10.0, d[2] => 20.0]), + Dict([model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]]), + Dict([model_vec.p[1] => 4.0, model_vec.p[2] => 5.0, model_vec.d[1] => 10.0, model_vec.d[2] => 20.0]), + Dict([:p => [4.0, 5.0], :d => [0.2, 0.5]]), + # Tuples not providing default values. + (p => [1.0, 2.0]), + (p[1] => 1.0, p[2] => 2.0), + (model_vec.p => [1.0, 2.0]), + (model_vec.p[1] => 1.0, model_vec.p[2] => 2.0), + (:p => [1.0, 2.0]), + # Tuples providing default values. + (p => [4.0, 5.0], d => [0.2, 0.5]), + (p[1] => 4.0, p[2] => 5.0, d[1] => 10.0, d[2] => 20.0), + (model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]), + (model_vec.p[1] => 4.0, model_vec.p[2] => 5.0, model_vec.d[1] => 10.0, model_vec.d[2] => 20.0), + (:p => [4.0, 5.0], :d => [0.2, 0.5]), + ] + + # Declares a timespan. + tspan = (0.0, 10.0) +end + +# Perform ODE simulations (singular and ensemble). +let + # Creates normal and ensemble problems. + base_oprob = ODEProblem(model_vec, u0_alts_vec[1], tspan, p_alts_vec[1]) + base_sol = solve(base_oprob, Tsit5(); saveat = 1.0) + base_eprob = EnsembleProblem(base_oprob) + base_esol = solve(base_eprob, Tsit5(); trajectories = 2, saveat = 1.0) + + # Simulates problems for all input types, checking that identical solutions are found. + for u0 in u0_alts_vec, p in p_alts_vec + oprob = remake(base_oprob; u0, p) + @test base_sol == solve(oprob, Tsit5(); saveat = 1.0) + eprob = remake(base_eprob; u0, p) + @test base_esol == solve(eprob, Tsit5(); trajectories = 2, saveat = 1.0) + end +end + +# Perform SDE simulations (singular and ensemble). +let + # Creates normal and ensemble problems. + base_sprob = SDEProblem(model, u0_alts_vec[1], tspan, p_alts_vec[1]) + base_sol = solve(base_sprob, ImplicitEM(); seed, saveat = 1.0) + base_eprob = EnsembleProblem(base_sprob) + base_esol = solve(base_eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) + + # Simulates problems for all input types, checking that identical solutions are found. + for u0 in u0_alts_vec, p in p_alts_vec + sprob = remake(base_sprob; u0, p) + @test base_sol == solve(sprob, ImplicitEM(); seed, saveat = 1.0) + eprob = remake(base_eprob; u0, p) + @test base_esol == solve(eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) + end +end + +# Perform jump simulations (singular and ensemble). +let + # Creates normal and ensemble problems. + base_dprob = DiscreteProblem(model, u0_alts_vec[1], tspan, p_alts_vec[1]) + base_jprob = JumpProblem(model, base_dprob, Direct(); rng) + base_sol = solve(base_jprob, SSAStepper(); seed, saveat = 1.0) + base_eprob = EnsembleProblem(base_jprob) + base_esol = solve(base_eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) + + # Simulates problems for all input types, checking that identical solutions are found. + for u0 in u0_alts_vec, p in p_alts_vec + jprob = remake(base_jprob; u0, p) + @test base_sol == solve(base_jprob, SSAStepper(); seed, saveat = 1.0) + eprob = remake(base_eprob; u0, p) + @test base_esol == solve(eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) + end +end + +# Solves a nonlinear problem (EnsembleProblems are not possible for these). +let + base_nlprob = NonlinearProblem(model, u0_alts_vec[1], p_alts_vec[1]) + base_sol = solve(base_nlprob, NewtonRaphson()) + for u0 in u0_alts_vec, p in p_alts_vec + nlprob = remake(base_nlprob; u0, p) + @test base_sol == solve(nlprob, NewtonRaphson()) + end +end + +# Perform steady state simulations (singular and ensemble). +let + # Creates normal and ensemble problems. + base_ssprob = SteadyStateProblem(model, u0_alts_vec[1], p_alts_vec[1]) + base_sol = solve(base_ssprob, DynamicSS(Tsit5())) + base_eprob = EnsembleProblem(base_ssprob) + base_esol = solve(base_eprob, DynamicSS(Tsit5()); trajectories = 2) + + # Simulates problems for all input types, checking that identical solutions are found. + for u0 in u0_alts_vec, p in p_alts_vec + ssprob = remake(base_ssprob; u0, p) + @test base_sol == solve(ssprob, DynamicSS(Tsit5())) + eprob = remake(base_eprob; u0, p) + @test base_esol == solve(eprob, DynamicSS(Tsit5()); trajectories = 2) + end +end ### Checks Errors On Faulty Inputs ### diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index 0a4ab1301a..f78702914c 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -73,3 +73,25 @@ let @test length(cons_laws_constants) == 2 @test count(isequal.(conserved_quantity, Num(0))) == 2 end + +# Conservation law simulations for vectorised species. +let + # Prepares the model. + t = default_t() + @species X(t)[1:2] + @parameters k[1:2] + rxs = [ + Reaction(k[1], [X[1]], [X[2]]), + Reaction(k[2], [X[2]], [X[1]]) + ] + @named rs = ReactionSystem(rxs, t) + rs = complete(rs) + + # Checks that simulation reaches known equilibrium + u0 = [:X => [3.0, 9.0]] + ps = [:k => [1.0, 2.0]] + oprob = ODEProblem(rs, u0, (0.0, 1000.0), ps; remove_conserved = true) + sol = solve(oprob, Vern7()) + @test sol[X[1]] ≈ 8.0 + @test sol[X[2]] ≈ 4.0 +end \ No newline at end of file diff --git a/test/reactionsystem_structure/reactionsystem.jl b/test/reactionsystem_structure/reactionsystem.jl index 9590a05364..396edd6f22 100644 --- a/test/reactionsystem_structure/reactionsystem.jl +++ b/test/reactionsystem_structure/reactionsystem.jl @@ -279,6 +279,68 @@ let end end +### Nich Model Declarations ### + +# Checks model with vector species and parameters. +# Checks that it works for programmatic/dsl-based modelling. +# Checks that all forms of model input (parameter/initial condition and vector/non-vector) are +# handled properly. +let + # Declares programmatic model. + @parameters p[1:2] k d1 d2 + @species (X(t))[1:2] Y1(t) Y2(t) + rxs = [ + Reaction(p[1], [], [X[1]]), + Reaction(p[2], [], [X[2]]), + Reaction(k, [X[1]], [Y1]), + Reaction(k, [X[2]], [Y2]), + Reaction(d1, [Y1], []), + Reaction(d2, [Y2], []), + ] + rs_prog = complete(ReactionSystem(rxs, t; name = :rs)) + + # Declares DSL-based model. + rs_dsl = @reaction_network rs begin + @parameters p[1:2] k d1 d2 + @species (X(t))[1:2] Y1(t) Y2(t) + (p[1],p[2]), 0 --> (X[1],X[2]) + k, (X[1],X[2]) --> (Y1,Y2) + (d1,d2), (Y1,Y2) --> 0 + end + + # Checks equivalence. + rs_dsl == rs_prog + + # Creates all possible initial conditions and parameter values. + u0_alts = [ + [X => [2.0, 5.0], Y1 => 0.2, Y2 => 0.5], + [X[1] => 2.0, X[2] => 5.0, Y1 => 0.2, Y2 => 0.5], + [rs_dsl.X => [2.0, 5.0], rs_dsl.Y1 => 0.2, rs_dsl.Y2 => 0.5], + [rs_dsl.X[1] => 2.0, X[2] => 5.0, rs_dsl.Y1 => 0.2, rs_dsl.Y2 => 0.5], + [:X => [2.0, 5.0], :Y1 => 0.2, :Y2 => 0.5] + ] + ps_alts = [ + [p => [1.0, 10.0], d1 => 5.0, d2 => 4.0, k => 2.0], + [p[1] => 1.0, p[2] => 10.0, d1 => 5.0, d2 => 4.0, k => 2.0], + [rs_dsl.p => [1.0, 10.0], rs_dsl.d1 => 5.0, rs_dsl.d2 => 4.0, rs_dsl.k => 2.0], + [rs_dsl.p[1] => 1.0, p[2] => 10.0, rs_dsl.d1 => 5.0, rs_dsl.d2 => 4.0, rs_dsl.k => 2.0], + [:p => [1.0, 10.0], :d1 => 5.0, :d2 => 4.0, :k => 2.0] + ] + + # Loops through all inputs and check that the correct steady state is reached + # Target steady state: (X1, X2, Y1, Y2) = (p1/k, p2/k, p1/d1, p2/d2). + # Technically only one model needs to be check. However, "equivalent" models in MTK can still + # have slight differences, so checking for both here to be certain. + for rs in [rs_prog, rs_dsl] + oprob = ODEProblem(rs, u0_alts[1], (0.0, 10000.), ps_alts[1]) + for rs in [rs_prog, rs_dsl], u0 in u0_alts, p in ps_alts + oprob_remade = remake(oprob; u0, p) + sol = solve(oprob_remade, Vern7(); abstol = 1e-8, reltol = 1e-8) + @test sol[end] ≈ [0.5, 5.0, 0.2, 2.5] + end + end +end + ### Other Tests ### # Test for https://github.com/SciML/ModelingToolkit.jl/issues/436. From b1bc2426133b70c36f40568a8b2a57e545c537d0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 27 May 2024 10:30:04 -0400 Subject: [PATCH 002/446] dummy_change --- test/dsl/dsl_basic_model_construction.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/dsl/dsl_basic_model_construction.jl b/test/dsl/dsl_basic_model_construction.jl index 72dd01f1ab..18a4d01750 100644 --- a/test/dsl/dsl_basic_model_construction.jl +++ b/test/dsl/dsl_basic_model_construction.jl @@ -437,3 +437,4 @@ let @test_throws LoadError @eval @reaction k, 0 --> im @test_throws LoadError @eval @reaction k, 0 --> nothing end + From b9731d827e383fa020b519352658b0de072134b3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 27 May 2024 10:52:04 -0400 Subject: [PATCH 003/446] rm DiffferentialEquations --- docs/Project.toml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 28d446f257..2b4976f3fe 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,7 +5,6 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DiffEqParamEstim = "1130ab10-4a5a-5621-a13d-e4788d82bd4c" -DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DynamicalSystems = "61744808-ddfa-5f27-97ff-6e42cc95d634" @@ -43,7 +42,6 @@ CairoMakie = "0.12" Catalyst = "13" DataFrames = "1" DiffEqParamEstim = "2.1" -DifferentialEquations = "7.7" Distributions = "0.25" Documenter = "1.4.1" DynamicalSystems = "3" @@ -57,10 +55,6 @@ ModelingToolkit = "9.5" NonlinearSolve = "3.4.0" Optim = "1" Optimization = "3.19" -OptimizationBBO = "0.1.5, 0.2" -OptimizationNLopt = "0.1.8" -OptimizationOptimJL = "0.1.14" -OptimizationOptimisers = "0.1.1" OrdinaryDiffEq = "6" Plots = "1.36" SciMLBase = "2.13" From 4506fdfd006aa58ca82924e6e3bbad017797905f Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 27 May 2024 18:28:05 -0400 Subject: [PATCH 004/446] small changes --- .../catalyst_for_new_julia_users.md | 15 ++++++++++----- .../ode_simulation_performance.md | 10 +++++----- test/dsl/dsl_basic_model_construction.jl | 1 + 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md index 82c297b33d..ef9924f34f 100644 --- a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md +++ b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md @@ -55,15 +55,15 @@ To import a Julia package into a session, you can use the `using PackageName` co using Pkg Pkg.add("Catalyst") ``` -Here, the Julia package manager package (`Pkg`) is by default installed on your computer when Julia is installed, and can be activated directly. Next, we also wish to install the `DifferentialEquations` and `Plots` packages (for numeric simulation of models, and plotting, respectively). +Here, the Julia package manager package (`Pkg`) is by default installed on your computer when Julia is installed, and can be activated directly. Next, we also wish to install the `OrdinaryDiffEq` and `Plots` packages (for numeric simulation of models, and plotting, respectively). ```julia -Pkg.add("DifferentialEquations") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` Once a package has been installed through the `Pkg.add` command, this command does not have to be repeated if we restart our Julia session. We can now import all three packages into our current session with: ```@example ex2 using Catalyst -using DifferentialEquations +using OrdinaryDiffEq using Plots ``` Here, if we restart Julia, these `using` commands *must be rerun*. @@ -130,7 +130,11 @@ For more information about the numerical simulation package, please see the [Dif ## Additional modelling example To make this introduction more comprehensive, we here provide another example, using a more complicated model. Instead of simulating our model as concentrations evolve over time, we will now simulate the individual reaction events through the [Gillespie algorithm](https://en.wikipedia.org/wiki/Gillespie_algorithm) (a common approach for adding *noise* to models). -Remember (unless we have restarted Julia) we do not need to activate our packages (through the `using` command) again. +Remember (unless we have restarted Julia) we do not need to activate our packages (through the `using` command) again. However, we do need to install, and then import, the JumpProcesses package (just to perform Gillespie, and other jump, simulations) +```julia +Pkg.add("JumpProcesses") +using JumpProcesses +``` This time, we will declare a so-called [SIR model for an infectious disease](https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SIR_model). Note that even if this model does not describe a set of chemical reactions, it can be modelled using the same framework. The model consists of 3 species: * $S$, the amount of *susceptible* individuals. @@ -164,12 +168,13 @@ nothing # hide Previously we have bundled this information into an `ODEProblem` (denoting a deterministic *ordinary differential equation*). Now we wish to simulate our model as a jump process (where each reaction event corresponds to a single jump in the state of the system). We do this by first creating a `DiscreteProblem`, and then using this as an input to a `JumpProblem`. ```@example ex2 +using JumpProcesses # hide dprob = DiscreteProblem(sir_model, u0, tspan, params) jprob = JumpProblem(sir_model, dprob, Direct()) ``` Again, the order in which the inputs are given to the `DiscreteProblem` and the `JumpProblem` is important. The last argument to the `JumpProblem` (`Direct()`) denotes which simulation method we wish to use. For now, we recommend that users simply use the `Direct()` option, and then consider alternative ones (see the [JumpProcesses.jl docs](https://docs.sciml.ai/JumpProcesses/stable/)) when they are more familiar with modelling in Catalyst and Julia. -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()`). This is a time-stepping algorithm that calls the `Direct` solver to advance a simulation. Again, we recommend at this stage you simply use this option, and then explore exactly what this means at a later stage. +Finally, we can simulate our model using the `solve` function, and plot the solution using the `plot` function. For jump simulations, the `solve` function also requires a second argument (`SSAStepper()`). This is a time-stepping algorithm that calls the `Direct` solver to advance a simulation. Again, we recommend at this stage you simply use this option, and then explore exactly what this means at a later stage. ```@example ex2 sol = solve(jprob, SSAStepper()) sol = solve(jprob, SSAStepper(); seed=1234) # hide diff --git a/docs/src/model_simulation/ode_simulation_performance.md b/docs/src/model_simulation/ode_simulation_performance.md index 78c0dfe8a6..86c50123d5 100644 --- a/docs/src/model_simulation/ode_simulation_performance.md +++ b/docs/src/model_simulation/ode_simulation_performance.md @@ -235,17 +235,17 @@ plot(0.01:0.01:1.0, map(sol -> sol[:P][end], esol.u), xguide = "kP", yguide = "P ``` Above, we have simply used `EnsembleProblem` as a convenient interface to run a large number of similar simulations. However, these problems have the advantage that they allow the passing of an *ensemble algorithm* to the `solve` command, which describes a strategy for parallelising the simulations. By default, `EnsembleThreads` is used. This parallelises the simulations using [multithreading](https://en.wikipedia.org/wiki/Multithreading_(computer_architecture)) (parallelisation within a single process), which is typically advantageous for small problems on shared memory devices. An alternative is `EnsembleDistributed` which instead parallelises the simulations using [multiprocessing](https://en.wikipedia.org/wiki/Multiprocessing) (parallelisation across multiple processes). To do this, we simply supply this additional solver to the solve command: -```@example ode_simulation_performance_4 +```julia esol = solve(eprob, Tsit5(), EnsembleDistributed(); trajectories=100) nothing # hide ``` To utilise multiple processes, you must first give Julia access to these. You can check how many processes are available using the `nprocs` (which requires the [Distributed.jl](https://github.com/JuliaLang/Distributed.jl) package): -```@example ode_simulation_performance_4 +```julia using Distributed nprocs() ``` Next, more processes can be added using `addprocs`. E.g. here we add an additional 4 processes: -```@example ode_simulation_performance_4 +```julia addprocs(4) nothing # hide ``` @@ -268,7 +268,7 @@ Furthermore (while not required) to receive good performance, we should also mak - We should designate all our vectors (i.e. initial conditions and parameter values) as [static vectors](https://github.com/JuliaArrays/StaticArrays.jl). We will assume that we are using the CUDA GPU hardware, so we will first load the [CUDA.jl](https://github.com/JuliaGPU/CUDA.jl) backend package, as well as DiffEqGPU: -```@example ode_simulation_performance_5 +```julia using CUDA, DiffEqGPU ``` Which backend package you should use depends on your available hardware, with the alternatives being listed [here](https://docs.sciml.ai/DiffEqGPU/stable/manual/backends/). @@ -306,7 +306,7 @@ We can now simulate our model using a GPU-based ensemble algorithm. Currently, t - While `EnsembleGPUArray` can use standard ODE solvers, `EnsembleGPUKernel` requires specialised versions (such as `GPUTsit5`). A list of available such solvers can be found [here](https://docs.sciml.ai/DiffEqGPU/dev/manual/ensemblegpukernel/#specialsolvers). Generally, it is recommended to use `EnsembleGPUArray` for large models (that have at least $100$ variables), and `EnsembleGPUKernel` for smaller ones. Here we simulate our model using both approaches (noting that `EnsembleGPUKernel` requires `GPUTsit5`): -```@example ode_simulation_performance_5 +```julia esol1 = solve(eprob, Tsit5(), EnsembleGPUArray(CUDA.CUDABackend()); trajectories = 10000) esol2 = solve(eprob, GPUTsit5(), EnsembleGPUKernel(CUDA.CUDABackend()); trajectories = 10000) nothing # hide diff --git a/test/dsl/dsl_basic_model_construction.jl b/test/dsl/dsl_basic_model_construction.jl index 18a4d01750..6c3d5466c3 100644 --- a/test/dsl/dsl_basic_model_construction.jl +++ b/test/dsl/dsl_basic_model_construction.jl @@ -438,3 +438,4 @@ let @test_throws LoadError @eval @reaction k, 0 --> nothing end + From b2a88cf68da52d5877af62a33d25482a95137849 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 27 May 2024 21:49:43 -0400 Subject: [PATCH 005/446] small update to check build times --- docs/src/inverse_problems/behaviour_optimisation.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/inverse_problems/behaviour_optimisation.md b/docs/src/inverse_problems/behaviour_optimisation.md index 41c1e9eecf..80080f8aa4 100644 --- a/docs/src/inverse_problems/behaviour_optimisation.md +++ b/docs/src/inverse_problems/behaviour_optimisation.md @@ -1,14 +1,14 @@ # [Optimization for non-data fitting purposes](@id behaviour_optimisation) In previous tutorials we have described how to use [PEtab.jl](@ref petab_parameter_fitting) and [Optimization.jl](@ref optimization_parameter_fitting) for parameter fitting. This involves solving an optimisation problem (to find the parameter set yielding the best model-to-data fit). There are, however, other situations that require solving optimisation problems. Typically, these involve the creation of a custom cost function, which optimum can then be found using Optimization.jl. In this tutorial we will describe this process, demonstrating how parameter space can be searched to find values that achieve a desired system behaviour. A more throughout description on how to solve these problems is provided by [Optimization.jl's documentation](https://docs.sciml.ai/Optimization/stable/) and the literature[^1]. -## Maximising the pulse amplitude of an incoherent feed forward loop. +## [Maximising the pulse amplitude of an incoherent feed forward loop](@id behaviour_optimisation_IFFL_example) Incoherent feedforward loops (network motifs where a single component both activates and deactivates a downstream component) are able to generate pulses in response to step inputs[^2]. In this tutorial we will consider such an incoherent feedforward loop, attempting to generate a system with as prominent a response pulse as possible. Our model consists of 3 species: $X$ (the input node), $Y$ (an intermediary), and $Z$ (the output node). In it, $X$ activates the production of both $Y$ and $Z$, with $Y$ also deactivating $Z$. When $X$ is activated, there will be a brief time window where $Y$ is still inactive, and $Z$ is activated. However, as $Y$ becomes active, it will turn $Z$ off. This creates a pulse of $Z$ activity. To trigger the system, we create [an event](@ref ref), which increases the production rate of $X$ ($pX$) by a factor of $10$ at time $t = 10$. ```@example behaviour_optimization using Catalyst incoherent_feed_forward = @reaction_network begin - @discrete_event [10.0] ~ [p ~ 10*p] + @discrete_events [10.0] => [pX ~ 10*pX] pX, 0 --> X pY*X, 0 --> Y pZ*X/Y, 0 --> Z @@ -34,13 +34,13 @@ function pulse_amplitude(p, _) ps = Dict([:pX => p[1], :pY => p[2], :pZ => p[2]]) u0_new = [:X => ps[:pX], :Y => ps[:pX]*ps[:pY], :Z => ps[:pZ]/ps[:pY]^2] oprob_local = remake(oprob; u0= u0_new, p = ps) - sol = solve(oprob_local, verbose = false, maxiters = 10000) + sol = solve(oprob_local, Tsit5(); verbose = false, maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return -(maximum(sol[:Z]) - sol[:Z][1]) end nothing # here ``` -This cost function takes two arguments (a parameter value `p`, and an additional one which we will ignore here but discuss later). It first calculates the new initial steady state concentration for the given parameter set. Next, it creates an updated `ODEProblem` using the steady state as initial conditions and the, to the cost function provided, input parameter set. While we could create a new `ODEProblem` within the cost function, cost functions are often called a large number of times during the optimisation process (making performance important). Here, using [`remake` on a previously created `ODEProblem`](@ref ref) is more performant than creating a new one. Just like [when using Optimization.jl to fit parameters to data](@ref optimization_parameter_fitting), we use the `verbose = false` option to prevent unnecessary simulation printouts, and a reduced `maxiters` value to reduce time spent simulating (for the model) unsuitable parameter sets. We also use `SciMLBase.successful_retcode(sol)` to check whether the simulation return code indicates a successful simulation (and if it did not, returns a large cost function value). Finally, Optimization.jl finds the function's *minimum value*, so to find the *maximum* relative pulse amplitude, we make our cost function return the negative pulse amplitude. +This cost function takes two arguments (a parameter value `p`, and an additional one which we will ignore here but discuss later). It first calculates the new initial steady state concentration for the given parameter set. Next, it creates an updated `ODEProblem` using the steady state as initial conditions and the, to the cost function provided, input parameter set. While we could create a new `ODEProblem` within the cost function, cost functions are often called a large number of times during the optimisation process (making performance important). Here, using [`remake` on a previously created `ODEProblem`](@ref simulation_structure_interfacing_problems_remake) is more performant than creating a new one. Just like [when using Optimization.jl to fit parameters to data](@ref optimization_parameter_fitting), we use the `verbose = false` option to prevent unnecessary simulation printouts, and a reduced `maxiters` value to reduce time spent simulating (for the model) unsuitable parameter sets. We also use `SciMLBase.successful_retcode(sol)` to check whether the simulation return code indicates a successful simulation (and if it did not, returns a large cost function value). Finally, Optimization.jl finds the function's *minimum value*, so to find the *maximum* relative pulse amplitude, we make our cost function return the negative pulse amplitude. Just like for [parameter fitting](@ref optimization_parameter_fitting), we create a `OptimizationProblem` using our cost function, and some initial guess of the parameter value. We also set upper and lower bounds for each parameter using the `lb` and `ub` optional arguments (in this case limiting each parameter's value to the interval $(0.1,10.0)$). ```@example behaviour_optimization @@ -61,7 +61,7 @@ Finally, we plot a simulation using the found parameter set (stored in `opt_sol. ps_res = Dict([:pX => opt_sol.u[1], :pY => opt_sol.u[2], :pZ => opt_sol.u[2]]) u0_res = [:X => ps_res[:pX], :Y => ps_res[:pX]*ps_res[:pY], :Z => ps_res[:pZ]/ps_res[:pY]^2] oprob_res = remake(oprob; u0 = u0_res, p = ps_res) -sol_res = solve(oprob_res) +sol_res = solve(oprob_res, Tsit5()) plot(sol_res; idxs=:Z) ``` For this model, it turns out that $Z$'s maximum pulse amplitude is equal to twice its steady state concentration. Hence, the maximisation of its pulse amplitude is equivalent to maximising its steady state concentration. @@ -71,7 +71,7 @@ For this model, it turns out that $Z$'s maximum pulse amplitude is equal to twic There are several modifications to our problem where it would actually have parameters. E.g. our model might have had additional parameters (e.g. a degradation rate) which we would like to keep fixed throughout the optimisation process. If we then would like to run the optimisation process for several different values of these fixed parameters, we could have made them parameters to our `OptimizationProblem` (and their values provided as a third argument, after `initial_guess`). -## Utilising automatic differentiation +## [Utilising automatic differentiation](@id behaviour_optimisation_AD) Optimisation methods can be divided into differentiation-free and differentiation-based optimisation methods. E.g. consider finding the minimum of the function $f(x) = x^2$, given some initial guess of $x$. Here, we can simply compute the differential and descend along it until we find $x=0$ (admittedly, for this simple problem the minimum can be computed directly). This principle forms the basis of optimisation methods such as [gradient descent](https://en.wikipedia.org/wiki/Gradient_descent), which utilises information of a function's differential to minimise it. When attempting to find a global minimum, to avoid getting stuck in local minimums, these methods are often augmented by additional routines. While the differentiation of most algebraic functions is trivial, it turns out that even complicated functions (such as the one we used above) can be differentiated computationally through the use of [*automatic differentiation* (AD)](https://en.wikipedia.org/wiki/Automatic_differentiation). Through packages such as [ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl), [ReverseDiff.jl](https://github.com/JuliaDiff/ReverseDiff.jl), and [Zygote.jl](https://github.com/FluxML/Zygote.jl), Julia supports AD for most code. Specifically for code including simulation of differential equations, differentiation is supported by [SciMLSensitivity.jl](https://github.com/SciML/SciMLSensitivity.jl). Generally, AD can be used without specific knowledge from the user, however, it requires an additional step in the construction of our `OptimizationProblem`. Here, we create a [specialised `OptimizationFunction` from our cost function](https://docs.sciml.ai/Optimization/stable/API/optimization_function/#optfunction). To it, we will also provide our choice of AD method. There are [several alternatives](https://docs.sciml.ai/Optimization/stable/API/optimization_function/#Automatic-Differentiation-Construction-Choice-Recommendations), and in our case we will use `AutoForwardDiff()` (a good choice for small optimisation problems). We can then create a new `OptimizationProblem` using our updated cost function: From 0aba5f92d0cc908c0a3b04723c65624f04b4425c Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 27 May 2024 23:00:51 -0400 Subject: [PATCH 006/446] project.toml update --- docs/Project.toml | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 2b4976f3fe..6225a0e2cb 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -28,7 +28,6 @@ Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" QuasiMonteCarlo = "8a4e6c94-4038-4cdc-81c3-7e6ffdb2a71b" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" -Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f" StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" @@ -36,32 +35,32 @@ StructuralIdentifiability = "220ca800-aa68-49bb-acd8-6037fa93a544" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [compat] -BenchmarkTools = "1" +BenchmarkTools = "1.5" BifurcationKit = "0.3" CairoMakie = "0.12" Catalyst = "13" -DataFrames = "1" -DiffEqParamEstim = "2.1" +DataFrames = "1.6" +DiffEqParamEstim = "2.2" Distributions = "0.25" Documenter = "1.4.1" -DynamicalSystems = "3" -GlobalSensitivity = "2.4.0" -HomotopyContinuation = "2.6" +DynamicalSystems = "3.3" +GlobalSensitivity = "2.6" +HomotopyContinuation = "2.9" IncompleteLU = "0.2" -JumpProcesses = "9" -Latexify = "0.15, 0.16" -LinearSolve = "2" -ModelingToolkit = "9.5" -NonlinearSolve = "3.4.0" -Optim = "1" -Optimization = "3.19" -OrdinaryDiffEq = "6" -Plots = "1.36" -SciMLBase = "2.13" -SciMLSensitivity = "7.19" -Setfield = "1.1" -SpecialFunctions = "2.1" -SteadyStateDiffEq = "2.0.1" -StochasticDiffEq = "6" -StructuralIdentifiability = "0.5.1" -Symbolics = "5.14" +JumpProcesses = "9.11" +Latexify = "0.16" +LinearSolve = "2.30" +ModelingToolkit = "9.15" +NonlinearSolve = "3.12" +Optim = "1.9" +Optimization = "3.25" +OrdinaryDiffEq = "6.80.1" +Plots = "1.40" +QuasiMonteCarlo = "0.3" +SciMLBase = "2.39" +SciMLSensitivity = "7.60" +SpecialFunctions = "2.4" +SteadyStateDiffEq = "2.2" +StochasticDiffEq = "6.65" +StructuralIdentifiability = "0.5.7" +Symbolics = "5.28" From c383597252c1dd5c48def9266cdf980ce9f0cf27 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 28 May 2024 12:12:44 -0400 Subject: [PATCH 007/446] up --- HISTORY.md | 2 +- docs/pages.jl | 16 +++--- docs/src/assets/Project.toml | 57 +++++++++++--------- src/Catalyst.jl | 1 - src/reaction.jl | 46 ++++++++-------- src/reactionsystem.jl | 2 +- src/reactionsystem_conversions.jl | 2 +- test/reactionsystem_core/reaction.jl | 24 ++++----- test/simulation_and_solving/simulate_SDEs.jl | 11 ++-- 9 files changed, 85 insertions(+), 76 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index d2c610a262..270d64b177 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -24,7 +24,7 @@ rn = @reaction_network begin @parameters η k, 2X --> X2, [noise_scaling=η] end -get_noise_scaling(rn) +getnoisescaling(rn) ``` - `SDEProblem` no longer takes the `noise_scaling` argument (see above for new approach to handle noise scaling). - Changed fields of internal `Reaction` structure. `ReactionSystems`s saved using `serialize` on previous Catalyst versions cannot be loaded using this (or later) versions. diff --git a/docs/pages.jl b/docs/pages.jl index 96e6587bf3..9cb614a846 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -8,11 +8,11 @@ pages = Any[ "Model Creation and Properties" => Any[ "model_creation/dsl_basics.md", "model_creation/dsl_advanced.md", - "model_creation/programmatic_CRN_construction.md", - "model_creation/compositional_modeling.md", - "model_creation/constraint_equations.md", + #"model_creation/programmatic_CRN_construction.md", + #"model_creation/compositional_modeling.md", + #"model_creation/constraint_equations.md", # Events. - "model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. + #"model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. # Loading and writing models to files. # Model visualisation. "model_creation/network_analysis.md", @@ -20,8 +20,8 @@ pages = Any[ "Model creation examples" => Any[ "model_creation/examples/basic_CRN_library.md", "model_creation/examples/programmatic_generative_linear_pathway.md", - "model_creation/examples/hodgkin_huxley_equation.md", - "model_creation/examples/smoluchowski_coagulation_equation.md" + #"model_creation/examples/hodgkin_huxley_equation.md", + #"model_creation/examples/smoluchowski_coagulation_equation.md" ] ], "Model simulation" => Any[ @@ -67,6 +67,6 @@ pages = Any[ # # Contributor's guide. # # Repository structure. # ], - "FAQs" => "faqs.md", - "API" => "api.md" + #"FAQs" => "faqs.md", + #"API" => "api.md" ] \ No newline at end of file diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index e7454091d2..6225a0e2cb 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -1,18 +1,25 @@ [deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" BifurcationKit = "0f109fa4-8a5d-4b75-95aa-f515264e7665" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DiffEqParamEstim = "1130ab10-4a5a-5621-a13d-e4788d82bd4c" -DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DynamicalSystems = "61744808-ddfa-5f27-97ff-6e42cc95d634" +GlobalSensitivity = "af5da776-676b-467e-8baf-acd8249e4f0f" HomotopyContinuation = "f213a82b-91d6-5c5d-acf7-10f1c761b327" +IncompleteLU = "40713840-3770-5561-ab4c-a76e7d0d7895" +JumpProcesses = "ccbc3e58-028d-4f4c-8cd5-9ae44345cda5" Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" +LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" Optim = "429524aa-4258-5aef-a3af-852621145aeb" Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba" +OptimizationBBO = "3e6eede4-6085-4f62-9a71-46d9bc1eb92b" OptimizationNLopt = "4e6fcdb7-1186-4e1f-a706-475e75c168bb" OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e" OptimizationOptimisers = "42dfb2eb-d2b4-4451-abcd-913932933ac1" @@ -21,7 +28,6 @@ Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" QuasiMonteCarlo = "8a4e6c94-4038-4cdc-81c3-7e6ffdb2a71b" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" -Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f" StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" @@ -29,29 +35,32 @@ StructuralIdentifiability = "220ca800-aa68-49bb-acd8-6037fa93a544" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [compat] +BenchmarkTools = "1.5" BifurcationKit = "0.3" +CairoMakie = "0.12" Catalyst = "13" -DataFrames = "1" -DiffEqParamEstim = "2.1" -DifferentialEquations = "7.7" +DataFrames = "1.6" +DiffEqParamEstim = "2.2" Distributions = "0.25" Documenter = "1.4.1" -HomotopyContinuation = "2.6" -Latexify = "0.15, 0.16" -ModelingToolkit = "9.5" -NonlinearSolve = "3.4.0" -Optim = "1" -Optimization = "3.19" -OptimizationNLopt = "0.1.8" -OptimizationOptimJL = "0.1.14" -OptimizationOptimisers = "0.1.1" -OrdinaryDiffEq = "6" -Plots = "1.36" -SciMLBase = "2.13" -SciMLSensitivity = "7.19" -Setfield = "1.1" -SpecialFunctions = "2.1" -SteadyStateDiffEq = "2.0.1" -StochasticDiffEq = "6" -StructuralIdentifiability = "0.5.1" -Symbolics = "5.14" +DynamicalSystems = "3.3" +GlobalSensitivity = "2.6" +HomotopyContinuation = "2.9" +IncompleteLU = "0.2" +JumpProcesses = "9.11" +Latexify = "0.16" +LinearSolve = "2.30" +ModelingToolkit = "9.15" +NonlinearSolve = "3.12" +Optim = "1.9" +Optimization = "3.25" +OrdinaryDiffEq = "6.80.1" +Plots = "1.40" +QuasiMonteCarlo = "0.3" +SciMLBase = "2.39" +SciMLSensitivity = "7.60" +SpecialFunctions = "2.4" +SteadyStateDiffEq = "2.2" +StochasticDiffEq = "6.65" +StructuralIdentifiability = "0.5.7" +Symbolics = "5.28" diff --git a/src/Catalyst.jl b/src/Catalyst.jl index d80c115119..8c9949f94a 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -93,7 +93,6 @@ end include("reaction.jl") export isspecies export Reaction -export get_noise_scaling, has_noise_scaling # The `ReactionSystem` structure and its functions. include("reactionsystem.jl") diff --git a/src/reaction.jl b/src/reaction.jl index 95eb7c0e71..1c4aee000e 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -377,8 +377,8 @@ function ModelingToolkit.get_variables!(set, rx::Reaction) for stoichs in (rx.substoich, rx.prodstoich), stoich in stoichs (stoich isa BasicSymbolic) && get_variables!(set, stoich) end - if has_noise_scaling(rx) - get_variables!(set, get_noise_scaling(rx)) + if hasnoisescaling(rx) + get_variables!(set, getnoisescaling(rx)) end return (set isa AbstractVector) ? unique!(set) : set end @@ -440,7 +440,7 @@ end ### Reaction Metadata Implementation ### -# These are currently considered internal, but can be used by public accessor functions like get_noise_scaling. +# These are currently considered internal, but can be used by public accessor functions like getnoisescaling. """ getmetadata_dict(reaction::Reaction) @@ -507,7 +507,7 @@ end # Noise scaling. """ -has_noise_scaling(reaction::Reaction) +hasnoisescaling(reaction::Reaction) Returns `true` if the input reaction has the `noise_scaing` metadata field assigned, else `false`. @@ -517,15 +517,15 @@ Arguments: Example: ```julia reaction = @reaction k, 0 --> X, [noise_scaling=0.0] -has_noise_scaling(reaction) +hasnoisescaling(reaction) ``` """ -function has_noise_scaling(reaction::Reaction) +function hasnoisescaling(reaction::Reaction) return hasmetadata(reaction, :noise_scaling) end """ -get_noise_scaling(reaction::Reaction) +getnoisescaling(reaction::Reaction) Returns `noise_scaing` metadata field for the input reaction. @@ -535,11 +535,11 @@ Arguments: Example: ```julia reaction = @reaction k, 0 --> X, [noise_scaling=0.0] -get_noise_scaling(reaction) +getnoisescaling(reaction) ``` """ -function get_noise_scaling(reaction::Reaction) - if has_noise_scaling(reaction) +function getnoisescaling(reaction::Reaction) + if hasnoisescaling(reaction) return getmetadata(reaction, :noise_scaling) else error("Attempts to access noise_scaling metadata field for a reaction which does not have a value assigned for this metadata.") @@ -548,7 +548,7 @@ end # Description. """ -has_description(reaction::Reaction) +hasdescription(reaction::Reaction) Returns `true` if the input reaction has the `description` metadata field assigned, else `false`. @@ -558,15 +558,15 @@ Arguments: Example: ```julia reaction = @reaction k, 0 --> X, [description="A reaction"] -has_description(reaction) +hasdescription(reaction) ``` """ -function has_description(reaction::Reaction) +function hasdescription(reaction::Reaction) return hasmetadata(reaction, :description) end """ -get_description(reaction::Reaction) +getdescription(reaction::Reaction) Returns `description` metadata field for the input reaction. @@ -576,11 +576,11 @@ Arguments: Example: ```julia reaction = @reaction k, 0 --> X, [description="A reaction"] -get_description(reaction) +getdescription(reaction) ``` """ -function get_description(reaction::Reaction) - if has_description(reaction) +function getdescription(reaction::Reaction) + if hasdescription(reaction) return getmetadata(reaction, :description) else error("Attempts to access `description` metadata field for a reaction which does not have a value assigned for this metadata.") @@ -589,7 +589,7 @@ end # Misc. """ -has_misc(reaction::Reaction) +hasmisc(reaction::Reaction) Returns `true` if the input reaction has the `misc` metadata field assigned, else `false`. @@ -599,15 +599,15 @@ Arguments: Example: ```julia reaction = @reaction k, 0 --> X, [misc="A reaction"] -misc(reaction) +hasmisc(reaction) ``` """ -function has_misc(reaction::Reaction) +function hasmisc(reaction::Reaction) return hasmetadata(reaction, :misc) end """ -get_misc(reaction::Reaction) +getmisc(reaction::Reaction) Returns `misc` metadata field for the input reaction. @@ -617,7 +617,7 @@ Arguments: Example: ```julia reaction = @reaction k, 0 --> X, [misc="A reaction"] -get_misc(reaction) +getmisc(reaction) ``` Notes: @@ -629,7 +629,7 @@ creating a `ReactionSystem` programmatically (in which case any symbolic variabl """ function get_misc(reaction::Reaction) - if has_misc(reaction) + if hasmisc(reaction) return getmetadata(reaction, :misc) else error("Attempts to access `misc` metadata field for a reaction which does not have a value assigned for this metadata.") diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 5c04b2e642..3be6b6c06c 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -497,7 +497,7 @@ function make_ReactionSystem_internal(rxs_and_eqs::Vector, iv, us_in, ps_in; spa end # Extract all quantities encountered in relevant `Reaction` metadata. - has_noise_scaling(rx) && findvars!(ps, us, get_noise_scaling(rx), ivs, vars) + hasnoisescaling(rx) && findvars!(ps, us, getnoisescaling(rx), ivs, vars) end # Extracts any species, variables, and parameters that occur in (non-reaction) equations. diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 38a634ff77..2a9688cd73 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -121,7 +121,7 @@ function assemble_diffusion(rs, sts, ispcs; combinatoric_ratelaws = true, for (j, rx) in enumerate(get_rxs(rs)) rlsqrt = sqrt(abs(oderatelaw(rx; combinatoric_ratelaw = combinatoric_ratelaws))) - has_noise_scaling(rx) && (rlsqrt *= get_noise_scaling(rx)) + hasnoisescaling(rx) && (rlsqrt *= getnoisescaling(rx)) remove_conserved && (rlsqrt = substitute(rlsqrt, depspec_submap)) for (spec, stoich) in rx.netstoich diff --git a/test/reactionsystem_core/reaction.jl b/test/reactionsystem_core/reaction.jl index eff2ab20b3..4760641c4b 100644 --- a/test/reactionsystem_core/reaction.jl +++ b/test/reactionsystem_core/reaction.jl @@ -116,10 +116,10 @@ let r1 = Reaction(k, [X], [X2], [2], [1]) r2 = Reaction(k, [X], [X2], [2], [1]; metadata=[:noise_scaling => η]) - @test !Catalyst.has_noise_scaling(r1) - @test Catalyst.has_noise_scaling(r2) - @test_throws Exception Catalyst.get_noise_scaling(r1) - @test isequal(Catalyst.get_noise_scaling(r2), η) + @test !Catalyst.hasnoisescaling(r1) + @test Catalyst.hasnoisescaling(r2) + @test_throws Exception Catalyst.getnoisescaling(r1) + @test isequal(Catalyst.getnoisescaling(r2), η) end # Tests the description metadata. @@ -131,10 +131,10 @@ let r1 = Reaction(k, [X], [X2], [2], [1]) r2 = Reaction(k, [X], [X2], [2], [1]; metadata=[:description => "A reaction"]) - @test !Catalyst.has_description(r1) - @test Catalyst.has_description(r2) - @test_throws Exception Catalyst.get_description(r1) - @test isequal(Catalyst.get_description(r2), "A reaction") + @test !Catalyst.hasdescription(r1) + @test Catalyst.hasdescription(r2) + @test_throws Exception Catalyst.getdescription(r1) + @test isequal(Catalyst.getdescription(r2), "A reaction") end # Tests the misc metadata. @@ -146,8 +146,8 @@ let r1 = Reaction(k, [X], [X2], [2], [1]) r2 = Reaction(k, [X], [X2], [2], [1]; metadata=[:misc => ('M', :M)]) - @test !Catalyst.has_misc(r1) - @test Catalyst.has_misc(r2) - @test_throws Exception Catalyst.get_misc(r1) - @test isequal(Catalyst.get_misc(r2), ('M', :M)) + @test !Catalyst.hasmisc(r1) + @test Catalyst.hasmisc(r2) + @test_throws Exception Catalyst.getmisc(r1) + @test isequal(Catalyst.getmisc(r2), ('M', :M)) end \ No newline at end of file diff --git a/test/simulation_and_solving/simulate_SDEs.jl b/test/simulation_and_solving/simulate_SDEs.jl index 5684db4132..e59a9bfc74 100644 --- a/test/simulation_and_solving/simulate_SDEs.jl +++ b/test/simulation_and_solving/simulate_SDEs.jl @@ -2,6 +2,7 @@ # Fetch packages. using Catalyst, Statistics, StochasticDiffEq, Test +using Catalyst: getnoisescaling # Sets stable rng number. using StableRNGs @@ -325,8 +326,8 @@ let @test issetequal([X, H], species(rs)) @test issetequal([X, H, h], unknowns(rs)) @test issetequal([p, d, η], parameters(rs)) - @test isequal(get_noise_scaling(reactions(rs)[1]), η*H + 1) - @test isequal(get_noise_scaling(reactions(rs)[2]), h) + @test isequal(getnoisescaling(reactions(rs)[1]), η*H + 1) + @test isequal(getnoisescaling(reactions(rs)[2]), h) end # Tests the `remake_noise_scaling` function. @@ -369,9 +370,9 @@ let # Checks that systems have the correct noise scaling terms. rn = set_default_noise_scaling(rn, 0.5) - rn1_noise_scaling = [get_noise_scaling(rx) for rx in Catalyst.get_rxs(rn)] - rn2_noise_scaling = [get_noise_scaling(rx) for rx in Catalyst.get_rxs(Catalyst.get_systems(rn)[1])] - rn_noise_scaling = [get_noise_scaling(rx) for rx in reactions(rn)] + rn1_noise_scaling = [getnoisescaling(rx) for rx in Catalyst.get_rxs(rn)] + rn2_noise_scaling = [getnoisescaling(rx) for rx in Catalyst.get_rxs(Catalyst.get_systems(rn)[1])] + rn_noise_scaling = [getnoisescaling(rx) for rx in reactions(rn)] @test issetequal(rn1_noise_scaling, [2.0, 0.5]) @test issetequal(rn2_noise_scaling, [5.0, 0.5]) @test issetequal(rn_noise_scaling, [2.0, 0.5, 5.0, 0.5]) From b0a00f8740d8fb800aed6900669fe2db755232b2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 28 May 2024 12:23:32 -0400 Subject: [PATCH 008/446] fix --- src/reaction.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reaction.jl b/src/reaction.jl index 1c4aee000e..23538214aa 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -628,7 +628,7 @@ creating a `ReactionSystem` programmatically (in which case any symbolic variabl `misc` metadata field should also be explicitly provided to the `ReactionSystem` constructor). """ -function get_misc(reaction::Reaction) +function getmisc(reaction::Reaction) if hasmisc(reaction) return getmetadata(reaction, :misc) else From 3b91b28b870bdaedbd660b5d7c647cb288c3e174 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 28 May 2024 15:23:09 -0400 Subject: [PATCH 009/446] up --- docs/Project.toml | 6 ++++ docs/pages.jl | 2 +- docs/src/api.md | 3 -- .../global_sensitivity_analysis.md | 4 +-- .../optimization_ode_param_fitting.md | 28 +++++++++---------- .../dynamical_systems.md | 2 +- .../homotopy_continuation.md | 2 +- .../nonlinear_solve.md | 13 +++++---- .../steady_state_stability_computation.md | 6 ++-- 9 files changed, 35 insertions(+), 31 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 6225a0e2cb..246727a23e 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -29,6 +29,7 @@ QuasiMonteCarlo = "8a4e6c94-4038-4cdc-81c3-7e6ffdb2a71b" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f" StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" StructuralIdentifiability = "220ca800-aa68-49bb-acd8-6037fa93a544" @@ -54,12 +55,17 @@ ModelingToolkit = "9.15" NonlinearSolve = "3.12" Optim = "1.9" Optimization = "3.25" +OptimizationBBO = "0.2.1" +OptimizationNLopt = "0.2.1" +OptimizationOptimJL = "0.3.1" +OptimizationOptimisers = "0.2.1" OrdinaryDiffEq = "6.80.1" Plots = "1.40" QuasiMonteCarlo = "0.3" SciMLBase = "2.39" SciMLSensitivity = "7.60" SpecialFunctions = "2.4" +StaticArrays = "1.9" SteadyStateDiffEq = "2.2" StochasticDiffEq = "6.65" StructuralIdentifiability = "0.5.7" diff --git a/docs/pages.jl b/docs/pages.jl index 9cb614a846..a161f4fd00 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -2,7 +2,7 @@ pages = Any[ "Home" => "home.md", "Introduction to Catalyst" => Any[ "introduction_to_catalyst/catalyst_for_new_julia_users.md", - "introduction_to_catalyst/introduction_to_catalyst.md" + # "introduction_to_catalyst/introduction_to_catalyst.md" # Advanced introduction. ], "Model Creation and Properties" => Any[ diff --git a/docs/src/api.md b/docs/src/api.md index 49b74adaa1..9e7086e6f8 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -152,16 +152,13 @@ accessor functions. ```@docs species nonspecies -reactionsystemparams reactions nonreactions numspecies numparams numreactions -numreactionsystemparams speciesmap paramsmap -reactionsystemparamsmap isspecies isautonomous Catalyst.isconstant diff --git a/docs/src/inverse_problems/global_sensitivity_analysis.md b/docs/src/inverse_problems/global_sensitivity_analysis.md index d611997a9f..ee20b6a802 100644 --- a/docs/src/inverse_problems/global_sensitivity_analysis.md +++ b/docs/src/inverse_problems/global_sensitivity_analysis.md @@ -11,7 +11,7 @@ A related concept to global sensitivity is *local sensitivity*. This, rather tha While local sensitivities are primarily used as a subroutine of other methodologies (such as optimisation schemes), it also has direct uses. E.g., in the context of fitting parameters to data, local sensitivity analysis can be used to, at the parameter set of the optimal fit, [determine the cost function's sensitivity to the system parameters](@ref ref). ## [Basic example](@id global_sensitivity_analysis_basic_example) -We will consider a simple [SEIR model of an infectious disease](https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology). This is an expansion of the classic [SIR model](@ref ref) with an additional *exposed* state, $E$, denoting individuals who are latently infected but currently unable to transmit their infection to others. +We will consider a simple [SEIR model of an infectious disease](https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology). This is an expansion of the classic [SIR model](@ref basic_CRN_library_sir) with an additional *exposed* state, $E$, denoting individuals who are latently infected but currently unable to transmit their infection to others. ```@example gsa_1 using Catalyst seir_model = @reaction_network begin @@ -53,7 +53,7 @@ on the domain $10^β ∈ (-3.0,-1.0)$, $10^a ∈ (-2.0,0.0)$, $10^γ ∈ (-2.0,0 !!! note We should make a couple of notes about the example above: - Here, we write our parameters on the forms $10^β$, $10^a$, and $10^γ$, which transforms them into log-space. As [previously described](@ref optimization_parameter_fitting_logarithmic_scale), this is advantageous in the context of inverse problems such as this one. - - For GSA, where a function is evaluated a large number of times, it is ideal to write it as performant as possible. Hence, we initially create a base `ODEProblem`, and then apply the [`remake`](@ref ref) function to it in each evaluation of `peak_cases` to generate a problem which is solved for that specific parameter set. + - For GSA, where a function is evaluated a large number of times, it is ideal to write it as performant as possible. Hence, we initially create a base `ODEProblem`, and then apply the [`remake`](@ref simulation_structure_interfacing_problems_remake) function to it in each evaluation of `peak_cases` to generate a problem which is solved for that specific parameter set. - Again, as [previously described in other inverse problem tutorials](@ref optimization_parameter_fitting_basics), when exploring a function over large parameter spaces, we will likely simulate our model for unsuitable parameter sets. To reduce time spent on these, and to avoid excessive warning messages, we provide the `maxiters = 100000` and `verbose = false` arguments to `solve`. - As we have encountered in [a few other cases](@ref optimization_parameter_fitting_basics), the `gsa` function is not able to take parameter inputs of the map form usually used for Catalyst. Hence, as a first step in `peak_cases` we convert the parameter vector to this form. Next, we remember that the order of the parameters when we e.g. evaluate the GSA output, or set the parameter bounds, corresponds to the order used in `ps = [:β => p[1], :a => p[2], :γ => p[3]]`. diff --git a/docs/src/inverse_problems/optimization_ode_param_fitting.md b/docs/src/inverse_problems/optimization_ode_param_fitting.md index c579a68aa7..742ed58ee0 100644 --- a/docs/src/inverse_problems/optimization_ode_param_fitting.md +++ b/docs/src/inverse_problems/optimization_ode_param_fitting.md @@ -1,11 +1,11 @@ -# [Parameter Fitting for ODEs using SciML/Optimization.jl and DiffEqParamEstim.jl](@id optimization_parameter_fitting) +# [Parameter Fitting for ODEs using Optimization.jl and DiffEqParamEstim.jl](@id optimization_parameter_fitting) Fitting parameters to data involves solving an optimisation problem (that is, finding the parameter set that optimally fits your model to your data, typically by minimising a cost function). The SciML ecosystem's primary package for solving optimisation problems is [Optimization.jl](https://github.com/SciML/Optimization.jl). It provides access to a variety of solvers via a single common interface by wrapping a large number of optimisation libraries that have been implemented in Julia. -This tutorial demonstrates both how to create parameter fitting cost functions using the [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl) package, and how to use Optimization.jl to minimise these. Optimization.jl can also be used in other contexts, such as [finding parameter sets that maximise the magnitude of some system behaviour](@ref ref). More details on how to use these packages can be found in their [respective](https://docs.sciml.ai/Optimization/stable/) [documentations](https://docs.sciml.ai/DiffEqParamEstim/stable/). +This tutorial demonstrates both how to create parameter fitting cost functions using the [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl) package, and how to use Optimization.jl to minimise these. Optimization.jl can also be used in other contexts, such as [finding parameter sets that maximise the magnitude of some system behaviour](@ref behaviour_optimisation). More details on how to use these packages can be found in their [respective](https://docs.sciml.ai/Optimization/stable/) [documentations](https://docs.sciml.ai/DiffEqParamEstim/stable/). ## [Basic example](@id optimization_parameter_fitting_basics) -Let us consider a [Michaelis-Menten enzyme kinetics model](@ref ref), where an enzyme ($E$) converts a substrate ($S$) into a product ($P$): +Let us consider a [Michaelis-Menten enzyme kinetics model](@ref basic_CRN_library_mm), where an enzyme ($E$) converts a substrate ($S$) into a product ($P$): ```@example diffeq_param_estim_1 using Catalyst rn = @reaction_network begin @@ -30,8 +30,8 @@ data_vals = (0.8 .+ 0.4*rand(10)) .* data_sol[:P][2:end] # Plots the true solutions and the (synthetic) data measurements. using Plots -plot(true_sol; idxs=:P, label="True solution", lw=8) -plot!(data_ts, data_vals; label="Measurements", seriestype=:scatter, ms=6, color=:blue) +plot(true_sol; idxs = :P, label = "True solution", lw = 8) +plot!(data_ts, data_vals; label = "Measurements", seriestype=:scatter, ms = 6, color = :blue) ``` Next, we will use DiffEqParamEstim to build a loss function to measure how well our model's solutions fit the data. @@ -45,7 +45,7 @@ nothing # hide ``` To `build_loss_objective` we provide the following arguments: - `oprob`: The `ODEProblem` with which we simulate our model (using some dummy parameter values, since we do not know these). -- `Tsit5()`: The [numeric integrator](@ref ref) we wish to simulate our model with. +- `Tsit5()`: The [numeric solver](@ref simulation_intro_solver_options) we wish to simulate our model with. - `L2Loss(data_ts, data_vals)`: Defines the loss function. While [other alternatives](https://docs.sciml.ai/DiffEqParamEstim/stable/getting_started/#Alternative-Cost-Functions-for-Increased-Robustness) are available, `L2Loss` is the simplest one (measuring the sum of squared distances between model simulations and data measurements). Its first argument is the time points at which the data is collected, and the second is the data's values. - `Optimization.AutoForwardDiff()`: Our choice of [automatic differentiation](https://en.wikipedia.org/wiki/Automatic_differentiation) framework. @@ -63,27 +63,27 @@ nothing # hide !!! note `OptimizationProblem` cannot currently accept parameter values in the form of a map (e.g. `[:kB => 1.0, :kD => 1.0, :kP => 1.0]`). These must be provided as individual values (using the same order as the parameters occur in in the `parameters(rs)` vector). Similarly, `build_loss_objective`'s `save_idxs` uses the species' indexes, rather than the species directly. These inconsistencies should be remedied in future DiffEqParamEstim releases. -Finally, we can optimise `optprob` to find the parameter set that best fits our data. Optimization.jl only provides a few optimisation methods natively. However, for each supported optimisation package, it provides a corresponding wrapper-package to import that optimisation package for use with Optimization.jl. E.g., if we wish to use [Optim.jl](https://github.com/JuliaNLSolvers/Optim.jl)'s [Nelder-Mead](https://en.wikipedia.org/wiki/Nelder%E2%80%93Mead_method) method, we must install and import the OptimizationOptimJL package. A summary of all, by Optimization.jl supported, optimisation packages can be found [here](https://docs.sciml.ai/Optimization/stable/#Overview-of-the-Optimizers). Here, we import the Optim.jl package and uses it to minimise our cost function (thus finding a parameter set that fits the data): +Finally, we can optimise `optprob` to find the parameter set that best fits our data. Optimization.jl only provides a few optimisation methods natively. However, for each supported optimisation package, it provides a corresponding wrapper-package to import that optimisation package for use with Optimization.jl. E.g., if we wish to use [NLopt.jl](https://github.com/JuliaOpt/NLopt.jl)'s [Nelder-Mead](https://en.wikipedia.org/wiki/Nelder%E2%80%93Mead_method) method, we must install and import the OptimizationNLopt package. A summary of all, by Optimization.jl supported, optimisation packages can be found [here](https://docs.sciml.ai/Optimization/stable/#Overview-of-the-Optimizers). Here, we import the NLopt.jl package and uses it to minimise our cost function (thus finding a parameter set that fits the data): ```@example diffeq_param_estim_1 -using OptimizationOptimJL -optsol = solve(optprob, Optim.NelderMead()) +using OptimizationNLopt +optsol = solve(optprob, NLopt.LN_NELDERMEAD()) nothing # hide ``` We can now simulate our model for the corresponding parameter set, checking that it fits our data. ```@example diffeq_param_estim_1 -oprob_fitted = remake(oprob; p=optsol.u) +oprob_fitted = remake(oprob; p = optsol.u) fitted_sol = solve(oprob_fitted, Tsit5()) -plot!(fitted_sol; idxs=:P, label="Fitted solution", linestyle=:dash, lw=6, color=:lightblue) +plot!(fitted_sol; idxs = :P, label = "Fitted solution", linestyle = :dash, lw = 6, color = :lightblue) ``` !!! note Here, a good exercise is to check the resulting parameter set and note that, while it creates a good fit to the data, it does not actually correspond to the original parameter set. [Identifiability](@ref structural_identifiability) is a concept that studies how to deal with this problem. -Say that we instead would like to use the [Broyden–Fletcher–Goldfarb–Shannon](https://en.wikipedia.org/wiki/Broyden%E2%80%93Fletcher%E2%80%93Goldfarb%E2%80%93Shanno_algorithm) algorithm, as implemented by the [NLopt.jl](https://github.com/JuliaOpt/NLopt.jl) package. In this case we would run: +Say that we instead would like to use the [Broyden–Fletcher–Goldfarb–Shannon](https://en.wikipedia.org/wiki/Broyden%E2%80%93Fletcher%E2%80%93Goldfarb%E2%80%93Shanno_algorithm) algorithm, as implemented by the [Optim.jl](https://github.com/JuliaNLSolvers/Optim.jl) package. In this case we would run: ```@example diffeq_param_estim_1 -using OptimizationNLopt -sol = solve(optprob, NLopt.LD_LBFGS()) +using OptimizationOptimJL +sol = solve(optprob, Optim.LBFGS()) nothing # hide ``` diff --git a/docs/src/steady_state_functionality/dynamical_systems.md b/docs/src/steady_state_functionality/dynamical_systems.md index fcd56129e0..e818449963 100644 --- a/docs/src/steady_state_functionality/dynamical_systems.md +++ b/docs/src/steady_state_functionality/dynamical_systems.md @@ -54,7 +54,7 @@ More information on how to compute basins of attractions for ODEs using Dynamica While Lyapunov exponents can be used for other purposes, they are primarily used to characterise [*chaotic behaviours*](https://en.wikipedia.org/wiki/Chaos_theory) (where small changes in initial conditions has large effect on the resulting trajectories). Generally, an ODE exhibit chaotic behaviour if its attractor(s) have *at least one* positive Lyapunov exponent. Practically, Lyapunov exponents can be computed using DynamicalSystems.jl's `lyapunovspectrum` function. Here we will use it to investigate two models, one which exhibits chaos and one which do not. -First, let us consider the [Willamowski–Rössler model](@ref ref), which is known to exhibit chaotic behaviour. +First, let us consider the [Willamowski–Rössler model](@ref basic_CRN_library_wr), which is known to exhibit chaotic behaviour. ```@example dynamical_systems_lyapunov using Catalyst wr_model = @reaction_network begin diff --git a/docs/src/steady_state_functionality/homotopy_continuation.md b/docs/src/steady_state_functionality/homotopy_continuation.md index 19a15d39e6..ee81f88121 100644 --- a/docs/src/steady_state_functionality/homotopy_continuation.md +++ b/docs/src/steady_state_functionality/homotopy_continuation.md @@ -12,7 +12,7 @@ integer Hill exponents). The roots of these can reliably be found through a Catalyst contains a special homotopy continuation extension that is loaded whenever HomotopyContinuation.jl is. This exports a single function, `hc_steady_states`, that can be used to find the steady states of any `ReactionSystem` structure. -For this tutorial, we will use the [Wilhelm model](@ref ref) (which +For this tutorial, we will use the [Wilhelm model](@ref basic_CRN_library_wilhelm) (which demonstrates bistability in a small chemical reaction network). We declare the model and the parameter set for which we want to find the steady states: ```@example hc_basics diff --git a/docs/src/steady_state_functionality/nonlinear_solve.md b/docs/src/steady_state_functionality/nonlinear_solve.md index 601fd51c93..55d994d56a 100644 --- a/docs/src/steady_state_functionality/nonlinear_solve.md +++ b/docs/src/steady_state_functionality/nonlinear_solve.md @@ -8,9 +8,9 @@ While these approaches only find a single steady state, they offer two advantage - They are typically much faster. - They can find steady states for models that do not produce multivariate, rational, polynomial systems (which is a requirement for homotopy continuation to work). Examples include models with non-integer hill coefficients. -In practice, model steady states are found through [nonlinear system solving](@ref steady_state_solving_nonlinear) by creating a `NonlinearProblem`, and through [forward ODE simulation](@ref ref) by creating a `SteadyStateProblem`. These are then solved through solvers implemented in the [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl), package (with the latter approach also requiring the [SteadyStateDiffEq.jl](https://github.com/SciML/SteadyStateDiffEq.jl) package). This tutorial describes how to find steady states through these two approaches. More extensive descriptions of available solvers and options can be found in [NonlinearSolve's documentation](https://docs.sciml.ai/NonlinearSolve/stable/). +In practice, model steady states are found through [nonlinear system solving](@ref steady_state_solving_nonlinear) by creating a `NonlinearProblem`, and through forward ODE simulation by creating a `SteadyStateProblem`. These are then solved through solvers implemented in the [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl), package (with the latter approach also requiring the [SteadyStateDiffEq.jl](https://github.com/SciML/SteadyStateDiffEq.jl) package). This tutorial describes how to find steady states through these two approaches. More extensive descriptions of available solvers and options can be found in [NonlinearSolve's documentation](https://docs.sciml.ai/NonlinearSolve/stable/). -## [Steady state finding through nonlinear solving](@ref steady_state_solving_nonlinear) +## [Steady state finding through nonlinear solving](@id steady_state_solving_nonlinear) Let us consider a simple dimerisation system, where a protein ($P$) can exist in a monomer and a dimer form. The protein is produced at a constant rate from its mRNA, which is also produced at a constant rate. ```@example steady_state_solving_nonlinear using Catalyst @@ -57,7 +57,7 @@ sol ≈ sol_ntr ``` ### [Systems with conservation laws](@id steady_state_solving_nonlinear_conservation_laws) -As described in the section on homotopy continuation, when finding the steady states of systems with conservation laws, [additional considerations have to be taken](homotopy_continuation_conservation_laws). E.g. consider the following [two-state system](@ref ref): +As described in the section on homotopy continuation, when finding the steady states of systems with conservation laws, [additional considerations have to be taken](homotopy_continuation_conservation_laws). E.g. consider the following [two-state system](@ref basic_CRN_library_two_states): ```@example steady_state_solving_claws using Catalyst, NonlinearSolve # hide two_state_model = @reaction_network begin @@ -85,6 +85,7 @@ sol[[:X1, :X2]] ## [Finding steady states through ODE simulations](@id steady_state_solving_simulation) The `NonlinearProblem`s generated by Catalyst corresponds to ODEs. A common method of solving these is to simulate the ODE from an initial condition until a steady state is reached. Here we do so for the dimerisation system considered in the previous section. First, we declare our model, initial condition, and parameter values. ```@example steady_state_solving_simulation +using Catalyst # hide dimer_production = @reaction_network begin pₘ, 0 --> mRNA pₚ, mRNA --> mRNA + P @@ -106,13 +107,13 @@ solve(ssprob) ``` Note that, unlike for nonlinear system solving, `u0` is not just an initial guess of the solution, but the initial conditions from which the steady state simulation is carried out. This means that, for a system with multiple steady states, we can determine the steady states associated with specific initial conditions (which is not possible when the nonlinear solving approach is used). This also permits us to easily [handle the presence of conservation laws](@ref steady_state_solving_nonlinear_conservation_laws). The forward ODE simulation approach (unlike homotopy continuation and nonlinear solving) cannot find unstable steady states. -The forward ODE solving approach uses the ODE solvers implemented by the [OrdinaryDiffEq.jl](@ref ref) package. If this package is loaded, it is possible to designate a specific solver to use. Any available ODE solver can be used, however, it has to be encapsulated by the `DynamicSS()` function. E.g. here we designate the `Rodas5P` solver: +The forward ODE solving approach uses the ODE solvers implemented by the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package. If this package is loaded, it is possible to designate a specific solver to use. Any available ODE solver can be used, however, it has to be encapsulated by the `DynamicSS()` function. E.g. here we designate the `Rodas5P` solver: ```@example steady_state_solving_simulation using OrdinaryDiffEqDiffEq solve(ssprob, DynamicSS(Rodas5P())) nothing # hide ``` -Generally, `SteadyStateProblem`s can be solved using the [same options that are available for ODE simulations](@ref ref). E.g. here we designate a specific `dt` step size: +Generally, `SteadyStateProblem`s can be solved using the [same options that are available for ODE simulations](@ref simulation_intro_solver_options). E.g. here we designate a specific `dt` step size: ```@example steady_state_solving_simulation solve(ssprob, DynamicSS(Rodas5P()); dt = 0.01) nothing # hide @@ -146,4 +147,4 @@ If you use this functionality in your research, [in addition to Catalyst](@ref c --- ## References -[^1]: [J. Nocedal, S. J. Wright, *Numerical Optimization*, Springer (2006).](https://www.math.uci.edu/~qnie/Publications/NumericalOptimization.pdf) +[^1]: [J. Nocedal, S. J. Wright, *Numerical Optimization*, Springer (2006).](https://www.math.uci.edu/~qnie/Publications/NumericalOptimization.pdf) \ No newline at end of file diff --git a/docs/src/steady_state_functionality/steady_state_stability_computation.md b/docs/src/steady_state_functionality/steady_state_stability_computation.md index 0cc60ebf94..8e43dcfe28 100644 --- a/docs/src/steady_state_functionality/steady_state_stability_computation.md +++ b/docs/src/steady_state_functionality/steady_state_stability_computation.md @@ -19,7 +19,7 @@ steady_state = [:X => 4.0] steady_state_stability(steady_state, rn, ps) ``` -Next, let us consider the following [self-activation loop](@ref ref): +Next, let us consider the following [self-activation loop](@ref basic_CRN_library_self_activation): ```@example stability_1 sa_loop = @reaction_network begin (hill(X,v,K,n),d), 0 <--> X @@ -51,11 +51,11 @@ ss_jac = steady_state_jac(sa_loop) ps_1 = [:v => 2.0, :K => 0.5, :n => 3, :d => 1.0] steady_states_1 = hc_steady_states(sa_loop, ps) -stability_1 = [steady_state_stability(state, sa_loop, ps_1; ss_jac = ss_jac) for state in steady_states_1] +stabs_1 = [steady_state_stability(st, sa_loop, ps_1; ss_jac) for st in steady_states_1] ps_2 = [:v => 4.0, :K => 1.5, :n => 2, :d => 1.0] steady_states_2 = hc_steady_states(sa_loop, ps) -stability_2 = [steady_state_stability(state, sa_loop, ps_2; ss_jac = ss_jac) for state in steady_states_2] +stabs_2 = [steady_state_stability(st, sa_loop, ps_2; ss_jac) for st in steady_states_2] nothing # hide ``` From aa9735ce2c7c1313e8a393f88b29eaefcf234269 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 28 May 2024 15:44:19 -0400 Subject: [PATCH 010/446] up --- docs/src/assets/Project.toml | 6 ++++++ test/reactionsystem_core/events.jl | 6 +++--- test/simulation_and_solving/simulate_SDEs.jl | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index 6225a0e2cb..246727a23e 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -29,6 +29,7 @@ QuasiMonteCarlo = "8a4e6c94-4038-4cdc-81c3-7e6ffdb2a71b" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f" StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" StructuralIdentifiability = "220ca800-aa68-49bb-acd8-6037fa93a544" @@ -54,12 +55,17 @@ ModelingToolkit = "9.15" NonlinearSolve = "3.12" Optim = "1.9" Optimization = "3.25" +OptimizationBBO = "0.2.1" +OptimizationNLopt = "0.2.1" +OptimizationOptimJL = "0.3.1" +OptimizationOptimisers = "0.2.1" OrdinaryDiffEq = "6.80.1" Plots = "1.40" QuasiMonteCarlo = "0.3" SciMLBase = "2.39" SciMLSensitivity = "7.60" SpecialFunctions = "2.4" +StaticArrays = "1.9" SteadyStateDiffEq = "2.2" StochasticDiffEq = "6.65" StructuralIdentifiability = "0.5.7" diff --git a/test/reactionsystem_core/events.jl b/test/reactionsystem_core/events.jl index ab15dd68ae..ab8488eaf7 100644 --- a/test/reactionsystem_core/events.jl +++ b/test/reactionsystem_core/events.jl @@ -97,9 +97,9 @@ let @test Symbolics.unwrap(rs_ce_de.α) isa Symbolics.BasicSymbolic{Int64} @test Symbolics.unwrap(rs_de.α) isa Symbolics.BasicSymbolic{Int64} @test Symbolics.unwrap(rs_ce_de.α) isa Symbolics.BasicSymbolic{Int64} - @test getdescription(rs_ce_de.A) == "A species" - @test getdescription(rs_de.A) == "A species" - @test getdescription(rs_ce_de.A) == "A species" + @test ModelingToolkit.getdescription(rs_ce_de.A) == "A species" + @test ModelingToolkit.getdescription(rs_de.A) == "A species" + @test ModelingToolkit.getdescription(rs_ce_de.A) == "A species" # Tests that species/variables/parameters can be accessed correctly one a MTK problem have been created. u0 = [X => 1] diff --git a/test/simulation_and_solving/simulate_SDEs.jl b/test/simulation_and_solving/simulate_SDEs.jl index e59a9bfc74..3e71947495 100644 --- a/test/simulation_and_solving/simulate_SDEs.jl +++ b/test/simulation_and_solving/simulate_SDEs.jl @@ -244,8 +244,8 @@ let u0 = [:X1 => 500.0, :X2 => 500.0] p = [:p => 20.0, :d => 0.1, :η1 => 0.0, :η3 => 0.0, :η4 => 0.0, :k1 => 2.0, :k2 => 2.0, :par1 => 1000.0, :par2 => 1000.0] - @test getdescription(parameters(noise_scaling_network)[2]) == "Parameter par1" - @test getdescription(parameters(noise_scaling_network)[5]) == "Parameter η2" + @test ModelingToolkit.getdescription(parameters(noise_scaling_network)[2]) == "Parameter par1" + @test ModelingToolkit.getdescription(parameters(noise_scaling_network)[5]) == "Parameter η2" sprob = SDEProblem(noise_scaling_network, u0, (0.0, 1000.0), p) @test sprob.ps[:η1] == sprob.ps[:η2] == sprob.ps[:η3] == sprob.ps[:η4] == 0.0 From 0d13a402ba6b46687780dfcf118fe25f144eadec Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 28 May 2024 17:18:53 -0400 Subject: [PATCH 011/446] update more docs with internal references --- .../catalyst_for_new_julia_users.md | 4 +-- .../chemistry_related_functionality.md | 17 +++++----- docs/src/model_creation/dsl_advanced.md | 32 +++++++++---------- docs/src/model_creation/dsl_basics.md | 16 +++++----- .../examples/basic_CRN_library.md | 2 +- .../programmatic_generative_linear_pathway.md | 5 ++- .../src/model_creation/model_visualisation.md | 8 ++--- test/dsl/dsl_options.jl | 4 +-- 8 files changed, 46 insertions(+), 42 deletions(-) diff --git a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md index ef9924f34f..8bf05f80ca 100644 --- a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md +++ b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md @@ -77,7 +77,7 @@ Catalyst models are created through the `@reaction_network` *macro*. For more in The `@reaction_network` command is followed by the `begin` keyword, which is followed by one line for each *reaction* of the model. Each reaction consists of a *reaction rate*, followed by the reaction itself. The reaction contains a set of *substrates* and a set of *products* (what is consumed and produced by the reaction, respectively). These are separated by a `-->` arrow. Finally, the model ends with the `end` keyword. -Here, we create a simple *birth-death* model, where a single species ($X$) is created at rate $b$, and degraded at rate $d$. The model is stored in the variable `rn`. +Here, we create a simple [*birth-death* model](@ref basic_CRN_library_bd), where a single species ($X$) is created at rate $b$, and degraded at rate $d$. The model is stored in the variable `rn`. ```@example ex2 rn = @reaction_network begin b, 0 --> X @@ -136,7 +136,7 @@ Pkg.add("JumpProcesses") using JumpProcesses ``` -This time, we will declare a so-called [SIR model for an infectious disease](https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SIR_model). Note that even if this model does not describe a set of chemical reactions, it can be modelled using the same framework. The model consists of 3 species: +This time, we will declare a so-called [SIR model for an infectious disease](@ref basic_CRN_library_sir). Note that even if this model does not describe a set of chemical reactions, it can be modelled using the same framework. The model consists of 3 species: * $S$, the amount of *susceptible* individuals. * $I$, the amount of *infected* individuals. * $R$, the amount of *recovered* (or *removed*) individuals. diff --git a/docs/src/model_creation/chemistry_related_functionality.md b/docs/src/model_creation/chemistry_related_functionality.md index ff6dfd9373..2325e88f6d 100644 --- a/docs/src/model_creation/chemistry_related_functionality.md +++ b/docs/src/model_creation/chemistry_related_functionality.md @@ -1,9 +1,10 @@ # [Chemistry-related functionality](@id chemistry_functionality) -While Catalyst has primarily been designed around the modelling of biological systems, reaction network models are also common across chemistry. This section describes two types of functionality, that while of general interest, should be especially useful in the modelling of chemical systems. +While Catalyst has primarily been designed around the modelling of biological systems, reaction network models are also common in chemistry. This section describes two types of functionality, that while of general interest, should be especially useful in the modelling of chemical systems. - The `@compound` option, which enables the user to designate that a specific species is composed of certain subspecies. - The `balance_reaction` function, which enables the user to balance a reaction so the same number of components occur on both sides. + ## Modelling with compound species ### Creating compound species programmatically @@ -17,7 +18,7 @@ Next, we create the `CO2` compound species: ```@example chem1 @compound CO2 ~ C + 2O ``` -Here, the compound is the first argument to the macro, followed by its component (with the left-hand and right-hand sides separated by a `~` sign). While non-compound species (such as `C` and `O`) have their independent variable (in this case `t`) designated, independent variables are generally not designated for compounds (these are instead directly inferred from their components). Components with non-unit stoichiometries have this value written before the component (generally, the rules for designating the components of a compound are identical to those of designating the substrates or products of a reaction). The created compound, `CO2`, is also a species, and can be used wherever e.g. `C` can be used: +Here, the compound is the first argument to the macro, followed by its component (with the left-hand and right-hand sides separated by a `~` sign). While non-compound species (such as `C` and `O`) have their independent variable (in this case `t`) designated, independent variables are generally not designated for compounds (these are instead directly inferred from their components). Components with non-unitary stoichiometries have this value written before the component (generally, the rules for designating the components of a compound are identical to those of designating the substrates or products of a reaction). The created compound, `CO2`, is also a species, and can be used wherever e.g. `C` can be used: ```@example chem1 isspecies(CO2) ``` @@ -32,7 +33,7 @@ Alternatively, we can retrieve the components and their stoichiometric coefficie ```@example chem1 component_coefficients(CO2) ``` -Finally, it is possible to check whether a species is a compound or not using the `iscompound` function: +Finally, it is possible to check whether a species is a compound using the `iscompound` function: ```@example chem1 iscompound(CO2) ``` @@ -68,7 +69,7 @@ end ``` When creating compound species using the DSL, it is important to note that *every component must be known to the system as a species, either by being declared using the `@species` or `@compound` options, or by appearing in a reaction*. E.g. the following is not valid ```julia -rn = @reaction_network begin`` +rn = @reaction_network begin @compounds begin C2O ~ C + 2O H2O ~ 2H + O @@ -77,7 +78,7 @@ rn = @reaction_network begin`` (k1,k2), H2O+ CO2 <--> H2CO3 end ``` -as the components `C`, `H`, and `O` are not declared as a species anywhere. Please also note that only `@compounds` can be used as an option in the DSL, not `@compound`. +as the components `C`, `H`, and `O` are not declared as species anywhere. Please also note that only `@compounds` can be used as an option in the DSL, not `@compound`. ### Designating metadata and default values for compounds Just like for normal species, it is possible to designate metadata and default values for compounds. Metadata is provided after the compound name, but separated from it by a `,`: @@ -92,10 +93,10 @@ If both default values and meta data are provided, the metadata is provided afte ```@example chem1 @compound (CO2 = 2.0, [unit="mol"]) ~ C + 2O ``` -In all of these cases, the side to the left of the `~` must be enclosed within `()`. +In all of these cases, the left-hand side must be enclosed within `()`. ### Compounds with multiple independent variables -While we generally do not need to specify independent variables for compound, if the components (together) have more than one independent variable, this have to be done: +While we generally do not need to specify independent variables for compound, if the components (together) have more than one independent variable, this *must be done*: ```@example chem1 t = default_t() @variables s @@ -113,7 +114,7 @@ Here, the reaction rate (`k`) is not involved in the reaction balancing. We use ```@example chem1 balance_reaction(rx) ``` -which correctly finds the (rather trivial) solution `C + 2O --> CO2`. Here we note that `balance_reaction` actually returns a vector. The reason is that the reaction balancing problem may have several solutions. Typically, there is only a single solution (in which case this is the vector's only element). No, or an infinite number of, solutions is also possible depending on the given reaction. +which correctly finds the (rather trivial) solution `C + 2O --> CO2`. Here we note that `balance_reaction` actually returns a vector. The reason is that, in some cases, the reaction balancing problem does not have a single obvious solution. Typically, a single solution is the obvious candidate (in which case this is the vector's only element). However, when this is not the case, the vector instead contain several reactions (from which a balanced reaction cab be generated). Let us consider a more elaborate example, the reaction between ammonia (NH₃) and oxygen (O₂) to form nitrogen monoxide (NO) and water (H₂O). Let us first create the components and the unbalanced reaction: ```@example chem2 diff --git a/docs/src/model_creation/dsl_advanced.md b/docs/src/model_creation/dsl_advanced.md index 8a1026bbae..b302cf8341 100644 --- a/docs/src/model_creation/dsl_advanced.md +++ b/docs/src/model_creation/dsl_advanced.md @@ -9,7 +9,7 @@ using Catalyst ``` ## [Explicit specification of network species and parameters](@id dsl_advanced_options_declaring_species_and_parameters) -[Previously](@ref ref), we mentioned that the DSL automatically determines which symbols correspond to species and which to parameters. This is done by designating everything that appears as either a substrate or a product as a species, and all remaining quantities as parameters (i.e. those only appearing within rates or [stoichiometric constants](@ref ref)). Sometimes, one might want to manually override this default behaviour for a given symbol. I.e. consider the following model, where the conversion of a protein `P` from its inactive form (`Pᵢ`) to its active form (`Pₐ`) is catalysed by an enzyme (`E`). Using the most natural description: +Previously, we mentioned that the DSL automatically determines which symbols correspond to species and which to parameters. This is done by designating everything that appears as either a substrate or a product as a species, and all remaining quantities as parameters (i.e. those only appearing within rates or [stoichiometric constants](@ref dsl_description_stoichiometries_parameters)). Sometimes, one might want to manually override this default behaviour for a given symbol. I.e. consider the following model, where the conversion of a protein `P` from its inactive form (`Pᵢ`) to its active form (`Pₐ`) is catalysed by an enzyme (`E`). Using the most natural description: ```@example dsl_advanced_explicit_definitions catalysis_sys = @reaction_network begin k*E, Pᵢ --> Pₐ @@ -74,7 +74,7 @@ end parameters(dimerisation) ``` !!! danger - Generally, Catalyst and the SciML ecosystem *do not* guarantee that parameter and species order are preserved throughout various operations on a model. Writing programs that depend on these orders is *strongly discouraged*. There are, however, some legacy packages which still depend on order (one example is provided [here](@ref ref)). In these situations, this might be useful. However, in these cases, it is recommended that the user is extra wary, and also checks the order manually. + Generally, Catalyst and the SciML ecosystem *do not* guarantee that parameter and species order are preserved throughout various operations on a model. Writing programs that depend on these orders is *strongly discouraged*. There are, however, some legacy packages which still depend on order (one example can be found [here](@ref optimization_parameter_fitting_basics)). In these situations, this might be useful. However, in these cases, it is recommended that the user is extra wary, and also checks the order manually. !!! note The syntax of the `@species` and `@parameters` options is identical to that used by the `@species` and `@parameters` macros [used in programmatic modelling in Catalyst](@ref programmatic_CRN_construction) (for e.g. designating metadata or initial conditions). Hence, if one has learnt how to specify species/parameters using either approach, that knowledge can be transferred to the other one. @@ -125,7 +125,7 @@ rn = @reaction_network begin end tspan = (0.0, 10.0) -p = [:p => 1.0, :D => 0.2] +p = [:p => 1.0, :d => 0.2] oprob = ODEProblem(rn, u0, tspan, p) sol = solve(oprob) plot(sol) @@ -171,10 +171,10 @@ two_state_system = @reaction_network begin (ka,kD), Xi <--> Xa end ``` -A metadata can be given to only a subset of a system's species/parameters, and a quantity can be given several metadata entries. To give several metadata, separate each by a `,`. Here we only provide a description for `kA`, for which we also provide a [bounds metadata](@ref https://docs.sciml.ai/ModelingToolkit/dev/basics/Variable_metadata/#Bounds), +A metadata can be given to only a subset of a system's species/parameters, and a quantity can be given several metadata entries. To give several metadata, separate each by a `,`. Here we only provide a description for `kA`, for which we also provide a [`bounds` metadata](@ref https://docs.sciml.ai/ModelingToolkit/dev/basics/Variable_metadata/#Bounds), ```@example dsl_advanced_metadata two_state_system = @reaction_network begin - @parameters kA [description="X's activation rate", bound=(0.01,10.0)] + @parameters kA [description="X's activation rate", bounds=(0.01,10.0)] (ka,kD), Xi <--> Xa end ``` @@ -182,7 +182,7 @@ end It is possible to add both default values and metadata to a parameter/species. In this case, first provide the default value, and next the metadata. I.e. to in the above example set $kA$'s default value to $1.0$ we use ```@example dsl_advanced_metadata two_state_system = @reaction_network begin - @parameters kA=1.0 [description="X's activation rate", bound=(0.01,10.0)] + @parameters kA=1.0 [description="X's activation rate", bounds=(0.01,10.0)] (ka,kD), Xi <--> Xa end ``` @@ -191,10 +191,10 @@ When designating metadata for species/parameters in `begin ... end` blocks the s ```@example dsl_advanced_metadata two_state_system = @reaction_network begin @parameters begin - kA, [description="X's activation rate", bound=(0.01,10.0)] - kD = 1.0, [description="X's deactivation rate"] + kA, [description="X's activation rate", bounds=(0.01,10.0)] + kD = 1.0, [description="X's deactivation rate"] end - (ka,kD), Xi <--> Xa + (kA,kD), Xi <--> Xa end ``` @@ -229,7 +229,7 @@ end A common use-case for constant species is when modelling systems where some species are present in such surplus that their amounts the reactions' effect on it is negligible. A system which is commonly modelled this way is the [Brusselator](https://en.wikipedia.org/wiki/Brusselator). ### [Designating parameter types](@id dsl_advanced_options_parameter_types) -Sometimes it is desired to designate that a parameter should have a specific [type](@ref ref). When supplying this parameter's value to e.g. an `ODEProblem`, that parameter will then be restricted to that specific type. Designating a type is done by appending the parameter with `::` followed by its type. E.g. in the following example we specify that the parameter `n` (the number of `X` molecules in the `Xn` polymer) must be an integer (`Int64`) +Sometimes it is desired to designate that a parameter should have a specific [type](https://docs.julialang.org/en/v1/manual/types/). When supplying this parameter's value to e.g. an `ODEProblem`, that parameter will then be restricted to that specific type. Designating a type is done by appending the parameter with `::` followed by its type. E.g. in the following example we specify that the parameter `n` (the number of `X` molecules in the `Xn` polymer) must be an integer (`Int64`) ```@example dsl_advanced_parameter_types using Catalyst # hide polymerisation_network = @reaction_network begin @@ -238,7 +238,7 @@ polymerisation_network = @reaction_network begin end nothing # hide ``` -Generally, when simulating models with mixed parameter types, it is recommended to [declare parameter values as tuples, rather than vectors](@ref ref), e.g.: +Generally, when simulating models with mixed parameter types, it is recommended to [declare parameter values as tuples, rather than vectors](@ref simulation_intro_ODEs_input_forms), e.g.: ```@example dsl_advanced_parameter_types ps = (:kB => 0.2, :kD => 1.0, :n => 2) nothing # hide @@ -254,7 +254,7 @@ nothing # hide ``` ### [Vector-valued species or parameters](@id dsl_advanced_options_vector_variables) -Sometimes, one wishes to declare a large number of similar parameters or species. This can be done by *creating them as vectors*. E.g. below we create a [two-state system](@ref ref). However, instead of declaring `X1` and `X2` (and `k1` and `k2`) as separate entities, we declare them as vectors: +Sometimes, one wishes to declare a large number of similar parameters or species. This can be done by *creating them as vectors*. E.g. below we create a [two-state system](@ref basic_CRN_library_two_states). However, instead of declaring `X1` and `X2` (and `k1` and `k2`) as separate entities, we declare them as vectors: ```@example dsl_advanced_vector_variables using Catalyst # hide two_state_model = @reaction_network begin @@ -316,7 +316,7 @@ end rn1 == rn2 ``` -Setting model names is primarily useful for [hierarchical modelling](@ref ref), where network names are appended to the display names of subnetworks' species and parameters. +Setting model names is primarily useful for [hierarchical modelling](@ref compositional_modeling), where network names are appended to the display names of subnetworks' species and parameters. ## [Creating observables](@id dsl_advanced_options_observables) Sometimes one might want to use observable variables. These are variables with values that can be computed directly from a system's state (rather than having their values implicitly given by reactions or equations). Observables can be designated using the `@observables` option. Here, the `@observables` option is followed by a `begin ... end` block with one line for each observable. Each line first gives the observable, followed by a `~` (*not* a `=`!), followed by an expression describing how to compute it. @@ -350,11 +350,11 @@ sol[:Xtot] to get a vector with `Xtot`'s value throughout the simulation. We can also use ```@example dsl_advanced_observables using Plots -plot(sol; idxs = [:Xtot, :Ytot]) +plot(sol; idxs = :Xtot) ``` to plot the observables (rather than the species). -Observables can be defined using complicated expressions containing species, parameters, and [variables](@ref ref) (but not other observables). In the following example (which uses a [parametric stoichiometry](@ref ref)) `X` polymerises to form a complex `Xn` containing `n` copies of `X`. Here, we create an observable describing the total number of `X` molecules in the system: +Observables can be defined using complicated expressions containing species, parameters, and [variables](@ref ref) (but not other observables). In the following example (which uses a [parametric stoichiometry](@ref dsl_description_stoichiometries_parameters)) `X` polymerises to form a complex `Xn` containing `n` copies of `X`. Here, we create an observable describing the total number of `X` molecules in the system: ```@example dsl_advanced_observables rn = @reaction_network begin @observables Xtot ~ X + n*Xn @@ -378,7 +378,7 @@ Observables are by default considered [variables](@ref ref) (not species). To de ```@example dsl_advanced_observables rn = @reaction_network begin @species Xtot(t) - @observables Xtot ~ X + n*XnXY + @observables Xtot ~ X + n*Xn (kB,kD), n*X <--> Xn end nothing # hide diff --git a/docs/src/model_creation/dsl_basics.md b/docs/src/model_creation/dsl_basics.md index 19057d2d65..78864457f5 100644 --- a/docs/src/model_creation/dsl_basics.md +++ b/docs/src/model_creation/dsl_basics.md @@ -1,7 +1,7 @@ # [The Catalyst DSL - Introduction](@id dsl_description) In the [introduction to Catalyst](@ref introduction_to_catalyst) we described how the `@reaction_network` [macro](https://docs.julialang.org/en/v1/manual/metaprogramming/#man-macros) can be used to create chemical reaction network (CRN) models. This macro enables a so-called [domain-specific language](https://en.wikipedia.org/wiki/Domain-specific_language) (DSL) for creating CRN models. This tutorial will give a basic introduction on how to create Catalyst models using this macro (from now onwards called "*the Catalyst DSL*"). A [follow-up tutorial](@ref dsl_advanced_options) will describe some of the DSL's more advanced features. -The Catalyst DSL generates a [`ReactionSystem`](@ref) (the [julia structure](https://docs.julialang.org/en/v1/manual/types/#Composite-Types) Catalyst uses to represent CRN models). These can be created through alternative methods (e.g. [programmatically](@ref programmatic_CRN_construction) or [compositionally](@ref compositional_modeling)). A summary of the various ways to create `ReactionSystems`s can be found [here](@ref ref). [Previous](@ref ref) and [following](@ref ref) tutorials describe how to simulate models once they have been created using the DSL. This tutorial will solely focus on model creation. +The Catalyst DSL generates a [`ReactionSystem`](@ref) (the [julia structure](https://docs.julialang.org/en/v1/manual/types/#Composite-Types) Catalyst uses to represent CRN models). These can be created through alternative methods (e.g. [programmatically](@ref programmatic_CRN_construction) or [compositionally](@ref compositional_modeling)). A summary of the various ways to create `ReactionSystems`s can be found [here](@ref ref). [Previous](@ref ref) and [following](@ref simulation_intro) tutorials describe how to simulate models once they have been created using the DSL. This tutorial will solely focus on model creation. Before we begin, we will first load the Catalyst package (which is required to run the code). ```@example dsl_basics_intro @@ -9,18 +9,19 @@ using Catalyst ``` ### [Quick-start summary](@id dsl_description_quick_start) -The DSL is initiated through the `@reaction_network` macro, which is followed by one line for each reaction. Each reaction consists of a *rate*, followed lists first of the substrates and next of the products. E.g. a [Michaelis-Menten enzyme kinetics system](@ref ref) can be written as +The DSL is initiated through the `@reaction_network` macro, which is followed by one line for each reaction. Each reaction consists of a *rate*, followed lists first of the substrates and next of the products. E.g. a [Michaelis-Menten enzyme kinetics system](@ref basic_CRN_library_mm) can be written as ```@example dsl_basics_intro rn = @reaction_network begin (kB,kD), S + E <--> SE kP, SE --> P + E end ``` -Here, `<-->` is used to create a bi-directional reaction (with forward rate `kP` and backward rate `kD`). Next, the model (stored in the variable `rn`) can be used as input to various types of [simulations](@ref ref). +Here, `<-->` is used to create a bi-directional reaction (with forward rate `kP` and backward rate `kD`). Next, the model (stored in the variable `rn`) can be used as input to various types of [simulations](@ref simulation_intro). ## [Basic syntax](@id dsl_description_basic_syntax) The basic syntax of the DSL is ```@example dsl_basics +using Catalyst # hide rn = @reaction_network begin 2.0, X --> Y 1.0, Y --> X @@ -36,7 +37,7 @@ Each reaction line declares, in order, the rate, the substrate(s), and the produ Finally, `rn = ` is used to store the model in the variable `rn` (a normal Julia variable, which does not need to be called `rn`). ## [Defining parameters and species in the DSL](@id dsl_description_parameters_basics) -Typically, the rates are not constants, but rather parameters (which values can be set e.g. at [the beginning of each simulation](@ref ref)). To set parametric rates, simply use whichever symbol you wish to represent your parameter with. E.g. to set the above rates to `a` and `b`, we use: +Typically, the rates are not constants, but rather parameters (which values can be set e.g. at [the beginning of each simulation](@ref simulation_intro_ODEs)). To set parametric rates, simply use whichever symbol you wish to represent your parameter with. E.g. to set the above rates to `a` and `b`, we use: ```@example dsl_basics rn1 = @reaction_network begin a, X --> Y @@ -88,7 +89,7 @@ rn5 = @reaction_network begin kD, X2 --> 2X end ``` -Reactants whose stoichiometries are not defined are assumed to have stoichiometry `1`. Any integer number can be used, furthermore, [decimal numbers and parameters can also be used as stoichiometries](@ref ref). A discussion of non-unitary (i.e. not equal to `1`) stoichiometries affecting the created model can be found [here](@ref ref). +Reactants whose stoichiometries are not defined are assumed to have stoichiometry `1`. Any integer number can be used, furthermore, [decimal numbers and parameters can also be used as stoichiometries](@ref dsl_description_stoichiometries). A discussion of non-unitary (i.e. not equal to `1`) stoichiometries affecting the created model can be found [here](@ref ref). Stoichiometries can be combined with `()` to define them for multiple reactants. Here, the following (mock) model declares the same reaction twice, both with and without this notation: ```@example dsl_basics @@ -199,7 +200,7 @@ rn_13 = @reaction_network begin kP*A, 0 --> P end ``` -Here, `P`'s production rate will be reduced as `A` decays. We can [print the ODE this model produces with `Latexify`](@ref ref): +Here, `P`'s production rate will be reduced as `A` decays. We can [print the ODE this model produces with `Latexify`](@ref visualisation_latex): ```@example dsl_basics using Latexify latexify(rn_13; form=:ode) @@ -221,7 +222,7 @@ Here, while these models will generate identical ODE, SDE, and jump simulations, While `rn_13` and `rn_13_alt` will generate equivalent simulations, for jump simulations, the first model will have [reduced performance](@ref ref) (which generally are more performant when rates are constant). !!! danger - Catalyst automatically infers whether quantities appearing in the DSL are species or parameters (as described [here](@ref dsl_advanced_options_declaring_species_and_parameters)). Generally, anything that does not appear as a reactant is inferred to be a parameter. This means that if you want to model a reaction activated by a species (e.g. `kp*A, 0 --> P`), but that species does not occur as a reactant, it will be interpreted as a parameter. This can be handled by [manually declaring the system species](@ref dsl_advanced_options_declaring_species_and_parameters). A full example of how to do this for this example can be found [here](@ref ref). + Catalyst automatically infers whether quantities appearing in the DSL are species or parameters (as described [here](@ref dsl_advanced_options_declaring_species_and_parameters)). Generally, anything that does not appear as a reactant is inferred to be a parameter. This means that if you want to model a reaction activated by a species (e.g. `kp*A, 0 --> P`), but that species does not occur as a reactant, it will be interpreted as a parameter. This can be handled by [manually declaring the system species](@ref dsl_advanced_options_declaring_species_and_parameters). Above we used a simple example where the rate was the product of a species and a parameter. However, any valid Julia expression of parameters, species, and values can be used. E.g the following is a valid model: ```@example dsl_basics @@ -369,4 +370,3 @@ It should be noted that the following symbols are *not permitted* to be used as - `im` (used in Julia to represent [complex numbers](https://docs.julialang.org/en/v1/manual/complex-and-rational-numbers/#Complex-Numbers)). - `nothing` (used in Julia to denote [nothing](https://docs.julialang.org/en/v1/base/constants/#Core.nothing)). - `Γ` (used by Catalyst to represent [conserved quantities](@ref ref)). - diff --git a/docs/src/model_creation/examples/basic_CRN_library.md b/docs/src/model_creation/examples/basic_CRN_library.md index 4955aa522d..41e2a705f8 100644 --- a/docs/src/model_creation/examples/basic_CRN_library.md +++ b/docs/src/model_creation/examples/basic_CRN_library.md @@ -279,7 +279,7 @@ oplt2 = plot(osol2; title = "Oscillation") plot(oplt1, oplt2; lw = 3, size = (800,600), layout = (2,1)) ``` -## [The Repressilator](@id basic_CRN_library_) +## [The Repressilator](@id basic_CRN_library_repressilator) The Repressilator was introduced in [*Elowitz & Leibler (2000)*](https://www.nature.com/articles/35002125) as a simple system that can generate oscillations (most notably, they demonstrated this both in a model and in a synthetic in vivo implementation in *Escherichia col*). It consists of three genes, repressing each other in a cycle. Here, we will implement it using three species ($X$, $Y$, and $Z$) whose production rates are (repressing) [Hill functions](https://en.wikipedia.org/wiki/Hill_equation_(biochemistry)). ```@example crn_library_brusselator using Catalyst diff --git a/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md b/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md index ddd7993ed0..ba052781be 100644 --- a/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md +++ b/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md @@ -1,5 +1,5 @@ # [Programmatic, generative, modelling of a linear pathway](@id programmatic_generative_linear_pathway) -This example will show how to use programmatic, generative, modelling to model a system implicitly. I.e. rather than listing all system reactions explicitly, the reactions are implicitly generated from a simple set of rules. This example is specifically designed to show how [programmatic modelling](@ref ref) enables *generative workflows* (demonstrating one of its advantages as compared to [DSL-based modelling](@ref ref)). In our example, we will model linear pathways, so we will first introduce these. Next, we will model them first using the DSL, and then using a generative programmatic workflow. +This example will show how to use programmatic, generative, modelling to model a system implicitly. I.e. rather than listing all system reactions explicitly, the reactions are implicitly generated from a simple set of rules. This example is specifically designed to show how [programmatic modelling](@ref ref) enables *generative workflows* (demonstrating one of its advantages as compared to [DSL-based modelling](@ref dsl_description)). In our example, we will model linear pathways, so we will first introduce these. Next, we will model them first using the DSL, and then using a generative programmatic workflow. ## [Linear pathways](@id programmatic_generative_linear_pathway_intro) Linear pathways consists of a series of species ($X_0$, $X_1$, $X_2$, ..., $X_n$) where each activates the subsequent one. These are often modelled through the following reaction system: @@ -80,6 +80,8 @@ Above, we investigated the impact of linear pathways' lengths on their behaviour First, we create a function, `generate_lp`, which creates a linear pathway model of length `n`. It utilises [*vector variables*](@ref ref) to create an arbitrary number of species, and also creates an [observable](@ref ref) for the final species of the chain. ```@example programmatic_generative_linear_pathway_generative using Catalyst # hide +t = default_t() +@parameters τ function generate_lp(n) # Creates a vector `X` with n+1 species. @species X(t)[1:n+1] @@ -115,6 +117,7 @@ nothing # hide ``` We can now simulate linear pathways of arbitrary lengths using a simple syntax. We use this to recreate our previous result from the DSL: ```@example programmatic_generative_linear_pathway_generative +using OrdinaryDiffEq, Plots # hide sol_n3 = solve(generate_oprob(3)) sol_n10 = solve(generate_oprob(10)) plot(sol_n3; idxs = :Xend, label = "n = 3") diff --git a/docs/src/model_creation/model_visualisation.md b/docs/src/model_creation/model_visualisation.md index 463c96ce5c..36f8d738ef 100644 --- a/docs/src/model_creation/model_visualisation.md +++ b/docs/src/model_creation/model_visualisation.md @@ -4,7 +4,7 @@ Catalyst-created `ReactionSystem` models can be visualised either as LaTeX code ## [Displaying models using LaTeX](@id visualisation_latex) Once a model has been created, the [Latexify.jl](https://github.com/korsbo/Latexify.jl) package can be used to generate LaTeX code of the model. This can either be used for easy model inspection (e.g. to check which equations are being simulated), or to generate code which can be directly pasted into a LaTeX document. -Let us consider a simple [Brusselator model](@ref ref): +Let us consider a simple [Brusselator model](@ref basic_CRN_library_brusselator): ```@example visualisation_latex using Catalyst brusselator = @reaction_network begin @@ -32,10 +32,10 @@ latexify(brusselator; form = :ode) If you wish to copy the output to your [clipboard](https://en.wikipedia.org/wiki/Clipboard_(computing)) (e.g. so that you can paste it into a LaTeX document), run `copy_to_clipboard(true)` before you run `latexify`. A more throughout description of Latexify's features can be found in [its documentation](https://korsbo.github.io/Latexify.jl/stable/). !!! note - For a model to be nicely displayed you have to use an IDE that actually supports this (such as a [notebook](https://jupyter.org/)). Other environments (such as [the Julia REPL]([@ref ref](https://docs.julialang.org/en/v1/stdlib/REPL/))) will simply return the full LaTeX code which would generate the desired expression. + For a model to be nicely displayed you have to use an IDE that actually supports this (such as a [notebook](https://jupyter.org/)). Other environments (such as [the Julia REPL](https://docs.julialang.org/en/v1/stdlib/REPL/)) will simply return the full LaTeX code which would generate the desired expression. ## [Displaying model networks](@id visualisation_graphs) -A network graph showing a Catalyst model's species and reactions can be displayed using the `Graph` function. This first requires [Graphviz](https://graphviz.org/) to be installed and command line accessible. Here, we first declare a [Brusselator model](@ref ref) and then displays its network topology: +A network graph showing a Catalyst model's species and reactions can be displayed using the `Graph` function. This first requires [Graphviz](https://graphviz.org/) to be installed and command line accessible. Here, we first declare a [Brusselator model](@ref basic_CRN_library_brusselator) and then displays its network topology: ```@example visualisation_graphs using Catalyst brusselator = @reaction_network begin @@ -46,7 +46,7 @@ brusselator = @reaction_network begin end Graph(brusselator) ``` -The network graph represents species as blue nodes and reactions as orange dots. Black arrows from species to reactions indicate substrates, and are labelled with their respective stoichiometries. Similarly, black arrows from reactions to species indicate products (also labelled with their respective stoichiometries). If there are any reactions where a species affect the rate, but does not participate as a reactant, this is displayed with a dashed red arrow. This can be seen in the following [repressilator model](@ref ref): +The network graph represents species as blue nodes and reactions as orange dots. Black arrows from species to reactions indicate substrates, and are labelled with their respective stoichiometries. Similarly, black arrows from reactions to species indicate products (also labelled with their respective stoichiometries). If there are any reactions where a species affect the rate, but does not participate as a reactant, this is displayed with a dashed red arrow. This can be seen in the following [Repressilator model](@ref basic_CRN_library_repressilator): ```@example visualisation_graphs repressilator = @reaction_network begin hillr(Z,v,K,n), ∅ --> X diff --git a/test/dsl/dsl_options.jl b/test/dsl/dsl_options.jl index b6c8ada2e3..93ce90b541 100644 --- a/test/dsl/dsl_options.jl +++ b/test/dsl/dsl_options.jl @@ -584,7 +584,7 @@ let @observables (X, [description="my_description"]) ~ X1 + X2 k, 0 --> X1 + X2 end - @test getdescription(observed(rn)[1].lhs) == "my_description" + @test ModelingToolkit.getdescription(observed(rn)[1].lhs) == "my_description" end # Declares observables implicitly/explicitly. @@ -629,7 +629,7 @@ let (k1, k2), X1 <--> X2 end @test isequal(observed(rn1)[1].lhs, X) - @test getdescription(rn1.X) == "An observable" + @test ModelingToolkit.getdescription(rn1.X) == "An observable" @test isspecies(rn1.X) @test length(unknowns(rn1)) == 2 From 223c0fc5c4e049ce52806103a20c1a204efdcdd5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 28 May 2024 18:30:16 -0400 Subject: [PATCH 012/446] update references in simulation files --- .../model_simulation/ensemble_simulations.md | 13 +++++--- .../ode_simulation_performance.md | 23 +++++++------ .../simulation_introduction.md | 32 +++++++++---------- .../model_simulation/simulation_plotting.md | 2 +- .../simulation_structure_interfacing.md | 8 ++--- 5 files changed, 40 insertions(+), 38 deletions(-) diff --git a/docs/src/model_simulation/ensemble_simulations.md b/docs/src/model_simulation/ensemble_simulations.md index 47689b7050..b0731bf628 100644 --- a/docs/src/model_simulation/ensemble_simulations.md +++ b/docs/src/model_simulation/ensemble_simulations.md @@ -3,10 +3,10 @@ In many contexts, a single model is re-simulated under similar conditions. Examp - Performing Monte Carlo simulations of a stochastic model to gain insight in its behaviour. - Scanning a model's behaviour for different parameter values and/or initial conditions. -While this can be handled using `for` loops, it is typically better to first create an `EnsembleProblem`, and then perform an ensemble simulation. Advantages include a more concise interface and the option for [automatic simulation parallelisation](@ref ref). Here we provide a short tutorial on how to perform parallel ensemble simulations, with a more extensive documentation being available [here](@ref https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/). +While this can be handled using `for` loops, it is typically better to first create an `EnsembleProblem`, and then perform an ensemble simulation. Advantages include a more concise interface and the option for [automatic simulation parallelisation](@ref ode_simulation_performance_parallelisation). Here we provide a short tutorial on how to perform parallel ensemble simulations, with a more extensive documentation being available [here](https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/). ## [Monte Carlo simulations using unmodified conditions](@id ensemble_simulations_monte_carlo) -We will first consider Monte Carlo simulations where the simulation conditions are identical in-between simulations. First, we declare a [simple self-activation loop](@ref ref) model +We will first consider Monte Carlo simulations where the simulation conditions are identical in-between simulations. First, we declare a [simple self-activation loop](@ref basic_CRN_library_self_activation) model ```@example ensemble using Catalyst sa_model = @reaction_network begin @@ -19,6 +19,7 @@ ps = [:v0 => 0.1, :v => 2.5, :K => 40.0, :n => 4.0, :deg => 0.01] ``` We wish to simulate it as an SDE. Rather than performing a single simulation, however, we want to perform multiple ones. Here, we first create a normal `SDEProblem`, and use it as the single input to a `EnsembleProblem` (`EnsembleProblem` are created similarly for ODE and jump simulations, but the `ODEProblem` or `JumpProblem` is used instead). ```@example ensemble +using StochasticDiffEq sprob = SDEProblem(sa_model, u0, tspan, ps) eprob = EnsembleProblem(sprob) nothing # hide @@ -30,9 +31,10 @@ nothing # hide ``` Finally, we can use our ensemble simulation solution as input to `plot` (just like normal simulations): ```@example ensemble +using Plots plot(sols; la = 0.5) ``` -Here, each simulation is displayed as an individual trajectory. We also use the [`la` plotting option](@ref ref) to reduce the transparency of each individual line, improving the plot visual. +Here, each simulation is displayed as an individual trajectory. We also use the [`la` plotting option](@ref simulation_plotting_options) to reduce the transparency of each individual line, improving the plot visual. Various convenience functions are available for analysing and plotting ensemble simulations (a full list can be found [here]). Here, we use these to first create an `EnsembleSummary` (retrieving each simulation's value at time points `0.0, 1.0, 2.0, ... 1000.0`). Next, we use this as an input to the `plot` command, which automatically plots the mean $X$ activity across the ensemble, while also displaying the 5% and 95% quantiles as the shaded area: ```@example ensemble @@ -45,7 +47,8 @@ Previously, we assumed that each simulation used the same initial conditions and Here, we first create an `ODEProblem` of our previous self-activation loop: ```@example ensemble -oprob = ODEProblem(sa_model, u0, tspan, p) +using OrdinaryDiffEq +oprob = ODEProblem(sa_model, u0, tspan, ps) nothing # hide ``` Next, we wish to simulate the model for a range of initial conditions of $X$`. To do this we create a problem function, which takes the following arguments: @@ -53,7 +56,7 @@ Next, we wish to simulate the model for a range of initial conditions of $X$`. T - `i`: The number of this specific Monte Carlo iteration in the interval `1:trajectories`. - `repeat`: The iteration of the repeat of the simulation. Typically `1`, but potentially higher if [the simulation re-running option](https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/#Building-a-Problem) is used. -Here we will use the following problem function (utilising [remake](@ref ref)), which will provide a uniform range of initial concentrations of $X$: +Here we will use the following problem function (utilising [remake](@ref simulation_structure_interfacing_problems_remake)), which will provide a uniform range of initial concentrations of $X$: ```@example ensemble function prob_func(prob, i, repeat) remake(prob; u0 = [:X => i * 5.0]) diff --git a/docs/src/model_simulation/ode_simulation_performance.md b/docs/src/model_simulation/ode_simulation_performance.md index 86c50123d5..9eaf4d4018 100644 --- a/docs/src/model_simulation/ode_simulation_performance.md +++ b/docs/src/model_simulation/ode_simulation_performance.md @@ -13,7 +13,7 @@ Generally, this short checklist provides a quick guide for dealing with ODE perf ## [Regarding stiff and non-stiff problems and solvers](@id ode_simulation_performance_stiffness) Generally, ODE problems can be categorised into [*stiff ODEs* and *non-stiff ODEs*](https://en.wikipedia.org/wiki/Stiff_equation). This categorisation is important due to stiff ODEs requiring specialised solvers. A common cause of failure to simulate an ODE is the use of a non-stiff solver for a stiff problem. There is no exact way to determine whether a given ODE is stiff or not, however, systems with several different time scales (e.g. a CRN with both slow and fast reactions) typically generate stiff ODEs. -Here we simulate the (stiff) [Brusselator](@ref ref) model using the `Tsit5` solver (which is designed for non-stiff ODEs): +Here we simulate the (stiff) [Brusselator](@ref basic_CRN_library_brusselator) model using the `Tsit5` solver (which is designed for non-stiff ODEs): ```@example ode_simulation_performance_1 using Catalyst, OrdinaryDiffEq, Plots @@ -52,7 +52,7 @@ Finally, we should note that stiffness is not tied to the model equations only. ## [ODE solver selection](@id ode_simulation_performance_solvers) -OrdinaryDiffEq implements an unusually large number of ODE solvers, with the performance of the simulation heavily depending on which one is chosen. These are provided as the second argument to the `solve` command, e.g. here we use the `Tsit5` solver to simulate a simple [birth-death process](@ref ref): +OrdinaryDiffEq implements an unusually large number of ODE solvers, with the performance of the simulation heavily depending on which one is chosen. These are provided as the second argument to the `solve` command, e.g. here we use the `Tsit5` solver to simulate a simple [birth-death process](@ref basic_CRN_library_bd): ```@example ode_simulation_performance_2 using Catalyst, OrdinaryDiffEq @@ -123,7 +123,7 @@ nothing # hide ### [Using a sparse Jacobian](@id ode_simulation_performance_sparse_jacobian) For a system with $n$ variables, the Jacobian will be an $n\times n$ matrix. This means that, as $n$ becomes large, the Jacobian can become *very* large, potentially causing a significant strain on memory. In these cases, most Jacobian entries are typically $0$. This means that a [*sparse*](https://en.wikipedia.org/wiki/Sparse_matrix) Jacobian (rather than a *dense* one, which is the default) can be advantageous. To designate sparse Jacobian usage, simply provide the `sparse = true` option when constructing an `ODEProblem`: ```@example ode_simulation_performance_3 -oprob = ODEProblem(brusselator, u0, tspan, p; sparse = true) +oprob = ODEProblem(brusselator, u0, tspan, ps; sparse = true) nothing # hide ``` @@ -172,10 +172,10 @@ Generally, the use of preconditioners is only recommended for advanced users who ## [Parallelisation on CPUs and GPUs](@id ode_simulation_performance_parallelisation) Whenever an ODE is simulated a large number of times (e.g. when investigating its behaviour for different parameter values), the best way to improve performance is to [parallelise the simulation over multiple processing units](https://en.wikipedia.org/wiki/Parallel_computing). Indeed, an advantage of the Julia programming language is that it was designed after the advent of parallel computing, making it well-suited for this task. Roughly, parallelisation can be divided into parallelisation on [CPUs](https://en.wikipedia.org/wiki/Central_processing_unit) and on [GPUs](https://en.wikipedia.org/wiki/General-purpose_computing_on_graphics_processing_units). CPU parallelisation is most straightforward, while GPU parallelisation requires specialised ODE solvers (which Catalyst have access to). -Both CPU and GPU parallelisation require first building an `EnsembleProblem` (which defines the simulations you wish to perform) and then supplying this with the correct parallelisation options. These have [previously been introduced in Catalyst's documentation](@ref ref) (but in the context of convenient bundling of similar simulations, rather than to improve performance), with a more throughout description being found in [OrdinaryDiffEq's documentation](https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/#ensemble). Finally, a general documentation of parallel computing in Julia is available [here](https://docs.julialang.org/en/v1/manual/parallel-computing/). +Both CPU and GPU parallelisation require first building an `EnsembleProblem` (which defines the simulations you wish to perform) and then supplying this with the correct parallelisation options. `EnsembleProblem`s have [previously been introduced in Catalyst's documentation](@ref ensemble_simulations) (but in the context of convenient bundling of similar simulations, rather than to improve performance), with a more throughout description being found in [OrdinaryDiffEq's documentation](https://docs.sciml.ai/DiffEqDocs/stable/features/ensemble/#ensemble). Finally, a general documentation of parallel computing in Julia is available [here](https://docs.julialang.org/en/v1/manual/parallel-computing/). ### [CPU parallelisation](@id ode_simulation_performance_parallelisation_CPU) -For this example (and the one for GPUs), we will consider a modified [Michaelis-Menten enzyme kinetics model](@ref ref), which describes an enzyme ($E$) that converts a substrate ($S$) to a product ($P$): +For this example (and the one for GPUs), we will consider a modified [Michaelis-Menten enzyme kinetics model](@ref basic_CRN_library_mm), which describes an enzyme ($E$) that converts a substrate ($S$) to a product ($P$): ```@example ode_simulation_performance_4 using Catalyst mm_model = @reaction_network begin @@ -186,12 +186,12 @@ mm_model = @reaction_network begin end ``` The model can be simulated, showing how $P$ is produced from $S$: -```@example ode_simulation_performance_3 +```@example ode_simulation_performance_4 using OrdinaryDiffEq, Plots u0 = [:S => 1.0, :E => 1.0, :SE => 0.0, :P => 0.0] tspan = (0.0, 50.0) -p = [:kB => 1.0, :kD => 0.1, :kP => 0.5, :d => 0.1] -oprob = ODEProblem(mm_model, u0, tspan, p) +ps = [:kB => 1.0, :kD => 0.1, :kP => 0.5, :d => 0.1] +oprob = ODEProblem(mm_model, u0, tspan, ps) sol = solve(oprob, Tsit5()) plot(sol) ``` @@ -208,7 +208,7 @@ Here, `prob_func` takes 3 arguments: and output the `ODEProblem` simulated in the i'th simulation. -Let us assume that we wish to simulate our model 100 times, for $kP = 0.01, 0.02, ..., 0.99, 1.0$. We define our `prob_func` using [`remake`](@ref ref): +Let us assume that we wish to simulate our model 100 times, for $kP = 0.01, 0.02, ..., 0.99, 1.0$. We define our `prob_func` using [`remake`](@ref simulation_structure_interfacing_problems_remake): ```@example ode_simulation_performance_4 function prob_func(prob, i, repeat) return remake(prob; p = [:kP => 0.01*i]) @@ -240,12 +240,12 @@ esol = solve(eprob, Tsit5(), EnsembleDistributed(); trajectories=100) nothing # hide ``` To utilise multiple processes, you must first give Julia access to these. You can check how many processes are available using the `nprocs` (which requires the [Distributed.jl](https://github.com/JuliaLang/Distributed.jl) package): -```julia +```@example ode_simulation_performance_4 using Distributed nprocs() ``` Next, more processes can be added using `addprocs`. E.g. here we add an additional 4 processes: -```julia +```@example ode_simulation_performance_4 addprocs(4) nothing # hide ``` @@ -309,7 +309,6 @@ Generally, it is recommended to use `EnsembleGPUArray` for large models (that ha ```julia esol1 = solve(eprob, Tsit5(), EnsembleGPUArray(CUDA.CUDABackend()); trajectories = 10000) esol2 = solve(eprob, GPUTsit5(), EnsembleGPUKernel(CUDA.CUDABackend()); trajectories = 10000) -nothing # hide ``` Note that we have to provide the `CUDA.CUDABackend()` argument to our ensemble algorithms (to designate our GPU backend, in this case, CUDA). diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index 113b0fa7b4..ed3056c616 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -1,7 +1,7 @@ # [Model Simulation Introduction](@id simulation_intro) Catalyst's core functionality is the creation of *chemical reaction network* (CRN) models that can be simulated using ODE, SDE, and jump simulations. How such simulations are carried out has already been described in [Catalyst's introduction](@ref introduction_to_catalyst). This page provides a deeper introduction, giving some additional background and introducing various simulation-related options. -Here we will focus on the basics, with other sections of the simulation documentation describing various specialised features, or giving advice on performance. Anyone who plans on using Catalyst's simulation functionality extensively is recommended to also read the documentation on [solution plotting](@ref ref), and on how to [interact with simulation problems, integrators, and solutions](@ref ref). Anyone with an application for which performance is critical should consider reading the corresponding page on performance advice for [ODEs](@ref ref), [SDEs](@ref ref), or [jump simulations](@ref ref). +Here we will focus on the basics, with other sections of the simulation documentation describing various specialised features, or giving advice on performance. Anyone who plans on using Catalyst's simulation functionality extensively is recommended to also read the documentation on [solution plotting](@ref simulation_plotting), and on how to [interact with simulation problems, integrators, and solutions](@ref simulation_structure_interfacing). Anyone with an application for which performance is critical should consider reading the corresponding page on performance advice for [ODEs](@ref ode_simulation_performance), [SDEs](@ref ref), or [jump simulations](@ref ref). ### [Background to CRN simulations](@id simulation_intro_theory) This section provides some brief theory on CRN simulations. For details on how to carry out these simulations in actual code, please skip to the following sections. @@ -40,9 +40,9 @@ These three different approaches are summed up in the following table: Example simulation methods - [Euler](https://en.wikipedia.org/wiki/Euler_method#:~:text=The%20Euler%20method%20is%20a,proportional%20to%20the%20step%20size.), [Runge-Kutta](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods) - [Euler-Maruyama](https://en.wikipedia.org/wiki/Euler%E2%80%93Maruyama_method), [Milstein](https://en.wikipedia.org/wiki/Milstein_method) - [Gillespie](https://en.wikipedia.org/wiki/Gillespie_algorithm), [Sorting direct](https://pubmed.ncbi.nlm.nih.gov/16321569/) + Euler, Runge-Kutta + Euler-Maruyama, Milstein + Gillespie, Sorting direct Species units @@ -70,18 +70,18 @@ These three different approaches are summed up in the following table: Simulation package - [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) - [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) - [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) + OrdinaryDiffEq.jl + StochasticDiffEq.jl + JumpProcesses.jl ``` ## [Performing (ODE) simulations](@id simulation_intro_ODEs) -The following section gives a (more throughout than [previous]) introduction of how to simulate Catalyst models. This is exemplified using ODE simulations (some ODE-specific options will also be discussed). Later on, we will describe options specific to [SDE](@ref ref) and [jump](@ref ref) simulations. All ODE simulations are performed using the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package, which full documentation can be found [here](https://docs.sciml.ai/OrdinaryDiffEq/stable/). A dedicated section giving advice on how to optimise ODE simulation performance can be found [here](@ref ref) +The following section gives a (more throughout than [previous]) introduction of how to simulate Catalyst models. This is exemplified using ODE simulations (some ODE-specific options will also be discussed). Later on, we will describe things specific to [SDE](@ref simulation_intro_SDEs) and [jump](@ref simulation_intro_jumps) simulations. All ODE simulations are performed using the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package, which full documentation can be found [here](https://docs.sciml.ai/OrdinaryDiffEq/stable/). A dedicated section giving advice on how to optimise ODE simulation performance can be found [here](@ref ode_simulation_performance) -To perform any simulation, we must first define our model, as well as the simulation's initial conditions, time span, and parameter values. Here we will use a simple [two-state model](@ref ref): +To perform any simulation, we must first define our model, as well as the simulation's initial conditions, time span, and parameter values. Here we will use a simple [two-state model](@ref basic_CRN_library_two_states): ```@example simulation_intro_ode using Catalyst two_state_model = @reaction_network begin @@ -108,7 +108,7 @@ Finally, the result can be plotted using the [Plots.jl](https://github.com/Julia using Plots plot(sol) ``` -More information on how to interact with solution structures is provided [here](@ref simulation_structure_interfacing) and on how to plot them [here](@ref ref). +More information on how to interact with solution structures is provided [here](@ref simulation_structure_interfacing) and on how to plot them [here](@ref simulation_plotting). Some additional considerations: - If a model without parameters has been declared, only the first three arguments must be provided to `ODEProblem`. @@ -122,7 +122,7 @@ While good defaults are generally selected, OrdinaryDiffEq enables the user to c sol = solve(oprob, Rodas5P()) nothing # hide ``` -A full list of available solvers is provided [here](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/), and a discussion on optimal solver choices [here](@ref ref). +A full list of available solvers is provided [here](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/), and a discussion on optimal solver choices [here](@ref ode_simulation_performance_solvers). Additional options can be provided as keyword arguments. E.g. the `adaptive` arguments determine whether adaptive time-stepping is used (for algorithms that permit this). This defaults to `true`, but can be disabled using ```@example simulation_intro_ode @@ -160,7 +160,7 @@ nothing # hide The forms used for `u0` and `ps` does not need to be the same (but can e.g. be a vector and a tuple). !!! note - It [is possible](@ref ref) to designate specific types for parameters. When this is done, the tuple form for providing parameter values should be preferred. + It is possible to [designate specific types for parameters](@ref dsl_advanced_options_parameter_types). When this is done, the tuple form for providing parameter values should be preferred. Throughout Catalyst's documentation, we typically provide the time span as a tuple. However, if the first time point is `0.0` (which is typically the case), this can be omitted. Here, we supply only the simulation endpoint to our `ODEProblem`: ```@example simulation_intro_ode @@ -193,7 +193,7 @@ we can see that while this simulation (unlike the ODE ones) exhibits some fluctu Unlike for ODE and jump simulations, there are no good heuristics for automatically selecting suitable SDE solvers. Hence, for SDE simulations a solver must be provided. `STrapezoid` will work for a large number of cases. When this is not the case, however, please check the list of [available SDE solvers](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) for a suitable alternative (making sure to select one compatible with non-diagonal noise and the [Ito interpretation]https://en.wikipedia.org/wiki/It%C3%B4_calculus). ### [Common SDE simulation pitfalls](@id simulation_intro_SDEs_pitfalls) -Next, let us reduce species amounts (using [`remake`](@ref ref)), thereby also increasing the relative amount of noise, we encounter a problem when the model is simulated: +Next, let us reduce species amounts (using [`remake`](@ref simulation_structure_interfacing_problems_remake)), thereby also increasing the relative amount of noise, we encounter a problem when the model is simulated: ```@example simulation_intro_sde sprob = remake(sprob; u0 = [:X1 => 0.33, :X2 => 0.66]) sol = solve(sprob, STrapezoid()) @@ -209,7 +209,7 @@ sol = solve(sprob, STrapezoid()) sol = solve(sprob, STrapezoid(); seed = 12345) # hide plot(sol) ``` -Again, the simulation is aborted. This time, however, species concentrations are relatively large, so the CLE might still hold. What has happened this time is that the accuracy of the simulations has not reached its desired threshold. This can be deal with [by reducing simulation tolerances](@ref ref): +Again, the simulation is aborted. This time, however, species concentrations are relatively large, so the CLE might still hold. What has happened this time is that the accuracy of the simulations has not reached its desired threshold. This can be deal with [by reducing simulation tolerances](@ref ode_simulation_performance_error): ```@example simulation_intro_sde sol = solve(sprob, STrapezoid(), abstol = 1e-1, reltol = 1e-1) sol = solve(sprob, STrapezoid(); seed = 12345, abstol = 1e-1, reltol = 1e-1) # hide @@ -255,9 +255,9 @@ plot(sol) ``` !!! note - Above, Catalyst is unable to infer that $η$ is a parameter from the `@default_noise_scaling η` option only. Hence, `@parameters η` is used to explicitly declare $η$ to be a parameter (as discussed in more detail [here](@ref ref)). + Above, Catalyst is unable to infer that $η$ is a parameter from the `@default_noise_scaling η` option only. Hence, `@parameters η` is used to explicitly declare $η$ to be a parameter (as discussed in more detail [here](@ref dsl_advanced_options_declaring_species_and_parameters)). -It is possible to designate specific noise scaling terms for individual reactions through the `noise_scaling` [reaction metadata](@ref ref). Here, CLE noise terms associated with a specific reaction are multiplied by that reaction's noise scaling term. Here we use this to turn off the noise in the $X1 \to X2$ reaction: +It is possible to designate specific noise scaling terms for individual reactions through the `noise_scaling` [reaction metadata](@ref dsl_advanced_options_reaction_metadata). Here, CLE noise terms associated with a specific reaction are multiplied by that reaction's noise scaling term. Here we use this to turn off the noise in the $X1 \to X2$ reaction: ```@example simulation_intro_sde two_state_model = @reaction_network begin k1, X1 <--> X2, [noise_scaling = 0.0] diff --git a/docs/src/model_simulation/simulation_plotting.md b/docs/src/model_simulation/simulation_plotting.md index 25cbec54ee..4594ed692c 100644 --- a/docs/src/model_simulation/simulation_plotting.md +++ b/docs/src/model_simulation/simulation_plotting.md @@ -5,7 +5,7 @@ Catalyst uses the [Plots.jl](https://github.com/JuliaPlots/Plots.jl) package for [Makie.jl](https://github.com/MakieOrg/Makie.jl) is a popular alternative to the Plots.jl package. While it is not used within Catalyst's documentation, it is worth considering (especially for users interested in interactivity, or increased control over their plots). ## [Common plotting options](@id simulation_plotting_options) -Let us consider the oscillating [Brusselator](@ref ref) model. We have previously shown how model simulation solutions can be plotted using the `plot` function. Here we plot an ODE simulation from the [Brusselator](@ref ref) model: +Let us consider the oscillating [Brusselator](@ref basic_CRN_library_brusselator) model. We have previously shown how model simulation solutions can be plotted using the `plot` function. Here we plot an ODE simulation from the Brusselator: ```@example simulation_plotting using Catalyst, OrdinaryDiffEq, Plots diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index a993841262..f34d6b9808 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -5,7 +5,7 @@ Generally, when we have a structure `simulation_struct` and want to interface wi ## [Interfacing problem objects](@id simulation_structure_interfacing_problems) -We begin by demonstrating how we can interface with problem objects. First, we create an `ODEProblem` representation of a [chemical cross-coupling model](@ref ref) (where a catalyst, $C$, couples two substrates, $S₁$ and $S₂$, to form a product, $P$). +We begin by demonstrating how we can interface with problem objects. First, we create an `ODEProblem` representation of a [chemical cross-coupling model](@ref basic_CRN_library_cc) (where a catalyst, $C$, couples two substrates, $S₁$ and $S₂$, to form a product, $P$). ```@example structure_indexing using Catalyst cc_system = @reaction_network begin @@ -53,7 +53,7 @@ oprob[[:S₁, :S₂]] Generally, when updating problems, it is often better to use the [`remake` function](@ref simulation_structure_interfacing_problems_remake) (especially when several values are updated). !!! warn - Indexing *should not* be used not modify `JumpProblem`s. Here, [remake](@ref ref) should be used exclusively. + Indexing *should not* be used not modify `JumpProblem`s. Here, [remake](@ref simulation_structure_interfacing_problems_remake) should be used exclusively. A problem's time span can be accessed through the `tspan` field: ```@example structure_indexing @@ -162,7 +162,7 @@ get_S(oprob) ``` ## [Interfacing using symbolic representations](@id simulation_structure_interfacing_symbolic_representation) -As [previously described](@ref ref), when e.g. [programmatic modelling is used](@ref ref), species and parameters can be represented as *symbolic variables*. These can be used to index a problem, just like symbol-based representations can. Here we create a simple [two-state model](@ref ref) programmatically, and use its symbolic variables to check, and update, an initial condition: +As [previously described](@ref ref), when e.g. [programmatic modelling is used](@ref ref), species and parameters can be represented as *symbolic variables*. These can be used to index a problem, just like symbol-based representations can. Here we create a simple [two-state model](@ref rbasic_CRN_library_two_statesef) programmatically, and use its symbolic variables to check, and update, an initial condition: ```@example structure_indexing_symbolic_variables using Catalyst t = default_t() @@ -195,7 +195,7 @@ Just like symbolic variables can be used to directly interface with a structure, oprob[two_state_model.X1 + two_state_model.X2] ``` This can be used to form symbolic expressions using model quantities when a model has been created using the DSL (as an alternative to [@unpack] -(@ref ref)). Alternatively, [creating an observable](@ref ref), and then interface using its `Symbol` representation, is also possible. +(@ref ref)). Alternatively, [creating an observable](@ref dsl_advanced_options_observables), and then interface using its `Symbol` representation, is also possible. !!! warn With interfacing with a simulating structure using symbolic variables stored in a `ReactionSystem` model, ensure that the [model is complete](@ref ref). \ No newline at end of file From 8fd8cf6e1fe94684bd6df54d5c8d7b15c91b2676 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 28 May 2024 18:48:07 -0400 Subject: [PATCH 013/446] add misc missing references --- docs/src/model_creation/dsl_advanced.md | 2 +- docs/src/model_creation/dsl_basics.md | 2 +- .../examples/programmatic_generative_linear_pathway.md | 4 ++-- docs/src/model_creation/model_visualisation.md | 2 +- docs/src/model_creation/network_analysis.md | 2 +- docs/src/model_simulation/simulation_introduction.md | 2 +- docs/src/model_simulation/simulation_structure_interfacing.md | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/src/model_creation/dsl_advanced.md b/docs/src/model_creation/dsl_advanced.md index b302cf8341..3798edb1e5 100644 --- a/docs/src/model_creation/dsl_advanced.md +++ b/docs/src/model_creation/dsl_advanced.md @@ -28,7 +28,7 @@ end parameters(catalysis_sys) ``` !!! note - When declaring species using the `@species` option, the species symbol must be followed by `(t)`. The reason is that species are time-dependent variables, and this time-dependency must be explicitly specified ([designation of non-`t` dependant species is also possible](@ref ref)). + When declaring species using the `@species` option, the species symbol must be followed by `(t)`. The reason is that species are time-dependent variables, and this time-dependency must be explicitly specified ([designation of non-`t` dependant species is also possible](@ref dsl_advanced_options_ivs)). Similarly, the `@parameters` option can be used to explicitly designate something as a parameter: ```@example dsl_advanced_explicit_definitions diff --git a/docs/src/model_creation/dsl_basics.md b/docs/src/model_creation/dsl_basics.md index 78864457f5..7bc46f7ba9 100644 --- a/docs/src/model_creation/dsl_basics.md +++ b/docs/src/model_creation/dsl_basics.md @@ -267,7 +267,7 @@ Catalyst comes with the following predefined functions: - The activating/repressive Hill function: $hillar(X,Y,v,K,n) = v * (X^n)/(X^n + Y^n + K^n)$. ### [Time-dependant rates](@id dsl_description_nonconstant_rates_time) -Previously we have assumed that the rates are independent of the [time variable, $t$](@ref ref). However, time-dependent reactions are also possible. Here, simply use `t` to represent the time variable. E.g., to create a production/degradation model where the production rate decays as time progresses, we can use: +Previously we have assumed that the rates are independent of the time variable, $t$. However, time-dependent reactions are also possible. Here, simply use `t` to represent the time variable. E.g., to create a production/degradation model where the production rate decays as time progresses, we can use: ```@example dsl_basics rn_14 = @reaction_network begin kp/(1 + t), 0 --> P diff --git a/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md b/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md index ba052781be..ae2be87b48 100644 --- a/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md +++ b/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md @@ -1,5 +1,5 @@ # [Programmatic, generative, modelling of a linear pathway](@id programmatic_generative_linear_pathway) -This example will show how to use programmatic, generative, modelling to model a system implicitly. I.e. rather than listing all system reactions explicitly, the reactions are implicitly generated from a simple set of rules. This example is specifically designed to show how [programmatic modelling](@ref ref) enables *generative workflows* (demonstrating one of its advantages as compared to [DSL-based modelling](@ref dsl_description)). In our example, we will model linear pathways, so we will first introduce these. Next, we will model them first using the DSL, and then using a generative programmatic workflow. +This example will show how to use programmatic, generative, modelling to model a system implicitly. I.e. rather than listing all system reactions explicitly, the reactions are implicitly generated from a simple set of rules. This example is specifically designed to show how [programmatic modelling](@ref programmatic_CRN_construction) enables *generative workflows* (demonstrating one of its advantages as compared to [DSL-based modelling](@ref dsl_description)). In our example, we will model linear pathways, so we will first introduce these. Next, we will model them first using the DSL, and then using a generative programmatic workflow. ## [Linear pathways](@id programmatic_generative_linear_pathway_intro) Linear pathways consists of a series of species ($X_0$, $X_1$, $X_2$, ..., $X_n$) where each activates the subsequent one. These are often modelled through the following reaction system: @@ -75,7 +75,7 @@ plot!(sol_n10; idxs = :X10, label = "n = 10") ``` ## [Modelling linear pathways using programmatic, generative, modelling](@id programmatic_generative_linear_pathway_generative) -Above, we investigated the impact of linear pathways' lengths on their behaviours. Since the models were implemented using the DSL, we had to implement a new model for each pathway (in each case writing out all reactions). Here, we will instead show how [programmatic modelling](@ref ref) can be used to generate pathways of arbitrary lengths. +Above, we investigated the impact of linear pathways' lengths on their behaviours. Since the models were implemented using the DSL, we had to implement a new model for each pathway (in each case writing out all reactions). Here, we will instead show how [programmatic modelling](@ref programmatic_CRN_construction) can be used to generate pathways of arbitrary lengths. First, we create a function, `generate_lp`, which creates a linear pathway model of length `n`. It utilises [*vector variables*](@ref ref) to create an arbitrary number of species, and also creates an [observable](@ref ref) for the final species of the chain. ```@example programmatic_generative_linear_pathway_generative diff --git a/docs/src/model_creation/model_visualisation.md b/docs/src/model_creation/model_visualisation.md index 36f8d738ef..6efb71b4bb 100644 --- a/docs/src/model_creation/model_visualisation.md +++ b/docs/src/model_creation/model_visualisation.md @@ -64,7 +64,7 @@ savegraph(repressilator_graph, "repressilator_graph.png") rm("repressilator_graph.png") # hide ``` -Finally, a [network's reaction complexes](@ref ref) (and the reactions in between these) can be displayed using the `complexgraph(brusselator)` function: +Finally, a [network's reaction complexes](@ref network_analysis_reaction_complexes) (and the reactions in between these) can be displayed using the `complexgraph(brusselator)` function: ```@example visualisation_graphs complexgraph(brusselator) ``` diff --git a/docs/src/model_creation/network_analysis.md b/docs/src/model_creation/network_analysis.md index 0f212e2447..792ed5477d 100644 --- a/docs/src/model_creation/network_analysis.md +++ b/docs/src/model_creation/network_analysis.md @@ -101,7 +101,7 @@ Let's check these are equal symbolically isequal(odes, odes2) ``` -## Reaction complex representation +## [Reaction complex representation](@id network_analysis_reaction_complexes) We now introduce a further decomposition of the RRE ODEs, which has been used to facilitate analysis of a variety of reaction network properties. Consider a simple reaction system like diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index ed3056c616..00f757cc8a 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -267,7 +267,7 @@ nothing # hide ``` If the `@default_noise_scaling` option is used, that term is only applied to reactions *without* `noise_scaling` metadata. -While the `@default_noise_scaling` option is unavailable for [programmatically created models](@ref ref), the [`remake_reactionsystem`](@ref) function can be used to achieve a similar effect. +While the `@default_noise_scaling` option is unavailable for [programmatically created models](@ref programmatic_CRN_construction), the [`remake_reactionsystem`](@ref) function can be used to achieve a similar effect. ## [Performing jump simulations using stochastic chemical kinetics](@id simulation_intro_jumps) diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index f34d6b9808..aa70d51ed4 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -21,7 +21,7 @@ oprob = ODEProblem(cc_system, u0, tspan, ps) nothing # hide ``` -We can find a specie's (or [variable's](@ref ref)) initial condition value by simply indexing with the species of interest as input. Here we check the initial condition value of $C$: +We can find a species's (or [variable's](@ref ref)) initial condition value by simply indexing with the species of interest as input. Here we check the initial condition value of $C$: ```@example structure_indexing oprob[:C] ``` @@ -162,7 +162,7 @@ get_S(oprob) ``` ## [Interfacing using symbolic representations](@id simulation_structure_interfacing_symbolic_representation) -As [previously described](@ref ref), when e.g. [programmatic modelling is used](@ref ref), species and parameters can be represented as *symbolic variables*. These can be used to index a problem, just like symbol-based representations can. Here we create a simple [two-state model](@ref rbasic_CRN_library_two_statesef) programmatically, and use its symbolic variables to check, and update, an initial condition: +As [previously described](@ref ref), when e.g. [programmatic modelling is used](@ref programmatic_CRN_construction), species and parameters can be represented as *symbolic variables*. These can be used to index a problem, just like symbol-based representations can. Here we create a simple [two-state model](@ref rbasic_CRN_library_two_statesef) programmatically, and use its symbolic variables to check, and update, an initial condition: ```@example structure_indexing_symbolic_variables using Catalyst t = default_t() From 635e8244ece880a1ec165c8221d2f33890d6fcde Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 29 May 2024 10:22:26 -0400 Subject: [PATCH 014/446] up --- docs/pages.jl | 22 +-- .../catalyst_for_new_julia_users.md | 8 +- .../examples/ode_fitting_oscillation.md | 24 +-- .../optimization_ode_param_fitting.md | 2 +- .../structural_identifiability.md | 59 ++++--- docs/src/model_creation/dsl_advanced.md | 152 +++++++++--------- docs/src/model_creation/dsl_basics.md | 90 +++++------ .../examples/basic_CRN_library.md | 6 +- .../simulation_introduction.md | 34 ++-- .../model_simulation/simulation_plotting.md | 2 +- .../nonlinear_solve.md | 2 +- 11 files changed, 200 insertions(+), 201 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index a161f4fd00..3e5787a4db 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -15,10 +15,10 @@ pages = Any[ #"model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. # Loading and writing models to files. # Model visualisation. - "model_creation/network_analysis.md", + #"model_creation/network_analysis.md", "model_creation/chemistry_related_functionality.md", "Model creation examples" => Any[ - "model_creation/examples/basic_CRN_library.md", + #"model_creation/examples/basic_CRN_library.md", "model_creation/examples/programmatic_generative_linear_pathway.md", #"model_creation/examples/hodgkin_huxley_equation.md", #"model_creation/examples/smoluchowski_coagulation_equation.md" @@ -29,20 +29,20 @@ pages = Any[ # Simulation introduction. "model_simulation/simulation_plotting.md", "model_simulation/simulation_structure_interfacing.md", - "model_simulation/ensemble_simulations.md", + #"model_simulation/ensemble_simulations.md", # Stochastic simulation statistical analysis. - "model_simulation/ode_simulation_performance.md", - # ODE Performance considerations/advice. - # SDE Performance considerations/advice. - # Jump Performance considerations/advice. - # Finite state projection + # "model_simulation/ode_simulation_performance.md", + # # ODE Performance considerations/advice. + # # SDE Performance considerations/advice. + # # Jump Performance considerations/advice. + # # Finite state projection ], "Steady state analysis" => Any[ - "steady_state_functionality/homotopy_continuation.md", + # "steady_state_functionality/homotopy_continuation.md", "steady_state_functionality/nonlinear_solve.md", "steady_state_functionality/steady_state_stability_computation.md", - "steady_state_functionality/bifurcation_diagrams.md", - "steady_state_functionality/dynamical_systems.md" + # "steady_state_functionality/bifurcation_diagrams.md", + # "steady_state_functionality/dynamical_systems.md" ], "Inverse Problems" => Any[ # Inverse problems introduction. diff --git a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md index 8bf05f80ca..ab5c12ae01 100644 --- a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md +++ b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md @@ -80,8 +80,8 @@ The `@reaction_network` command is followed by the `begin` keyword, which is fol Here, we create a simple [*birth-death* model](@ref basic_CRN_library_bd), where a single species ($X$) is created at rate $b$, and degraded at rate $d$. The model is stored in the variable `rn`. ```@example ex2 rn = @reaction_network begin - b, 0 --> X - d, X --> 0 + b, 0 --> X + d, X --> 0 end ``` For more information on how to use the Catalyst model creator (also known as *the Catalyst DSL*), please read [the corresponding documentation](https://docs.sciml.ai/Catalyst/stable/catalyst_functionality/dsl_description/). @@ -152,8 +152,8 @@ Each reaction is also associated with a specific rate (corresponding to a parame We declare the model using the `@reaction_network` macro, and store it in the `sir_model` variable. ```@example ex2 sir_model = @reaction_network begin - b, S + I --> 2I - k, I --> R + b, S + I --> 2I + k, I --> R end ``` Note that the first reaction contains two different substrates (separated by a `+` sign). While there is only a single product (*I*), two copies of *I* are produced. The *2* in front of the product *I* denotes this. diff --git a/docs/src/inverse_problems/examples/ode_fitting_oscillation.md b/docs/src/inverse_problems/examples/ode_fitting_oscillation.md index 47f4623514..f5eaa4c65f 100644 --- a/docs/src/inverse_problems/examples/ode_fitting_oscillation.md +++ b/docs/src/inverse_problems/examples/ode_fitting_oscillation.md @@ -2,7 +2,7 @@ In this example we will use [Optimization.jl](https://github.com/SciML/Optimization.jl) to fit the parameters of an oscillatory system (the Brusselator) to data. Here, special consideration is taken to avoid reaching a local minimum. Instead of fitting the entire time series directly, we will start with fitting parameter values for the first period, and then use those as an initial guess for fitting the next (and then these to find the next one, and so on). Using this procedure is advantageous for oscillatory systems, and enables us to reach the global optimum. First, we fetch the required packages. -```@example pe1 +```@example pe_osc_example using Catalyst using OrdinaryDiffEq using Optimization @@ -11,7 +11,7 @@ using SciMLSensitivity # Required for `Optimization.AutoZygote()` automatic diff ``` Next, we declare our model, the Brusselator oscillator. -```@example pe1 +```@example pe_osc_example brusselator = @reaction_network begin A, ∅ --> X 1, 2X + Y --> 3X @@ -24,7 +24,7 @@ nothing # hide We simulate our model, and from the simulation generate sampled data points (to which we add noise). We will use this data to fit the parameters of our model. -```@example pe1 +```@example pe_osc_example u0 = [:X => 1.0, :Y => 1.0] tspan = (0.0, 30.0) @@ -37,7 +37,7 @@ nothing # hide ``` We can plot the real solution, as well as the noisy samples. -```@example pe1 +```@example pe_osc_example using Plots default(; lw = 3, framestyle = :box, size = (800, 400)) @@ -49,7 +49,7 @@ Next, we create a function to fit the parameters using the `ADAM` optimizer. For a given initial estimate of the parameter values, `pinit`, this function will fit parameter values, `p`, to our data samples. We use `tend` to indicate the time interval over which we fit the model. -```@example pe1 +```@example pe_osc_example function optimise_p(pinit, tend) function loss(p, _) newtimes = filter(<=(tend), sample_times) @@ -71,12 +71,12 @@ nothing # hide ``` Next, we will fit a parameter set to the data on the interval `(0, 10)`. -```@example pe1 +```@example pe_osc_example p_estimate = optimise_p([5.0, 5.0], 10.0) ``` We can compare this to the real solution, as well as the sample data -```@example pe1 +```@example pe_osc_example newprob = remake(prob; tspan = (0., 10.), p = p_estimate) sol_estimate = solve(newprob, Rosenbrock23()) plot(sol_real; color = [:blue :red], label = ["X real" "Y real"], linealpha = 0.2) @@ -88,7 +88,7 @@ plot!(sol_estimate; color = [:darkblue :darkred], linestyle = :dash, Next, we use this parameter estimate as the input to the next iteration of our fitting process, this time on the interval `(0, 20)`. -```@example pe1 +```@example pe_osc_example p_estimate = optimise_p(p_estimate, 20.) newprob = remake(prob; tspan = (0., 20.), p = p_estimate) sol_estimate = solve(newprob, Rosenbrock23()) @@ -101,20 +101,20 @@ plot!(sol_estimate; color = [:darkblue :darkred], linestyle = :dash, Finally, we use this estimate as the input to fit a parameter set on the full time interval of the sampled data. -```@example pe1 +```@example pe_osc_example p_estimate = optimise_p(p_estimate, 30.0) newprob = remake(prob; tspan = (0., 30.0), p = p_estimate) sol_estimate = solve(newprob, Rosenbrock23()) plot(sol_real; color = [:blue :red], label = ["X real" "Y real"], linealpha = 0.2) scatter!(sample_times, sample_vals'; color = [:blue :red], - label = ["Samples of X" "Samples of Y"], alpha = 0.4) + label = ["Samples of X" "Samples of Y"], alpha = 0.4) plot!(sol_estimate; color = [:darkblue :darkred], linestyle = :dash, label = ["X estimated" "Y estimated"], xlimit = tspan) ``` The final parameter estimate is then -```@example pe1 +```@example pe_osc_example p_estimate ``` which is close to the actual parameter set of `[1.0, 2.0]`. @@ -125,7 +125,7 @@ then extend the interval, is to avoid getting stuck in a local minimum. Here specifically, we chose our initial interval to be smaller than a full cycle of the oscillation. If we had chosen to fit a parameter set on the full interval immediately we would have obtained poor fit and an inaccurate estimate for the parameters. -```@example pe1 +```@example pe_osc_example p_estimate = optimise_p([5.0,5.0], 30.0) newprob = remake(prob; tspan = (0.0,30.0), p = p_estimate) diff --git a/docs/src/inverse_problems/optimization_ode_param_fitting.md b/docs/src/inverse_problems/optimization_ode_param_fitting.md index 742ed58ee0..6dd50c3e4a 100644 --- a/docs/src/inverse_problems/optimization_ode_param_fitting.md +++ b/docs/src/inverse_problems/optimization_ode_param_fitting.md @@ -40,7 +40,7 @@ using DiffEqParamEstim, Optimization ps_dummy = [:kB => 0.0, :kD => 0.0, :kP => 0.0] oprob = ODEProblem(rn, u0, (0.0, 10.0), ps_dummy) loss_function = build_loss_objective(oprob, Tsit5(), L2Loss(data_ts, data_vals), Optimization.AutoForwardDiff(); - maxiters = 10000, verbose = false, save_idxs = 4) + maxiters = 10000, verbose = false, save_idxs = 4) nothing # hide ``` To `build_loss_objective` we provide the following arguments: diff --git a/docs/src/inverse_problems/structural_identifiability.md b/docs/src/inverse_problems/structural_identifiability.md index 95e5de615a..289b14aa91 100644 --- a/docs/src/inverse_problems/structural_identifiability.md +++ b/docs/src/inverse_problems/structural_identifiability.md @@ -31,27 +31,27 @@ Global identifiability can be assessed using the `assess_identifiability` functi - Unidentifiable. To it, we provide our `ReactionSystem` model and a list of quantities that we are able to measure. Here, we consider a Goodwind oscillator (a simple 3-component model, where the three species $M$, $E$, and $P$ are produced and degraded, which may exhibit oscillations)[^2]. Let us say that we are able to measure the concentration of $M$, we then designate this using the `measured_quantities` argument. We can now assess identifiability in the following way: -```example si1 +```@example si1 using Catalyst, Logging, StructuralIdentifiability -goodwind_oscillator = @reaction_network begin +gwo = @reaction_network begin (pₘ/(1+P), dₘ), 0 <--> M (pₑ*M,dₑ), 0 <--> E (pₚ*E,dₚ), 0 <--> P end -assess_identifiability(goodwind_oscillator; measured_quantities=[:M], loglevel=Logging.Error) +assess_identifiability(gwo; measured_quantities = [:M], loglevel = Logging.Error) ``` -From the output, we find that `E(t)`, `pₑ`, and `pₚ` (the trajectory of $E$, and the production rates of $E$ and $P$, respectively) are non-identifiable. Next, `dₑ` and `dₚ` (the degradation rates of $E$ and $P$, respectively) are locally identifiable. Finally, `P(t)`, `M(t)`, `pₘ`, and `dₘ` (the trajectories of `P` and `M`, and the production and degradation rate of `M`, respectively) are all globally identifiable. We note that we also imported the Logging.jl package, and provided the `loglevel=Logging.Error` input argument. StructuralIdentifiability functions generally provide a large number of output messages. Hence, we will use this argument (which requires the Logging package) throughout this tutorial to decrease the amount of printed text. +From the output, we find that `E(t)`, `pₑ`, and `pₚ` (the trajectory of $E$, and the production rates of $E$ and $P$, respectively) are non-identifiable. Next, `dₑ` and `dₚ` (the degradation rates of $E$ and $P$, respectively) are locally identifiable. Finally, `P(t)`, `M(t)`, `pₘ`, and `dₘ` (the trajectories of `P` and `M`, and the production and degradation rate of `M`, respectively) are all globally identifiable. We note that we also imported the Logging.jl package, and provided the `loglevel = Logging.Error` input argument. StructuralIdentifiability functions generally provide a large number of output messages. Hence, we will use this argument (which requires the Logging package) throughout this tutorial to decrease the amount of printed text. Next, we also assess identifiability in the case where we can measure all three species concentrations: -```example si1 -assess_identifiability(goodwind_oscillator; measured_quantities=[:M, :P, :E], loglevel=Logging.Error) +```@example si1 +assess_identifiability(gwo; measured_quantities = [:M, :P, :E], loglevel = Logging.Error) ``` in which case all species trajectories and parameters become identifiable. ### Indicating known parameters In the previous case we assumed that all parameters are unknown, however, this is not necessarily true. If there are parameters with known values, we can supply these using the `known_p` argument. Providing this additional information might also make other, previously unidentifiable, parameters identifiable. Let us consider the previous example, where we measure the concentration of $M$ only, but now assume we also know the production rate of $E$ ($pₑ$): -```example si1 -assess_identifiability(gwo; measured_quantities=[:M], known_p=[:pₑ], loglevel=Logging.Error) +```@example si1 +assess_identifiability(gwo; measured_quantities = [:M], known_p = [:pₑ], loglevel = Logging.Error) ``` Not only does this turn the previously non-identifiable `pₑ` (globally) identifiable (which is obvious, given that its value is now known), but this additional information improve identifiability for several other network components. @@ -59,47 +59,46 @@ To, in a similar manner, indicate that certain initial conditions are known is a ### Providing non-trivial measured quantities Sometimes, ones may not have measurements of species, but rather some combinations of species (or possibly parameters). To account for this, `measured_quantities` accepts any algebraic expression (and not just single species). To form such expressions, species and parameters have to first be `@unpack`'ed from the model. Say that we have a model where an enzyme ($E$) is converted between an active and inactive form, which in turns activates the production of a product, $P$: -```example si2 -using Catalyst, StructuralIdentifiability # hide +```@example si1 enzyme_activation = @reaction_network begin (kA,kD), Eᵢ <--> Eₐ (Eₐ, d), 0 <-->P end ``` If we can measure the total amount of $E$ ($=Eᵢ+Eₐ$), as well as the amount of $P$, we can use the following to assess identifiability: -```example si2 +```@example si2 @unpack Eᵢ, Eₐ = enzyme_activation -assess_identifiability(enzyme_activation; measured_quantities=[Eᵢ+Eₐ, :P], loglevel=Logging.Error) +assess_identifiability(enzyme_activation; measured_quantities = [Eᵢ + Eₐ, :P], loglevel = Logging.Error) nothing # hide ``` ### Assessing identifiability for specified quantities only By default, StructuralIdentifiability assesses identifiability for all parameters and variables. It is, however, possible to designate precisely which quantities you want to check using the `funcs_to_check` option. This both includes selecting a smaller subset of parameters and variables to check, or defining customised expressions. Let us consider the Goodwind from previously, and say that we would like to check whether the production parameters ($pₘ$, $pₑ$, and $pₚ$) and the total amount of the three species ($P + M + E$) are identifiable quantities. Here, we would first unpack these (allowing us to form algebraic expressions) and then use the following code: -```example si1 -@unpack pₘ, pₑ, pₚ, M, E, P = goodwind_oscillator -assess_identifiability(goodwind_oscillator; measured_quantities=[:M], funcs_to_check=[pₘ, pₑ, pₚ, M + E + P], loglevel=Logging.Error) +```@example si1 +@unpack pₘ, pₑ, pₚ, M, E, P = gwo +assess_identifiability(gwo; measured_quantities = [:M], funcs_to_check = [pₘ, pₑ, pₚ, M + E + P], loglevel = Logging.Error) nothing # hide ``` ### Probability of correctness The identifiability methods used can, in theory, produce erroneous results. However, it is possible to adjust the lower bound for the probability of correctness using the argument `prob_threshold` (by default set to `0.99`, that is, at least a $99\%$ chance of correctness). We can e.g. increase the bound through: -```example si2 -assess_identifiability(goodwind_oscillator; measured_quantities=[:M], prob_threshold=0.999, loglevel=Logging.Error) +```@example si1 +assess_identifiability(gwo; measured_quantities=[:M], prob_threshold = 0.999, loglevel = Logging.Error) nothing # hide ``` giving a minimum bound of $99.9\%$ chance of correctness. In practise, the bounds used by StructuralIdentifiability are very conservative, which means that while the minimum guaranteed probability of correctness in the default case is $99\%$, in practise it is much higher. While increasing the value of `prob_threshold` increases the certainty of correctness, it will also increase the time required to assess identifiability. ## Local identifiability analysis Local identifiability can be assessed through the `assess_local_identifiability` function. While this is already determined by `assess_identifiability`, assessing local identifiability only has the advantage that it is easier to compute. Hence, there might be models where global identifiability analysis fails (or takes a prohibitively long time), where instead `assess_local_identifiability` can be used. This function takes the same inputs as `assess_identifiability` and returns, for each quantity, `true` if it is locally identifiable (or `false` if it is not). Here, for the Goodwind oscillator, we assesses it for local identifiability only: -```example si1 -assess_local_identifiability(goodwind_oscillator; measured_quantities=[:M], loglevel=Logging.Error) +```@example si1 +assess_local_identifiability(gwo; measured_quantities = [:M], loglevel = Logging.Error) ``` We note that the results are consistent with those produced by `assess_identifiability` (with globally or locally identifiable quantities here all being assessed as at least locally identifiable). ## Finding identifiable functions Finally, StructuralIdentifiability provides the `find_identifiable_functions` function. Rather than determining the identifiability of each parameter and unknown of the model, it finds a set of identifiable functions, such as any other identifiable expression of the model can be generated by these. Let us again consider the Goodwind oscillator, using the `find_identifiable_functions` function we find that identifiability can be reduced to five globally identifiable expressions: -```example si1 -find_identifiable_functions(goodwind_oscillator; measured_quantities=[:M], loglevel=Logging.Error) +```@example si1 +find_identifiable_functions(gwo; measured_quantities = [:M], loglevel = Logging.Error) ``` Again, these results are consistent with those produced by `assess_identifiability`. There, `pₑ` and `pₚ` where found to be globally identifiable. Here, they correspond directly to identifiable expressions. The remaining four parameters (`pₘ`, `dₘ`, `dₑ`, and `dₚ`) occur as part of more complicated composite expressions. @@ -107,34 +106,34 @@ Again, these results are consistent with those produced by `assess_identifiabili ## Creating StructuralIdentifiability compatible ODE models from Catalyst `ReactionSystem`s While the functionality described above covers the vast majority of analysis that user might want to perform, the StructuralIdentifiability package supports several additional features. While these does not have inherent Catalyst support, we do provide the `make_si_ode` function to simplify their use. Similar to the previous functions, it takes a `ReactionSystem`, lists of measured quantities, and known parameter values. The output is a [ODE of the standard form supported by StructuralIdentifiability](https://docs.sciml.ai/StructuralIdentifiability/stable/tutorials/creating_ode/#Defining-the-model-using-@ODEmodel-macro). It can be created using the following syntax: -```example si1 -si_ode = make_si_ode(goodwind_oscillator; measured_quantities=[:M]) +```@example si1 +si_ode = make_si_ode(gwo; measured_quantities = [:M]) nothing # hide ``` and then used as input to various StructuralIdentifiability functions. In the following example we use StructuralIdentifiability's `print_for_DAISY` function, printing the model as an expression that can be used by the [DAISY](https://daisy.dei.unipd.it/) software for identifiability analysis[^3]. -```example si1 +```@example si1 print_for_DAISY(si_ode) nothing # hide ``` ## Notes on systems with conservation laws Several reaction network models, such as -```example si2 +```@example si3 using Catalyst, Logging, StructuralIdentifiability # hide rs = @reaction_network begin - (k1,k2), X1 <--> X2 + (k1,k2), X1 <--> X2 end ``` contain conservation laws (in this case $Γ = X1 + X2$, where $Γ = X1(0) + X2(0)$ is a constant). Because the presence of such conservation laws makes structural identifiability analysis prohibitively computationally expensive (for all but the simplest of cases), these are automatically eliminated by Catalyst (removing one ODE from the resulting ODE system for each conservation law). For the `assess_identifiability` and `assess_local_identifiability` functions, this will be unnoticed by the user. However, for the `find_identifiable_functions` and `make_si_ode` functions, this may result in one, or several, parameters of the form `Γ[i]` (where `i` is an integer) appearing in the produced expressions. These correspond to the conservation law constants and can be found through -```example si2 +```@example si3 conservedequations(rs) ``` E.g. if you run: -```example si2 +```@example si3 find_identifiable_functions(rs; measured_quantities = [:X1, :X2]) ``` we see that `Γ[1]` (`= X1(0) + X2(0)`) is detected as an identifiable expression. If we want to disable this feature for any function, we can use the `remove_conserved = false` option: -```example si2 +```@example si3 find_identifiable_functions(rs; measured_quantities = [:X1, :X2], remove_conserved = false) ``` @@ -144,7 +143,7 @@ Structural identifiability cannot currently be applied to systems with parameter rn = @reaction_network begin (hill(X,v,K,n),d), 0 <--> X end -assess_identifiability(rn; measured_quantities=[:X]) +assess_identifiability(rn; measured_quantities = [:X]) ``` is currently not possible. Hopefully this will be a supported feature in the future. For now, such expressions will have to be rewritten to not include such exponents. For some cases, e.g. `10^k` this is trivial. However, it is also possible generally (but more involved and often includes introducing additional variables). diff --git a/docs/src/model_creation/dsl_advanced.md b/docs/src/model_creation/dsl_advanced.md index 3798edb1e5..172c8e76d8 100644 --- a/docs/src/model_creation/dsl_advanced.md +++ b/docs/src/model_creation/dsl_advanced.md @@ -12,7 +12,7 @@ using Catalyst Previously, we mentioned that the DSL automatically determines which symbols correspond to species and which to parameters. This is done by designating everything that appears as either a substrate or a product as a species, and all remaining quantities as parameters (i.e. those only appearing within rates or [stoichiometric constants](@ref dsl_description_stoichiometries_parameters)). Sometimes, one might want to manually override this default behaviour for a given symbol. I.e. consider the following model, where the conversion of a protein `P` from its inactive form (`Pᵢ`) to its active form (`Pₐ`) is catalysed by an enzyme (`E`). Using the most natural description: ```@example dsl_advanced_explicit_definitions catalysis_sys = @reaction_network begin - k*E, Pᵢ --> Pₐ + k*E, Pᵢ --> Pₐ end ``` `E` (as well as `k`) will be considered a parameter, something we can confirm directly: @@ -22,8 +22,8 @@ parameters(catalysis_sys) If we want `E` to be considered a species, we can designate this using the `@species` option: ```@example dsl_advanced_explicit_definitions catalysis_sys = @reaction_network begin - @species E(t) - k*E, Pᵢ --> Pₐ + @species E(t) + k*E, Pᵢ --> Pₐ end parameters(catalysis_sys) ``` @@ -33,8 +33,8 @@ parameters(catalysis_sys) Similarly, the `@parameters` option can be used to explicitly designate something as a parameter: ```@example dsl_advanced_explicit_definitions catalysis_sys = @reaction_network begin - @parameters k - k*E, Pᵢ --> Pₐ + @parameters k + k*E, Pᵢ --> Pₐ end ``` Here, while `k` is explicitly defined as a parameter, no information is provided about `E`. Hence, the default case will be used (setting `E` to a parameter). The `@species` and `@parameter` options can be used simultaneously (although a quantity cannot be declared *both* as a species and a parameter). They may be followed by a full list of all species/parameters, or just a subset. @@ -44,32 +44,32 @@ While designating something which would default to a parameter as a species is s Rather than listing all species/parameters on a single line after the options, a `begin ... end` block can be used (listing one species/parameter on each line). E.g. in the following example we use this notation to explicitly designate all species and parameters of the system: ```@example dsl_advanced_explicit_definitions catalysis_sys = @reaction_network begin - @species begin - E(t) - Pᵢ(t) - Pₐ(t) - end - @parameters begin - k - end - k*E, Pᵢ --> Pₐ + @species begin + E(t) + Pᵢ(t) + Pₐ(t) + end + @parameters begin + k + end + k*E, Pᵢ --> Pₐ end ``` A side-effect of using the `@species` and `@parameter` options is that they specify *the order in which the species and parameters are stored*. I.e. lets check the order of the parameters in the parameters in the following dimerisation model: ```@example dsl_advanced_explicit_definitions dimerisation = @reaction_network begin - (p,d), 0 <--> X - (kB,kD), 2X <--> X2 + (p,d), 0 <--> X + (kB,kD), 2X <--> X2 end parameters(dimerisation) ``` The default order is typically equal to the order with which the parameters (or species) are encountered in the DSL (this is, however, not guaranteed). If we specify the parameters using `@parameters`, the order used within the option is used instead: ```@example dsl_advanced_explicit_definitions dimerisation = @reaction_network begin - @parameters kB kD p d - (p,d), 0 <--> X - (kB,kD), 2X <--> X2 + @parameters kB kD p d + (p,d), 0 <--> X + (kB,kD), 2X <--> X2 end parameters(dimerisation) ``` @@ -93,9 +93,9 @@ When declaring species/parameters using the `@species` and `@parameters` options ```@example dsl_advanced_defaults using Catalyst # hide rn = @reaction_network begin - @species X(t)=1.0 - @parameters p=1.0 d=0.1 - (p,d), 0 <--> X + @species X(t)=1.0 + @parameters p=1.0 d=0.1 + (p,d), 0 <--> X end ``` Next, if we simulate the model, we do not need to provide values for species or parameters that have default values. In this case all have default values, so both `u0` and `ps` can be empty vectors: @@ -120,8 +120,8 @@ It is also possible to declare a model with default values for only some initial ```@example dsl_advanced_defaults using Catalyst # hide rn = @reaction_network begin - @species X(t)=1.0 - (p,d), 0 <--> X + @species X(t)=1.0 + (p,d), 0 <--> X end tspan = (0.0, 10.0) @@ -136,9 +136,9 @@ API for checking the default values of species and parameters can be found [here In the previous section, we designated default values for initial conditions and parameters. However, the right-hand side of the designation accepts any valid expression (not only numeric values). While this can be used to set up some advanced default values, the most common use case is to designate a species's initial condition as a parameter. E.g. in the following example we represent the initial condition of `X` using the parameter `X₀`. ```@example dsl_advanced_defaults rn = @reaction_network begin - @species X(t)=X₀ - @parameters X₀ - (p,d), 0 <--> X + @species X(t)=X₀ + @parameters X₀ + (p,d), 0 <--> X end ``` Please note that as the parameter `X₀` does not occur as part of any reactions, Catalyst's DSL cannot infer whether it is a species or a parameter. This must hence be explicitly declared. We can now simulate our model while providing `X`'s value through the `X₀` parameter: @@ -166,41 +166,41 @@ Whenever a species/parameter is declared using the `@species`/`@parameters` opti ```@example dsl_advanced_metadata using Catalyst # hide two_state_system = @reaction_network begin - @species Xi(t) [description="The X's inactive form"] Xa(t) [description="The X's active form"] - @parameters kA [description="X's activation rate"] kD [description="X's deactivation rate"] - (ka,kD), Xi <--> Xa + @species Xi(t) [description="The X's inactive form"] Xa(t) [description="The X's active form"] + @parameters kA [description="X's activation rate"] kD [description="X's deactivation rate"] + (ka,kD), Xi <--> Xa end ``` A metadata can be given to only a subset of a system's species/parameters, and a quantity can be given several metadata entries. To give several metadata, separate each by a `,`. Here we only provide a description for `kA`, for which we also provide a [`bounds` metadata](@ref https://docs.sciml.ai/ModelingToolkit/dev/basics/Variable_metadata/#Bounds), ```@example dsl_advanced_metadata two_state_system = @reaction_network begin - @parameters kA [description="X's activation rate", bounds=(0.01,10.0)] - (ka,kD), Xi <--> Xa + @parameters kA [description="X's activation rate", bounds=(0.01,10.0)] + (ka,kD), Xi <--> Xa end ``` It is possible to add both default values and metadata to a parameter/species. In this case, first provide the default value, and next the metadata. I.e. to in the above example set $kA$'s default value to $1.0$ we use ```@example dsl_advanced_metadata two_state_system = @reaction_network begin - @parameters kA=1.0 [description="X's activation rate", bounds=(0.01,10.0)] - (ka,kD), Xi <--> Xa + @parameters kA=1.0 [description="X's activation rate", bounds=(0.01,10.0)] + (ka,kD), Xi <--> Xa end ``` When designating metadata for species/parameters in `begin ... end` blocks the syntax changes slightly. Here, a `,` must be inserted before the metadata (but after any potential default value). I.e. a version of the previous example can be written as ```@example dsl_advanced_metadata two_state_system = @reaction_network begin - @parameters begin - kA, [description="X's activation rate", bounds=(0.01,10.0)] - kD = 1.0, [description="X's deactivation rate"] - end - (kA,kD), Xi <--> Xa + @parameters begin + kA, [description="X's activation rate", bounds=(0.01,10.0)] + kD = 1.0, [description="X's deactivation rate"] + end + (kA,kD), Xi <--> Xa end ``` -Each metadata has its own getter functions. E.g. we can get the description of the parameter `kA` using `getdescription` (here we use [system indexing](@ref ref) to access the parameter): +Each metadata has its own getter functions. E.g. we can get the description of the parameter `kA` using `ModelingToolkit.getdescription` (here we use [system indexing](@ref ref) to access the parameter): ```@example dsl_advanced_metadata -getdescription(two_state_system.kA) +ModelingToolkit.getdescription(two_state_system.kA) ``` It is not possible for the user to directly designate their own metadata. These have to first be added to Catalyst. Doing so is somewhat involved, and described in detail [here](@ref ref). A full list of metadata that can be used for species and/or parameters can be found [here](@ref ref). @@ -211,8 +211,8 @@ Catalyst enables the designation of parameters as `constantspecies`. These param ```@example dsl_advanced_constant_species using Catalyst # hide rn = @reaction_network begin - @parameters X [isconstantspecies=true] - k, X --> Xᴾ + @parameters X [isconstantspecies=true] + k, X --> Xᴾ end ``` We can confirm that $Xᴾ$ is the only species of the system: @@ -222,7 +222,7 @@ species(rn) Here, the produced model is actually identical to if $X$ had simply been a parameter in the reaction's rate: ```@example dsl_advanced_constant_species rn = @reaction_network begin - k*X, 0 --> Xᴾ + k*X, 0 --> Xᴾ end ``` @@ -279,14 +279,14 @@ Each reaction network model has a name. It can be accessed using the `nameof` fu ```@example dsl_advanced_names using Catalyst # hide rn = @reaction_network begin - (p,d), 0 <--> X + (p,d), 0 <--> X end nameof(rn) ``` A specific name can be given as an argument between the `@reaction_network` and the `begin`. E.g. to name a network `my_network` we can use: ```@example dsl_advanced_names rn = @reaction_network my_network begin - (p,d), 0 <--> X + (p,d), 0 <--> X end nameof(rn) ``` @@ -294,10 +294,10 @@ nameof(rn) A consequence of generic names being used by default is that networks, even if seemingly identical, by default are not. E.g. ```@example dsl_advanced_names rn1 = @reaction_network begin - (p,d), 0 <--> X + (p,d), 0 <--> X end rn2 = @reaction_network begin - (p,d), 0 <--> X + (p,d), 0 <--> X end rn1 == rn2 ``` @@ -308,10 +308,10 @@ nameof(rn1) == nameof(rn2) By designating the networks to have the same name, however, identity is achieved. ```@example dsl_advanced_names rn1 = @reaction_network my_network begin - (p,d), 0 <--> X + (p,d), 0 <--> X end rn2 = @reaction_network my_network begin - (p,d), 0 <--> X + (p,d), 0 <--> X end rn1 == rn2 ``` @@ -325,11 +325,11 @@ Let us consider a model where two species (`X` and `Y`) can bind to form a compl ```@example dsl_advanced_observables using Catalyst # hide rn = @reaction_network begin - @observables begin - Xtot ~ X + XY - Ytot ~ Y + XY - end - (kB,kD), X + Y <--> XY + @observables begin + Xtot ~ X + XY + Ytot ~ Y + XY + end + (kB,kD), X + Y <--> XY end ``` We can now simulate our model using normal syntax (initial condition values for observables should not, and can not, be provided): @@ -357,8 +357,8 @@ to plot the observables (rather than the species). Observables can be defined using complicated expressions containing species, parameters, and [variables](@ref ref) (but not other observables). In the following example (which uses a [parametric stoichiometry](@ref dsl_description_stoichiometries_parameters)) `X` polymerises to form a complex `Xn` containing `n` copies of `X`. Here, we create an observable describing the total number of `X` molecules in the system: ```@example dsl_advanced_observables rn = @reaction_network begin - @observables Xtot ~ X + n*Xn - (kB,kD), n*X <--> Xn + @observables Xtot ~ X + n*Xn + (kB,kD), n*X <--> Xn end nothing # hide ``` @@ -368,8 +368,8 @@ nothing # hide [Metadata](@ref dsl_advanced_options_species_and_parameters_metadata) can be supplied to an observable directly after its declaration (but before its formula). If so, the metadata must be separated from the observable with a `,`, and the observable plus the metadata encapsulated by `()`. E.g. to add a [description metadata](@ref dsl_advanced_options_species_and_parameters_metadata) to our observable we can use ```@example dsl_advanced_observables rn = @reaction_network begin - @observables (Xtot, [description="The total amount of X in the system."]) ~ X + n*Xn - (kB,kD), n*X <--> Xn + @observables (Xtot, [description="The total amount of X in the system."]) ~ X + n*Xn + (kB,kD), n*X <--> Xn end nothing # hide ``` @@ -377,9 +377,9 @@ nothing # hide Observables are by default considered [variables](@ref ref) (not species). To designate them as a species, they can be pre-declared using the `@species` option. I.e. Here `Xtot` becomes a species: ```@example dsl_advanced_observables rn = @reaction_network begin - @species Xtot(t) - @observables Xtot ~ X + n*Xn - (kB,kD), n*X <--> Xn + @species Xtot(t) + @observables Xtot ~ X + n*Xn + (kB,kD), n*X <--> Xn end nothing # hide ``` @@ -396,8 +396,8 @@ As [described elsewhere](@ref ref), Catalyst's `ReactionSystem` models depend on ```@example dsl_advanced_ivs using Catalyst # hide rn = @reaction_network begin - @ivs τ - (ka,kD), Xi <--> Xa + @ivs τ + (ka,kD), Xi <--> Xa end nothing # hide ``` @@ -409,19 +409,19 @@ species(rn) It is possible to designate several independent variables using `@ivs`. If so, the first one is considered the default (time) independent variable, while the following one(s) are considered spatial independent variable(s). If we want some species to depend on a non-default independent variable, this has to be explicitly declared: ```@example dsl_advanced_ivs rn = @reaction_network begin - @ivs τ x - @species X(τ) Y(x) - (p1,d1), 0 <--> X - (p2,d2), 0 <--> Y + @ivs τ x + @species X(τ) Y(x) + (p1,d1), 0 <--> X + (p2,d2), 0 <--> Y end species(rn) ``` It is also possible to have species which depends on several independent variables: ```@example dsl_advanced_ivs rn = @reaction_network begin - @ivs t x - @species Xi(t,x) Xa(t,x) - (ka,kD), Xi <--> Xa + @ivs t x + @species Xi(t,x) Xa(t,x) + (ka,kD), Xi <--> Xa end species(rn) ``` @@ -435,8 +435,8 @@ It is possible to supply reactions with *metadata*, containing some additional i ```@example dsl_advanced_reaction_metadata using Catalyst # hide bd_model = @reaction_network begin - p, 0 --> X, [description="A production reaction"] - d, X --> 0, [description="A degradation reaction"] + p, 0 --> X, [description="A production reaction"] + d, X --> 0, [description="A degradation reaction"] end nothing # hide ``` @@ -444,7 +444,7 @@ nothing # hide When [bundling reactions](@ref dsl_description_reaction_bundling), reaction metadata can be bundled using the same rules as rates. Bellow we re-declare our birth-death process, but on a single line: ```@example dsl_advanced_reaction_metadata bd_model = @reaction_network begin - (p,d), 0 --> X, ([description="A production reaction"], [description="A degradation reaction"]) + (p,d), 0 --> X, ([description="A production reaction"], [description="A degradation reaction"]) end nothing # hide ``` @@ -452,8 +452,8 @@ nothing # hide Here we declare a model where we also provide a `misc` metadata (which can hold any quantity we require) to our birth reaction: ```@example dsl_advanced_reaction_metadata bd_model = @reaction_network begin - p, 0 --> X, [description="A production reaction", misc=:value] - d, X --> 0, [description="A degradation reaction"] + p, 0 --> X, [description="A production reaction", misc=:value] + d, X --> 0, [description="A degradation reaction"] end nothing # hide ``` diff --git a/docs/src/model_creation/dsl_basics.md b/docs/src/model_creation/dsl_basics.md index 7bc46f7ba9..5d4b9ac51e 100644 --- a/docs/src/model_creation/dsl_basics.md +++ b/docs/src/model_creation/dsl_basics.md @@ -12,8 +12,8 @@ using Catalyst The DSL is initiated through the `@reaction_network` macro, which is followed by one line for each reaction. Each reaction consists of a *rate*, followed lists first of the substrates and next of the products. E.g. a [Michaelis-Menten enzyme kinetics system](@ref basic_CRN_library_mm) can be written as ```@example dsl_basics_intro rn = @reaction_network begin - (kB,kD), S + E <--> SE - kP, SE --> P + E + (kB,kD), S + E <--> SE + kP, SE --> P + E end ``` Here, `<-->` is used to create a bi-directional reaction (with forward rate `kP` and backward rate `kD`). Next, the model (stored in the variable `rn`) can be used as input to various types of [simulations](@ref simulation_intro). @@ -23,8 +23,8 @@ The basic syntax of the DSL is ```@example dsl_basics using Catalyst # hide rn = @reaction_network begin - 2.0, X --> Y - 1.0, Y --> X + 2.0, X --> Y + 1.0, Y --> X end ``` Here, you start with `@reaction_network begin`, next list all of the model's reactions, and finish with `end`. Each reaction consists of @@ -40,16 +40,16 @@ Finally, `rn = ` is used to store the model in the variable `rn` (a normal Julia Typically, the rates are not constants, but rather parameters (which values can be set e.g. at [the beginning of each simulation](@ref simulation_intro_ODEs)). To set parametric rates, simply use whichever symbol you wish to represent your parameter with. E.g. to set the above rates to `a` and `b`, we use: ```@example dsl_basics rn1 = @reaction_network begin - a, X --> Y - b, Y --> X + a, X --> Y + b, Y --> X end ``` Here we have used single-character symbols to designate all species and parameters. Multi-character symbols, however, are also permitted. E.g. we could call the rates `kX` and `kY`: ```@example dsl_basics rn1 = @reaction_network begin - kX, X --> Y - kY, Y --> X + kX, X --> Y + kY, Y --> X end nothing # hide ``` @@ -61,14 +61,14 @@ Generally, anything that is a [permitted Julia variable name](@id https://docs.j Previously, our reactions have had a single substrate and a single product. However, reactions with multiple substrates and/or products are possible. Here, all the substrates (or products) are listed and separated by a `+`. E.g. to create a model where `X` and `Y` bind (at rate `kB`) to form `XY` (which then can dissociate, at rate `kD`, to form `XY`) we use: ```@example dsl_basics rn2 = @reaction_network begin - kB, X + Y --> XY - kD, XY --> X + Y + kB, X + Y --> XY + kD, XY --> X + Y end ``` Reactions can have any number of substrates and products, and their names do not need to have any relationship to each other, as demonstrated by the following mock model: ```@example dsl_basics rn3 = @reaction_network begin - k, X + Y + Z --> A + B + C + D + k, X + Y + Z --> A + B + C + D end ``` @@ -76,8 +76,8 @@ end Some reactions have no products, in which case the substrate(s) are degraded (i.e. removed from the system). To denote this, set the reaction's right-hand side to `0`. Similarly, some reactions have no substrates, in which case the product(s) are produced (i.e. added to the system). This is denoted by setting the left-hand side to `0`. E.g. to create a model where a single species `X` is both created (in the first reaction) and degraded (in a second reaction), we use: ```@example dsl_basics rn4 = @reaction_network begin - p, 0 --> X - d, X --> 0 + p, 0 --> X + d, X --> 0 end ``` @@ -85,8 +85,8 @@ end Reactions may include multiple copies of the same reactant (i.e. a substrate or a product). To specify this, the reactant is preceded by a number indicating its number of copies (also called the reactant's *stoichiometry*). E.g. to create a model where two copies of `X` dimerise to form `X2` (which then dissociate back to two `X` copies) we use: ```@example dsl_basics rn5 = @reaction_network begin - kB, 2X --> X2 - kD, X2 --> 2X + kB, 2X --> X2 + kD, X2 --> 2X end ``` Reactants whose stoichiometries are not defined are assumed to have stoichiometry `1`. Any integer number can be used, furthermore, [decimal numbers and parameters can also be used as stoichiometries](@ref dsl_description_stoichiometries). A discussion of non-unitary (i.e. not equal to `1`) stoichiometries affecting the created model can be found [here](@ref ref). @@ -94,8 +94,8 @@ Reactants whose stoichiometries are not defined are assumed to have stoichiometr Stoichiometries can be combined with `()` to define them for multiple reactants. Here, the following (mock) model declares the same reaction twice, both with and without this notation: ```@example dsl_basics rn6 = @reaction_network begin - k, 2X + 3(Y + 2Z) --> 5(V + W) - k, 2X + 3Y + 6Z --> 5V + 5W + k, 2X + 3(Y + 2Z) --> 5(V + W) + k, 2X + 3Y + 6Z --> 5V + 5W end nothing # hide ``` @@ -106,14 +106,14 @@ nothing # hide As is the case for the following two-state model: ```@example dsl_basics rn7 = @reaction_network begin - k1, X1 --> X2 - k2, X2 --> X1 + k1, X1 --> X2 + k2, X2 --> X1 end ``` it is common that reactions occur in both directions (so-called *bi-directional* reactions). Here, it is possible to bundle the reactions into a single line by using the `<-->` arrow. When we do this, the rate term must include two separate rates (one for each direction, these are enclosed by a `()` and separated by a `,`). I.e. the two-state model can be declared using: ```@example dsl_basics rn7 = @reaction_network begin - (k1,k2), X1 <--> X2 + (k1,k2), X1 <--> X2 end ``` Here, the first rate (`k1`) denotes the *forward rate* and the second rate (`k2`) the *backwards rate*. @@ -121,13 +121,13 @@ Here, the first rate (`k1`) denotes the *forward rate* and the second rate (`k2` Catalyst also permits writing pure backwards reactions. These use identical syntax to forward reactions, but with the `<--` arrow: ```@example dsl_basics rn8 = @reaction_network begin - k, X <-- Y + k, X <-- Y end ``` Here, the substrate(s) are on the right-hand side and the product(s) are on the left-hand side. Hence, the above model can be written identically using: ```@example dsl_basics rn8 = @reaction_network begin - k, Y --> X + k, Y --> X end ``` Generally, using forward reactions is clearer than backwards ones, with the latter typically never being used. @@ -136,49 +136,49 @@ Generally, using forward reactions is clearer than backwards ones, with the latt There exist additional situations where models contain similar reactions (e.g. systems where all system components degrade at identical rates). Reactions which share either rates, substrates, or products can be bundled into a single line. Here, the parts which are different for the reactions are written using `(,)` (containing one separate expression for each reaction). E.g., let us consider the following model where species `X` and `Y` both degrade at the rate `d`: ```@example dsl_basics rn8 = @reaction_network begin - d, X --> 0 - d, Y --> 0 + d, X --> 0 + d, Y --> 0 end ``` These share both their rates (`d`) and products (`0`), however, the substrates are different (`X` and `Y`). Hence, the reactions can be bundled into a single line using the common rate and product expression while providing separate substrate expressions: ```@example dsl_basics rn8 = @reaction_network begin - d, (X,Y) --> 0 + d, (X,Y) --> 0 end ``` This declaration of the model is identical to the previous one. Reactions can share any subset of the rate, substrate, and product expression (the cases where they share all or none, however, do not make sense to use). I.e. if the two reactions also have different degradation rates: ```@example dsl_basics rn9 = @reaction_network begin - dX, X --> 0 - dY, Y --> 0 + dX, X --> 0 + dY, Y --> 0 end ``` This can be represented using: ```@example dsl_basics rn9 = @reaction_network begin - (dX,dY), (X,Y) --> 0 + (dX,dY), (X,Y) --> 0 end ``` It is possible to use bundling for any number of reactions. E.g. in the following model we bundle the conversion of a species $X$ between its various forms (where all reactions use the same rate $k$): ```@example dsl_basics rn10 = @reaction_network begin - k, (X0,X1,X2,X3) --> (X1,X2,X3,X4) + k, (X0,X1,X2,X3) --> (X1,X2,X3,X4) end ``` It is possible to combine bundling with bi-directional reactions. In this case, the rate is first split into the forward and backwards rates. These may then (or may not) indicate several rates. We exemplify this using the two following two (identical) networks, created with and without bundling. ```@example dsl_basics rn11 = @reaction_network begin - kf, S --> P1 - kf, S --> P2 - kb_1, P1 --> S - kb_2, P2 --> S + kf, S --> P1 + kf, S --> P2 + kb_1, P1 --> S + kb_2, P2 --> S end ``` ```@example dsl_basics rn11 = @reaction_network begin - (kf, (kb_1, kb_2)), S <--> (P1,P2) + (kf, (kb_1, kb_2)), S <--> (P1,P2) end ``` @@ -227,9 +227,9 @@ Here, while these models will generate identical ODE, SDE, and jump simulations, Above we used a simple example where the rate was the product of a species and a parameter. However, any valid Julia expression of parameters, species, and values can be used. E.g the following is a valid model: ```@example dsl_basics rn_14 = @reaction_network begin - 2.0 + X^2, 0 --> X + Y - k1 + k2^k3, X --> ∅ - pi * X/(sqrt(2) + Y), Y → ∅ + 2.0 + X^2, 0 --> X + Y + k1 + k2^k3, X --> ∅ + pi * X/(sqrt(2) + Y), Y → ∅ end ``` @@ -320,8 +320,8 @@ Julia permits any Unicode characters to be used in variable names, thus Catalyst Previously, we described how `0` could be used to [create degradation or production reactions](@ref dsl_description_reactions_degradation_and_production). Catalyst permits the user to instead use the `∅` symbol. E.g. the production/degradation system can alternatively be written as: ```@example dsl_basics rn4 = @reaction_network begin - p, ∅ --> X - d, X --> ∅ + p, ∅ --> X + d, X --> ∅ end ``` @@ -334,8 +334,8 @@ Catalyst uses `-->`, `<-->`, and `<--` to denote forward, bi-directional, and ba E.g. the production/degradation system can alternatively be written as: ```@example dsl_basics rn4 = @reaction_network begin - p, ∅ → X - d, X → ∅ + p, ∅ → X + d, X → ∅ end ``` @@ -349,10 +349,10 @@ A range of possible characters are available which can be incorporated into spec An example of how this can be used to create a neat-looking model can be found in [Schwall et al. (2021)](https://www.embopress.org/doi/full/10.15252/msb.20209832) where it was used to model a sigma factor V circuit in the bacteria *Bacillus subtilis*: ```@example dsl_basics σᵛ_model = @reaction_network begin - v₀ + hill(σᵛ,v,K,n), ∅ → σᵛ + A - kdeg, (σᵛ, A, Aσᵛ) → ∅ - (kB,kD), A + σᵛ ↔ Aσᵛ - L, Aσᵛ → σᵛ + v₀ + hill(σᵛ,v,K,n), ∅ → σᵛ + A + kdeg, (σᵛ, A, Aσᵛ) → ∅ + (kB,kD), A + σᵛ ↔ Aσᵛ + L, Aσᵛ → σᵛ end nothing # hide ``` diff --git a/docs/src/model_creation/examples/basic_CRN_library.md b/docs/src/model_creation/examples/basic_CRN_library.md index 41e2a705f8..3e75f73700 100644 --- a/docs/src/model_creation/examples/basic_CRN_library.md +++ b/docs/src/model_creation/examples/basic_CRN_library.md @@ -86,9 +86,9 @@ Catalyst has special methods for working with conserved quantities, which are de ```@example crn_library_michaelis_menten using Catalyst mm_system = @reaction_network begin - kB, S + E --> SE - kD, SE --> S + E - kP, SE --> P + E + kB, S + E --> SE + kD, SE --> S + E + kP, SE --> P + E end ``` Next, we perform ODE, SDE, and jump simulations of the model: diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index 00f757cc8a..7f5eb0528a 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -85,7 +85,7 @@ To perform any simulation, we must first define our model, as well as the simula ```@example simulation_intro_ode using Catalyst two_state_model = @reaction_network begin - (k1,k2), X1 <--> X2 + (k1,k2), X1 <--> X2 end u0 = [:X1 => 100.0, :X2 => 200.0] tspan = (0.0, 5.0) @@ -124,9 +124,9 @@ nothing # hide ``` A full list of available solvers is provided [here](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/), and a discussion on optimal solver choices [here](@ref ode_simulation_performance_solvers). -Additional options can be provided as keyword arguments. E.g. the `adaptive` arguments determine whether adaptive time-stepping is used (for algorithms that permit this). This defaults to `true`, but can be disabled using +Additional options can be provided as keyword arguments. E.g. the `maxiters` arguments determines the maximum number of simulation time steps (before the simulation is terminated). This defaults to `1e5`, but can be modified through: ```@example simulation_intro_ode -sol = solve(oprob; adaptive = false) +sol = solve(oprob; maxiters = 1e4) nothing # hide ``` @@ -174,9 +174,9 @@ Catalyst uses the [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffE SDE simulations are performed in a similar manner to ODE simulations. The only exception is that an `SDEProblem` is created (rather than an `ODEProblem`). Furthermore, the [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) package (rather than the OrdinaryDiffEq package) is required for performing simulations. Here we simulate the two-state model for the same parameter set as previously used: ```@example simulation_intro_sde -using Catalyst, StochasticDiffEq +using Catalyst, StochasticDiffEq, Plots two_state_model = @reaction_network begin - (k1,k2), X1 <--> X2 + (k1,k2), X1 <--> X2 end u0 = [:X1 => 100.0, :X2 => 200.0] tspan = (0.0, 1.0) @@ -220,8 +220,8 @@ plot(sol) When using the CLE to generate SDEs from a CRN, it can sometimes be desirable to scale the magnitude of the noise. This can be done by introducing a *noise scaling term*, with each noise term generated by the CLE being multiplied with this term. A noise scaling term can be set using the `@default_noise_scaling` option: ```@example simulation_intro_sde two_state_model = @reaction_network begin - @default_noise_scaling 0.1 - (k1,k2), X1 <--> X2 + @default_noise_scaling 0.1 + (k1,k2), X1 <--> X2 end ``` Here, we set the noise scaling term to `0.1`, reducing the noise with a factor $10$ (noise scaling terms $>1.0$ increase the noise, while terms $<1.0$ reduce the noise). If we re-simulate the model using the low-concentration settings used previously, we see that the noise has been reduced (in fact by so much that the model can now be simulated without issues): @@ -238,9 +238,9 @@ plot(sol) The `@default_noise_scaling` option can take any expression. This can be used to e.g. designate a *noise scaling parameter*: ```@example simulation_intro_sde two_state_model = @reaction_network begin - @parameters η - @default_noise_scaling η - (k1,k2), X1 <--> X2 + @parameters η + @default_noise_scaling η + (k1,k2), X1 <--> X2 end ``` Now we can tune the noise through $η$'s value. E.g. here we remove the noise entirely by setting $η = 0.0$ (thereby recreating an ODE simulation's behaviour): @@ -260,8 +260,8 @@ plot(sol) It is possible to designate specific noise scaling terms for individual reactions through the `noise_scaling` [reaction metadata](@ref dsl_advanced_options_reaction_metadata). Here, CLE noise terms associated with a specific reaction are multiplied by that reaction's noise scaling term. Here we use this to turn off the noise in the $X1 \to X2$ reaction: ```@example simulation_intro_sde two_state_model = @reaction_network begin - k1, X1 <--> X2, [noise_scaling = 0.0] - k2, X2 --> X1 + k1, X1 --> X2, [noise_scaling = 0.0] + k2, X2 --> X1 end nothing # hide ``` @@ -275,9 +275,9 @@ Catalyst uses the [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) Jump simulations are performed using so-called `JumpProblem`s. Unlike ODEs and SDEs (for which the corresponding problem types can be created directly), jump simulations require first creating an intermediary `DiscreteProblem`. In this example, we first declare our two-state model and its initial conditions, time span, and parameter values. ```@example simulation_intro_jumps -using Catalyst +using Catalyst, JumpProcesses, Plots two_state_model = @reaction_network begin - (k1,k2), X1 <--> X2 + (k1,k2), X1 <--> X2 end u0 = [:X1 => 5, :X2 => 10] tspan = (0.0, 5.0) @@ -292,19 +292,19 @@ Next, we bundle these into a `DiscreteProblem` (similarly to how `ODEProblem`s a dprob = DiscreteProblem(two_state_model, u0, tspan, ps) nothing # hide ``` -This is then used as input to a `JumpProblem`. The `JumpProblem` also requires the CRN model as input. +This is then used as input to a `JumpProblem`. The `JumpProblem` also requires the CRN model and [an aggregator](@ref simulation_intro_jumps_solver_designation) as input. ```@example simulation_intro_jumps jprob = JumpProblem(two_state_model, dprob, Direct()) nothing # hide ``` The `JumpProblem` can now be simulated using `solve` (just like any other problem type). ```@example simulation_intro_jumps -using JumpProcesses sol = solve(jprob, SSAStepper()) nothing # hide ``` If we plot the solution we can see how the system's state does not change continuously, but instead in discrete jumps (due to the occurrence of the individual reactions of the system). ```@example simulation_intro_jumps +using Plots plot(sol) ``` @@ -313,7 +313,7 @@ Jump simulations (just like ODEs and SDEs) are performed using solver methods. U Several different aggregators are available (a full list is provided [here](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-for-Exact-Simulation)). To designate a specific one, provide it as the third argument to the `JumpProblem`. E.g. to designate that Gillespie's direct method (`Direct`) should be used, use: ```@example simulation_intro_jumps -jprob = JumpProblem(brusselator, dprob, Direct()) +jprob = JumpProblem(two_state_model, dprob, Direct()) nothing # hide ``` Especially for large systems, the choice of aggregator is relevant to simulation performance. A guide for aggregator selection is provided [here](@ref ref). diff --git a/docs/src/model_simulation/simulation_plotting.md b/docs/src/model_simulation/simulation_plotting.md index 4594ed692c..b1e471cd04 100644 --- a/docs/src/model_simulation/simulation_plotting.md +++ b/docs/src/model_simulation/simulation_plotting.md @@ -1,4 +1,4 @@ -# [Simulation_plotting](@id simulation_plotting) +# [Simulation plotting](@id simulation_plotting) Catalyst uses the [Plots.jl](https://github.com/JuliaPlots/Plots.jl) package for performing all plots. This section provides a brief summary of some useful plotting options, while [Plots.jl's documentation](https://docs.juliaplots.org/stable/) provides a more throughout description of how to tune your plots. !!! note diff --git a/docs/src/steady_state_functionality/nonlinear_solve.md b/docs/src/steady_state_functionality/nonlinear_solve.md index 55d994d56a..5ee247d8c1 100644 --- a/docs/src/steady_state_functionality/nonlinear_solve.md +++ b/docs/src/steady_state_functionality/nonlinear_solve.md @@ -109,7 +109,7 @@ Note that, unlike for nonlinear system solving, `u0` is not just an initial gues The forward ODE solving approach uses the ODE solvers implemented by the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package. If this package is loaded, it is possible to designate a specific solver to use. Any available ODE solver can be used, however, it has to be encapsulated by the `DynamicSS()` function. E.g. here we designate the `Rodas5P` solver: ```@example steady_state_solving_simulation -using OrdinaryDiffEqDiffEq +using OrdinaryDiffEq solve(ssprob, DynamicSS(Rodas5P())) nothing # hide ``` From 86e750336357e95f6c212ce0ca7ac92f40d8cc45 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 29 May 2024 16:54:23 -0400 Subject: [PATCH 015/446] rework docs a bit to reduce runtimes --- docs/pages.jl | 24 ++-- .../model_creation/mm_kinetics.svg | 128 ++++++++++++++++++ .../model_creation/sir_outbreaks.svg | 128 ++++++++++++++++++ .../incomplete_brusselator_simulation.svg | 54 ++++++++ docs/src/{home.md => index.md} | 0 .../catalyst_for_new_julia_users.md | 13 +- .../behaviour_optimisation.md | 5 +- .../petab_ode_param_fitting.md | 0 .../structural_identifiability.md | 8 +- .../chemistry_related_functionality.md | 6 +- docs/src/model_creation/dsl_advanced.md | 49 +++---- docs/src/model_creation/dsl_basics.md | 7 +- .../examples/basic_CRN_library.md | 28 ++-- .../model_simulation/ensemble_simulations.md | 11 +- .../ode_simulation_performance.md | 33 +++-- .../simulation_introduction.md | 14 +- .../bifurcation_diagrams.md | 20 +-- .../dynamical_systems.md | 2 +- .../nonlinear_solve.md | 19 ++- 19 files changed, 441 insertions(+), 108 deletions(-) create mode 100644 docs/src/assets/long_ploting_times/model_creation/mm_kinetics.svg create mode 100644 docs/src/assets/long_ploting_times/model_creation/sir_outbreaks.svg create mode 100644 docs/src/assets/long_ploting_times/model_simulation/incomplete_brusselator_simulation.svg rename docs/src/{home.md => index.md} (100%) rename docs/{old_files => src/inverse_problems}/petab_ode_param_fitting.md (100%) diff --git a/docs/pages.jl b/docs/pages.jl index 3e5787a4db..fd9cfa18ab 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -1,5 +1,5 @@ pages = Any[ - "Home" => "home.md", + "Home" => "index.md", "Introduction to Catalyst" => Any[ "introduction_to_catalyst/catalyst_for_new_julia_users.md", # "introduction_to_catalyst/introduction_to_catalyst.md" @@ -14,11 +14,11 @@ pages = Any[ # Events. #"model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. # Loading and writing models to files. - # Model visualisation. + "model_creation/model_visualisation.md", #"model_creation/network_analysis.md", "model_creation/chemistry_related_functionality.md", "Model creation examples" => Any[ - #"model_creation/examples/basic_CRN_library.md", + "model_creation/examples/basic_CRN_library.md", "model_creation/examples/programmatic_generative_linear_pathway.md", #"model_creation/examples/hodgkin_huxley_equation.md", #"model_creation/examples/smoluchowski_coagulation_equation.md" @@ -29,20 +29,20 @@ pages = Any[ # Simulation introduction. "model_simulation/simulation_plotting.md", "model_simulation/simulation_structure_interfacing.md", - #"model_simulation/ensemble_simulations.md", + "model_simulation/ensemble_simulations.md", # Stochastic simulation statistical analysis. - # "model_simulation/ode_simulation_performance.md", - # # ODE Performance considerations/advice. - # # SDE Performance considerations/advice. - # # Jump Performance considerations/advice. - # # Finite state projection + "model_simulation/ode_simulation_performance.md", + # ODE Performance considerations/advice. + # SDE Performance considerations/advice. + # Jump Performance considerations/advice. + # Finite state projection ], "Steady state analysis" => Any[ - # "steady_state_functionality/homotopy_continuation.md", + "steady_state_functionality/homotopy_continuation.md", "steady_state_functionality/nonlinear_solve.md", "steady_state_functionality/steady_state_stability_computation.md", - # "steady_state_functionality/bifurcation_diagrams.md", - # "steady_state_functionality/dynamical_systems.md" + "steady_state_functionality/bifurcation_diagrams.md", + "steady_state_functionality/dynamical_systems.md" ], "Inverse Problems" => Any[ # Inverse problems introduction. diff --git a/docs/src/assets/long_ploting_times/model_creation/mm_kinetics.svg b/docs/src/assets/long_ploting_times/model_creation/mm_kinetics.svg new file mode 100644 index 0000000000..824a5fd376 --- /dev/null +++ b/docs/src/assets/long_ploting_times/model_creation/mm_kinetics.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/long_ploting_times/model_creation/sir_outbreaks.svg b/docs/src/assets/long_ploting_times/model_creation/sir_outbreaks.svg new file mode 100644 index 0000000000..3e213ebbdd --- /dev/null +++ b/docs/src/assets/long_ploting_times/model_creation/sir_outbreaks.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/long_ploting_times/model_simulation/incomplete_brusselator_simulation.svg b/docs/src/assets/long_ploting_times/model_simulation/incomplete_brusselator_simulation.svg new file mode 100644 index 0000000000..4f9f01fedf --- /dev/null +++ b/docs/src/assets/long_ploting_times/model_simulation/incomplete_brusselator_simulation.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/home.md b/docs/src/index.md similarity index 100% rename from docs/src/home.md rename to docs/src/index.md diff --git a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md index ab5c12ae01..bfeee426cf 100644 --- a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md +++ b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md @@ -137,9 +137,9 @@ using JumpProcesses ``` This time, we will declare a so-called [SIR model for an infectious disease](@ref basic_CRN_library_sir). Note that even if this model does not describe a set of chemical reactions, it can be modelled using the same framework. The model consists of 3 species: -* $S$, the amount of *susceptible* individuals. -* $I$, the amount of *infected* individuals. -* $R$, the amount of *recovered* (or *removed*) individuals. +* *S*, the amount of *susceptible* individuals. +* *I*, the amount of *infected* individuals. +* *R*, the amount of *recovered* (or *removed*) individuals. It also has 2 reaction events: * Infection, where a susceptible individual meets an infected individual and also becomes infected. @@ -171,6 +171,7 @@ Previously we have bundled this information into an `ODEProblem` (denoting a det using JumpProcesses # hide dprob = DiscreteProblem(sir_model, u0, tspan, params) jprob = JumpProblem(sir_model, dprob, Direct()) +nothing # hide ``` Again, the order in which the inputs are given to the `DiscreteProblem` and the `JumpProblem` is important. The last argument to the `JumpProblem` (`Direct()`) denotes which simulation method we wish to use. For now, we recommend that users simply use the `Direct()` option, and then consider alternative ones (see the [JumpProcesses.jl docs](https://docs.sciml.ai/JumpProcesses/stable/)) when they are more familiar with modelling in Catalyst and Julia. @@ -199,7 +200,7 @@ This will: 2. Switch your current Julia session to use the current folder's environment. !!! note - If you check any folder which has been designated as a Julia environment, it contains a Project.toml and a Manifest.toml file. These store all information regarding the corresponding environment. For non-advanced users, it is recommended to never touch these files directly (and instead do so using various functions from the Pkg package, the important ones which are described in the next two subsections). + If you check any folder which has been designated as a Julia environment, it contains a Project.toml and a Manifest.toml file. These store all information regarding the corresponding environment. For non-advanced users, it is recommended to never touch these files directly (and instead do so using various functions from the Pkg package, the important ones which are described in the next two subsections). ### [Installing and importing packages in Julia](@id catalyst_for_new_julia_users_packages_installing) Package installation and import have been described [previously](@ref catalyst_for_new_julia_users_packages_intro). However, for the sake of this extended tutorial, let us repeat the description by demonstrating how to install the [Latexify.jl](https://github.com/korsbo/Latexify.jl) package (which enables e.g. displaying Catalyst models in Latex format). First, we import the Julia Package manager ([Pkg](https://github.com/JuliaLang/Pkg.jl)) (which is required to install Julia packages): @@ -236,7 +237,7 @@ So, why is this required, and why cannot we simply import any package installed The reason why all this is important is that it is *highly recommended* to, for each project, define a separate environment. To these, only add the required packages. General-purpose environments with a large number of packages often, in the long term, produce package incompatibility issues. While these might not prevent you from installing all desired package, they often mean that you are unable to use the latest version of some packages. !!! note - A not-infrequent cause for reported errors with Catalyst (typically the inability to replicate code in tutorials) is package incompatibilities in large environments preventing the latest version of Catalyst from being installed. Hence, whenever an issue is encountered, it is useful to run `Pkg.status()` to check whenever the latest version of Catalyst is being used. + A not-infrequent cause for reported errors with Catalyst (typically the inability to replicate code in tutorials) is package incompatibilities in large environments preventing the latest version of Catalyst from being installed. Hence, whenever an issue is encountered, it is useful to run `Pkg.status()` to check whenever the latest version of Catalyst is being used. Some additional useful Pkg commands are: - `Pk.rm("PackageName")` removes a package from the current environment. @@ -244,7 +245,7 @@ Some additional useful Pkg commands are: - `Pkg.update()`: updates all packages. !!! note - A useful feature of Julia's environment system is that enables the exact definition of what packages and versions were used to execute a script. This supports e.g. reproducibility in academic research. Here, by providing the corresponding Project.toml and Manifest.toml files, you can enable someone to reproduce the exact program used to perform some set of analyses. + A useful feature of Julia's environment system is that enables the exact definition of what packages and versions were used to execute a script. This supports e.g. reproducibility in academic research. Here, by providing the corresponding Project.toml and Manifest.toml files, you can enable someone to reproduce the exact program used to perform some set of analyses. --- diff --git a/docs/src/inverse_problems/behaviour_optimisation.md b/docs/src/inverse_problems/behaviour_optimisation.md index 80080f8aa4..a555037119 100644 --- a/docs/src/inverse_problems/behaviour_optimisation.md +++ b/docs/src/inverse_problems/behaviour_optimisation.md @@ -47,6 +47,7 @@ Just like for [parameter fitting](@ref optimization_parameter_fitting), we creat using Optimization initial_guess = [1.0, 1.0, 1.0] opt_prob = OptimizationProblem(pulse_amplitude, initial_guess; lb = [1e-1, 1e-1, 1e-1], ub = [1e1, 1e1, 1e1]) +nothing # hide ``` !!! note As described in a [previous section on Optimization.jl](@ref optimization_parameter_fitting), `OptimizationProblem`s do not support setting parameter values using maps. We must instead set `initial_guess` values using a vector. Next, in the first line of our cost function, we reshape the parameter values to the common form used across Catalyst (e.g. `[:pX => p[1], :pY => p[2], :pZ => p[2]]`, however, here we use a dictionary to easier compute the steady state initial condition). We also note that the order used in this array corresponds to the order we give each parameter's bounds in `lb` and `ub`, and the order in which their values occur in the output solution. @@ -55,6 +56,7 @@ As [previously described](@ref optimization_parameter_fitting), Optimization.jl ```@example behaviour_optimization using OptimizationBBO opt_sol = solve(opt_prob, BBO_adaptive_de_rand_1_bin_radiuslimited()) +nothing # hide ``` Finally, we plot a simulation using the found parameter set (stored in `opt_sol.u`): ```@example behaviour_optimization @@ -78,9 +80,10 @@ Through packages such as [ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDi ```@example behaviour_optimization opt_func = OptimizationFunction(pulse_amplitude, AutoForwardDiff()) opt_prob = OptimizationProblem(opt_func, initial_guess; lb = [1e-1, 1e-1, 1e-1], ub = [1e1, 1e1, 1e1]) +nothing # hide ``` Finally, we can find the optimum using some differentiation-based optimisation methods. Here we will use [Optim.jl](https://github.com/JuliaNLSolvers/Optim.jl)'s `BFGS` method: -```@example behaviour_optimization +```julia using OptimizationOptimJL opt_sol = solve(opt_prob, OptimizationOptimJL.BFGS()) ``` diff --git a/docs/old_files/petab_ode_param_fitting.md b/docs/src/inverse_problems/petab_ode_param_fitting.md similarity index 100% rename from docs/old_files/petab_ode_param_fitting.md rename to docs/src/inverse_problems/petab_ode_param_fitting.md diff --git a/docs/src/inverse_problems/structural_identifiability.md b/docs/src/inverse_problems/structural_identifiability.md index 289b14aa91..8b118b367c 100644 --- a/docs/src/inverse_problems/structural_identifiability.md +++ b/docs/src/inverse_problems/structural_identifiability.md @@ -60,15 +60,15 @@ To, in a similar manner, indicate that certain initial conditions are known is a ### Providing non-trivial measured quantities Sometimes, ones may not have measurements of species, but rather some combinations of species (or possibly parameters). To account for this, `measured_quantities` accepts any algebraic expression (and not just single species). To form such expressions, species and parameters have to first be `@unpack`'ed from the model. Say that we have a model where an enzyme ($E$) is converted between an active and inactive form, which in turns activates the production of a product, $P$: ```@example si1 -enzyme_activation = @reaction_network begin +rs = @reaction_network begin (kA,kD), Eᵢ <--> Eₐ (Eₐ, d), 0 <-->P end ``` If we can measure the total amount of $E$ ($=Eᵢ+Eₐ$), as well as the amount of $P$, we can use the following to assess identifiability: -```@example si2 -@unpack Eᵢ, Eₐ = enzyme_activation -assess_identifiability(enzyme_activation; measured_quantities = [Eᵢ + Eₐ, :P], loglevel = Logging.Error) +```@example si1 +@unpack Eᵢ, Eₐ = rs +assess_identifiability(rs; measured_quantities = [Eᵢ + Eₐ, :P], loglevel = Logging.Error) nothing # hide ``` diff --git a/docs/src/model_creation/chemistry_related_functionality.md b/docs/src/model_creation/chemistry_related_functionality.md index 2325e88f6d..f87402d91c 100644 --- a/docs/src/model_creation/chemistry_related_functionality.md +++ b/docs/src/model_creation/chemistry_related_functionality.md @@ -12,7 +12,8 @@ We will first show how to create compound species through [programmatic model co ```@example chem1 using Catalyst t = default_t() -@species C(t) O(t) +@species C(t) O(t) +nothing # hide ``` Next, we create the `CO2` compound species: ```@example chem1 @@ -84,14 +85,17 @@ as the components `C`, `H`, and `O` are not declared as species anywhere. Please Just like for normal species, it is possible to designate metadata and default values for compounds. Metadata is provided after the compound name, but separated from it by a `,`: ```@example chem1 @compound (CO2, [unit="mol"]) ~ C + 2O +nothing # hide ``` Default values are designated using `=`, and provided directly after the compound name.: ```@example chem1 @compound (CO2 = 2.0) ~ C + 2O +nothing # hide ``` If both default values and meta data are provided, the metadata is provided after the default value: ```@example chem1 @compound (CO2 = 2.0, [unit="mol"]) ~ C + 2O +nothing # hide ``` In all of these cases, the left-hand side must be enclosed within `()`. diff --git a/docs/src/model_creation/dsl_advanced.md b/docs/src/model_creation/dsl_advanced.md index 172c8e76d8..1369efe04e 100644 --- a/docs/src/model_creation/dsl_advanced.md +++ b/docs/src/model_creation/dsl_advanced.md @@ -85,7 +85,7 @@ Generally, there are four main reasons for specifying species/parameters using t 3. To designate metadata for species/parameters (described [here](@ref dsl_advanced_options_species_and_parameters_metadata)). 4. To designate a species or parameters that do not occur in reactions, but are still part of the model (e.g a [parametric initial condition](@ref dsl_advanced_options_parametric_initial_conditions)) -!!!! warn +!!! warn Catalyst's DSL automatically infer species and parameters from the input. However, it only does so for *quantities that appear in reactions*. Until now this has not been relevant. However, this tutorial will demonstrate cases where species/parameters that are not part of reactions are used. These *must* be designated using either the `@species` or `@parameters` options (or the `@variables` option, which is described [later](@ref ref)). ### [Setting default values for species and parameters](@id dsl_advanced_options_default_vals) @@ -166,24 +166,24 @@ Whenever a species/parameter is declared using the `@species`/`@parameters` opti ```@example dsl_advanced_metadata using Catalyst # hide two_state_system = @reaction_network begin - @species Xi(t) [description="The X's inactive form"] Xa(t) [description="The X's active form"] - @parameters kA [description="X's activation rate"] kD [description="X's deactivation rate"] - (ka,kD), Xi <--> Xa + @species Xᵢ(t) [description="X's inactive form"] Xₐ(t) [description=" X's active form"] + @parameters kA [description="Activation rate"] kD [description="Deactivation rate"] + (ka,kD), Xᵢ <--> Xₐ end ``` -A metadata can be given to only a subset of a system's species/parameters, and a quantity can be given several metadata entries. To give several metadata, separate each by a `,`. Here we only provide a description for `kA`, for which we also provide a [`bounds` metadata](@ref https://docs.sciml.ai/ModelingToolkit/dev/basics/Variable_metadata/#Bounds), +A metadata can be given to only a subset of a system's species/parameters, and a quantity can be given several metadata entries. To give several metadata, separate each by a `,`. Here we only provide a description for `kA`, for which we also provide a [`bounds` metadata](https://docs.sciml.ai/ModelingToolkit/dev/basics/Variable_metadata/#Bounds), ```@example dsl_advanced_metadata two_state_system = @reaction_network begin - @parameters kA [description="X's activation rate", bounds=(0.01,10.0)] - (ka,kD), Xi <--> Xa + @parameters kA [description="Activation rate", bounds=(0.01,10.0)] + (ka,kD), Xᵢ <--> Xₐ end ``` It is possible to add both default values and metadata to a parameter/species. In this case, first provide the default value, and next the metadata. I.e. to in the above example set $kA$'s default value to $1.0$ we use ```@example dsl_advanced_metadata two_state_system = @reaction_network begin - @parameters kA=1.0 [description="X's activation rate", bounds=(0.01,10.0)] - (ka,kD), Xi <--> Xa + @parameters kA=1.0 [description="Activation rate", bounds=(0.01,10.0)] + (ka,kD), Xᵢ <--> Xₐ end ``` @@ -191,10 +191,10 @@ When designating metadata for species/parameters in `begin ... end` blocks the s ```@example dsl_advanced_metadata two_state_system = @reaction_network begin @parameters begin - kA, [description="X's activation rate", bounds=(0.01,10.0)] - kD = 1.0, [description="X's deactivation rate"] + kA, [description="Activation rate", bounds=(0.01,10.0)] + kD = 1.0, [description="Deactivation rate"] end - (kA,kD), Xi <--> Xa + (kA,kD), Xᵢ <--> Xₐ end ``` @@ -247,7 +247,7 @@ nothing # hide If a parameter has a type, metadata, and a default value, they are designated in the following order: ```@example dsl_advanced_parameter_types polymerisation_network = @reaction_network begin - @parameters n::Int64 = 2 [description="Parameter n, which is an integer and defaults to the value 2."] + @parameters n::Int64 = 2 [description="Parameter n, an integer with defaults value 2."] (kB,kD), n*X <--> Xn end nothing # hide @@ -315,6 +315,7 @@ rn2 = @reaction_network my_network begin end rn1 == rn2 ``` +If you wish to check for identity, and wish that models that have different names but are otherwise identical, should be considered equal, you can use the [`isequivalent`](@ref) function. Setting model names is primarily useful for [hierarchical modelling](@ref compositional_modeling), where network names are appended to the display names of subnetworks' species and parameters. @@ -346,11 +347,13 @@ nothing # hide Next, we can use [symbolic indexing](@ref simulation_structure_interfacing) of our solution object, but with the observable as input. E.g. we can use ```@example dsl_advanced_observables sol[:Xtot] +nothing # hide ``` to get a vector with `Xtot`'s value throughout the simulation. We can also use ```@example dsl_advanced_observables using Plots plot(sol; idxs = :Xtot) +plot!(ylimit = (minimum(sol[:Xtot])*0.95, maximum(sol[:Xtot])*1.05)) # hide ``` to plot the observables (rather than the species). @@ -362,7 +365,7 @@ rn = @reaction_network begin end nothing # hide ``` -!!! +!!! note If only a single observable is declared, the `begin .. end` block is not required and the observable can be declared directly after the `@observables` option. [Metadata](@ref dsl_advanced_options_species_and_parameters_metadata) can be supplied to an observable directly after its declaration (but before its formula). If so, the metadata must be separated from the observable with a `,`, and the observable plus the metadata encapsulated by `()`. E.g. to add a [description metadata](@ref dsl_advanced_options_species_and_parameters_metadata) to our observable we can use @@ -397,11 +400,11 @@ As [described elsewhere](@ref ref), Catalyst's `ReactionSystem` models depend on using Catalyst # hide rn = @reaction_network begin @ivs τ - (ka,kD), Xi <--> Xa + (ka,kD), Xᵢ <--> Xₐ end nothing # hide ``` -We can confirm that `Xi` and `Xa` depend on `τ` (and not `t`): +We can confirm that `Xᵢ` and `Xₐ` depend on `τ` (and not `t`): ```@example dsl_advanced_ivs species(rn) ``` @@ -420,8 +423,8 @@ It is also possible to have species which depends on several independent variabl ```@example dsl_advanced_ivs rn = @reaction_network begin @ivs t x - @species Xi(t,x) Xa(t,x) - (ka,kD), Xi <--> Xa + @species Xᵢ(t,x) Xₐ(t,x) + (ka,kD), Xᵢ <--> Xₐ end species(rn) ``` @@ -435,8 +438,8 @@ It is possible to supply reactions with *metadata*, containing some additional i ```@example dsl_advanced_reaction_metadata using Catalyst # hide bd_model = @reaction_network begin - p, 0 --> X, [description="A production reaction"] - d, X --> 0, [description="A degradation reaction"] + p, 0 --> X, [description="Production reaction"] + d, X --> 0, [description="Degradation reaction"] end nothing # hide ``` @@ -444,7 +447,7 @@ nothing # hide When [bundling reactions](@ref dsl_description_reaction_bundling), reaction metadata can be bundled using the same rules as rates. Bellow we re-declare our birth-death process, but on a single line: ```@example dsl_advanced_reaction_metadata bd_model = @reaction_network begin - (p,d), 0 --> X, ([description="A production reaction"], [description="A degradation reaction"]) + (p,d), 0 <--> X, ([description="Production reaction"], [description="Degradation reaction"]) end nothing # hide ``` @@ -452,8 +455,8 @@ nothing # hide Here we declare a model where we also provide a `misc` metadata (which can hold any quantity we require) to our birth reaction: ```@example dsl_advanced_reaction_metadata bd_model = @reaction_network begin - p, 0 --> X, [description="A production reaction", misc=:value] - d, X --> 0, [description="A degradation reaction"] + p, 0 --> X, [description="Production reaction", misc=:value] + d, X --> 0, [description="Degradation reaction"] end nothing # hide ``` diff --git a/docs/src/model_creation/dsl_basics.md b/docs/src/model_creation/dsl_basics.md index 5d4b9ac51e..8f8029585f 100644 --- a/docs/src/model_creation/dsl_basics.md +++ b/docs/src/model_creation/dsl_basics.md @@ -51,9 +51,8 @@ rn1 = @reaction_network begin kX, X --> Y kY, Y --> X end -nothing # hide ``` -Generally, anything that is a [permitted Julia variable name](@id https://docs.julialang.org/en/v1/manual/variables/#man-allowed-variable-names) can be used to designate a species or parameter in Catalyst. +Generally, anything that is a [permitted Julia variable name](https://docs.julialang.org/en/v1/manual/variables/#man-allowed-variable-names) can be used to designate a species or parameter in Catalyst. ## [Different types of reactions](@id dsl_description_reactions) @@ -97,7 +96,6 @@ rn6 = @reaction_network begin k, 2X + 3(Y + 2Z) --> 5(V + W) k, 2X + 3Y + 6Z --> 5V + 5W end -nothing # hide ``` ## [Bundling of similar reactions](@id dsl_description_reaction_bundling) @@ -354,13 +352,14 @@ An example of how this can be used to create a neat-looking model can be found i (kB,kD), A + σᵛ ↔ Aσᵛ L, Aσᵛ → σᵛ end -nothing # hide ``` This functionality can also be used to create less serious models: +```@example dsl_basics rn_13 = @reaction_network begin 🍦, 😢 --> 😃 end +``` It should be noted that the following symbols are *not permitted* to be used as species or parameter names: - `pi` and `π` (used in Julia to denote [`3.1415926535897...`](https://en.wikipedia.org/wiki/Pi)). diff --git a/docs/src/model_creation/examples/basic_CRN_library.md b/docs/src/model_creation/examples/basic_CRN_library.md index 3e75f73700..4955bae3a6 100644 --- a/docs/src/model_creation/examples/basic_CRN_library.md +++ b/docs/src/model_creation/examples/basic_CRN_library.md @@ -110,7 +110,7 @@ using JumpProcesses dprob = DiscreteProblem(mm_system, u0, tspan, ps) jprob = JumpProblem(mm_system, dprob, Direct()) jsol = solve(jprob, SSAStepper()) -jsol = solve(jprob, SSAStepper(); seed = 12) # hide +jsol = solve(jprob, SSAStepper(), seed = 12) # hide using Plots oplt = plot(osol; title = "Reaction rate equation (ODE)") @@ -118,8 +118,10 @@ splt = plot(ssol; title = "Chemical Langevin equation (SDE)") jplt = plot(jsol; title = "Stochastic chemical kinetics (Jump)") plot(oplt, splt, jplt; lw = 2, size=(800,800), layout = (3,1)) plot!(bottom_margin = 3Plots.Measures.mm) # hide +nothing # hide ``` -Note that, due to the large amounts of the species involved, teh stochastic trajectories are very similar to the deterministic one. +![MM Kinetics](../../assets/long_ploting_times/model_creation/mm_kinetics.svg) +Note that, due to the large amounts of the species involved, the stochastic trajectories are very similar to the deterministic one. ## [SIR infection model](@id basic_CRN_library_sir) The [SIR model](https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SIR_model) is the simplest model of the spread of an infectious disease. While the real system is very different from the chemical and cellular processes typically modelled with CRNs, it (and several other epidemiological systems) can be modelled using the same CRN formalism. The SIR model consists of three species: susceptible ($S$), infected ($I$), and removed ($R$) individuals, and two reaction events: infection and recovery. @@ -146,20 +148,22 @@ Next, we perform 3 different Jump simulations. Note that for the stochastic mode ```@example crn_library_sir using JumpProcesses dprob = DiscreteProblem(sir_model, u0, tspan, ps) -jprob = JumpProblem(sir_model, dprob, Direct()) +jprob = JumpProblem(sir_model, dprob, Direct(); save_positions = (false, false)) -jsol1 = solve(jprob, SSAStepper()) -jsol2 = solve(jprob, SSAStepper()) -jsol3 = solve(jprob, SSAStepper()) -jsol1 = solve(jprob, SSAStepper(); seed=1) # hide -jsol2 = solve(jprob, SSAStepper(); seed=2) # hide -jsol3 = solve(jprob, SSAStepper(); seed=3) # hide +jsol1 = solve(jprob, SSAStepper(); saveat = 1.0) +jsol2 = solve(jprob, SSAStepper(); saveat = 1.0) +jsol3 = solve(jprob, SSAStepper(); saveat = 1.0) +jsol1 = solve(jprob, SSAStepper(); saveat = 1.0, seed = 1) # hide +jsol2 = solve(jprob, SSAStepper(); saveat = 1.0, seed = 2) # hide +jsol3 = solve(jprob, SSAStepper(); saveat = 1.0, seed = 3) # hide jplt1 = plot(jsol1; title = "Outbreak") jplt2 = plot(jsol2; title = "Outbreak") jplt3 = plot(jsol3; title = "No outbreak") plot(jplt1, jplt2, jplt3; lw = 3, size=(800,700), layout = (3,1)) +nothing # hide ``` +![SIR Outbreak](../../assets/long_ploting_times/model_creation/sir_outbreaks.svg) ## [Chemical cross-coupling](@id basic_CRN_library_cc) In chemistry, [cross-coupling](https://en.wikipedia.org/wiki/Cross-coupling_reaction) is when a catalyst combines two substrates to form a product. In this example, the catalyst ($C$) first binds one substrate ($S₁$) to form an intermediary complex ($S₁C$). Next, the complex binds the second substrate ($S₂$) to form another complex ($CP$). Finally, the catalyst releases the now-formed product ($P$). This system is an extended version of the [Michaelis-Menten system presented earlier](@ref basic_CRN_library_mm). @@ -241,9 +245,9 @@ oprob = ODEProblem(sa_loop, u0, tspan, ps) osol = solve(oprob) dprob = DiscreteProblem(sa_loop, u0, tspan, ps) -jprob = JumpProblem(sa_loop, dprob, Direct()) -jsol = solve(jprob, SSAStepper()) -jsol = solve(jprob, SSAStepper(); seed = 12) # hide +jprob = JumpProblem(sa_loop, dprob, Direct(); save_positions = (false,false)) +jsol = solve(jprob, SSAStepper(); saveat = 10.0) +jsol = solve(jprob, SSAStepper(); saveat = 10.0, seed = 12) # hide plot(osol; lw = 3, label = "Reaction rate equation (ODE)") plot!(jsol; lw = 3, label = "Stochastic chemical kinetics (Jump)", yguide = "X", size = (800,350)) diff --git a/docs/src/model_simulation/ensemble_simulations.md b/docs/src/model_simulation/ensemble_simulations.md index b0731bf628..79a8dce1ec 100644 --- a/docs/src/model_simulation/ensemble_simulations.md +++ b/docs/src/model_simulation/ensemble_simulations.md @@ -16,6 +16,7 @@ end u0 = [:X => 10.0] tspan = (0.0, 1000.0) ps = [:v0 => 0.1, :v => 2.5, :K => 40.0, :n => 4.0, :deg => 0.01] +nothing # hide ``` We wish to simulate it as an SDE. Rather than performing a single simulation, however, we want to perform multiple ones. Here, we first create a normal `SDEProblem`, and use it as the single input to a `EnsembleProblem` (`EnsembleProblem` are created similarly for ODE and jump simulations, but the `ODEProblem` or `JumpProblem` is used instead). ```@example ensemble @@ -24,17 +25,19 @@ sprob = SDEProblem(sa_model, u0, tspan, ps) eprob = EnsembleProblem(sprob) nothing # hide ``` -Next, the `EnsembleProblem` can be used as input to the `solve` command. Here, we use exactly the same inputs that we use for single simulations, however, we add a `trajectories` argument to denote how many simulations we wish to carry out. Here we perform 100 simulations: +Next, the `EnsembleProblem` can be used as input to the `solve` command. Here, we use exactly the same inputs that we use for single simulations, however, we add a `trajectories` argument to denote how many simulations we wish to carry out. Here we perform 10 simulations: ```@example ensemble -sols = solve(eprob, STrapezoid(); trajectories = 100) +sols = solve(eprob, STrapezoid(); trajectories = 10) nothing # hide ``` Finally, we can use our ensemble simulation solution as input to `plot` (just like normal simulations): ```@example ensemble using Plots -plot(sols; la = 0.5) +plot(sols) ``` -Here, each simulation is displayed as an individual trajectory. We also use the [`la` plotting option](@ref simulation_plotting_options) to reduce the transparency of each individual line, improving the plot visual. +Here, each simulation is displayed as an individual trajectory. +!!! note + While not used here, the [`la` plotting option](@ref simulation_plotting_options) (which modifies line transparency) can help improve the plot visual when a large number of (overlapping) lines are plotted. Various convenience functions are available for analysing and plotting ensemble simulations (a full list can be found [here]). Here, we use these to first create an `EnsembleSummary` (retrieving each simulation's value at time points `0.0, 1.0, 2.0, ... 1000.0`). Next, we use this as an input to the `plot` command, which automatically plots the mean $X$ activity across the ensemble, while also displaying the 5% and 95% quantiles as the shaded area: ```@example ensemble diff --git a/docs/src/model_simulation/ode_simulation_performance.md b/docs/src/model_simulation/ode_simulation_performance.md index 9eaf4d4018..83e5fc7061 100644 --- a/docs/src/model_simulation/ode_simulation_performance.md +++ b/docs/src/model_simulation/ode_simulation_performance.md @@ -26,13 +26,16 @@ end u0 = [:X => 1.0, :Y => 0.0] tspan = (0.0, 20.0) -ps = [:A => 10.0, :B => 40.0] +ps = [:A => 400.0, :B => 2000.0] oprob = ODEProblem(brusselator, u0, tspan, ps) sol1 = solve(oprob, Tsit5()) plot(sol1) -``` -We note that we get a warning, indicating that an instability was detected (the typical indication of a non-stiff solver being used for a stiff ODE). Furthermore, the resulting plot ends at $t ≈ 10$, meaning that the simulation was not completed (as the simulation's endpoint is $t = 20$). Indeed, we can confirm this by checking the *return code* of the solution object: +nothing # hide +``` +![Incomplete Brusselator Simulation](../assets/long_ploting_times/model_simulation/incomplete_brusselator_simulation.svg) + +We get a warning, indicating that the simulation was terminated. Furthermore, the resulting plot ends at $t ≈ 12$, meaning that the simulation was not completed (as the simulation's endpoint is $t = 20$). Indeed, we can confirm this by checking the *return code* of the solution object: ```@example ode_simulation_performance_1 sol1.retcode ``` @@ -75,18 +78,18 @@ nothing # hide While the default choice is typically enough for most single simulations, if performance is important, it can be worthwhile exploring the available solvers to find one that is especially suited for the given problem. A complete list of possible ODE solvers, with advice on optimal selection, can be found [here](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/). This section will give some general advice. The most important part of solver selection is to select one appropriate for [the problem's stiffness](@ref ode_simulation_performance_stiffness). Generally, the `Tsit5` solver is good for non-stiff problems, and `Rodas5P` for stiff problems. For large stiff problems (with many species), `FBDF` can be a good choice. We can illustrate the impact of these choices by simulating our birth-death process using the `Tsit5`, `Vern7` (an explicit solver yielding [low error in the solution](@ref ode_simulation_performance_error)), `Rodas5P`, and `FBDF` solvers (benchmarking their respective performance using [BenchmarkTools.jl](https://github.com/JuliaCI/BenchmarkTools.jl)): -```@example ode_simulation_performance_2 +```julia using BenchmarkTools @btime solve(oprob, Tsit5()) @btime solve(oprob, Vern7()) @btime solve(oprob, Rodas5P()) @btime solve(oprob, FBDF()) ``` -Here, we note that the fastest solver is several times faster than the slowest one (`FBDF`, which is a poor choice for this ODE), +If you perform the above benchmarks on your machine, and check the results, you will note that the fastest solver is several times faster than the slowest one (`FBDF`, which is a poor choice for this ODE). ### [Simulation error, tolerance, and solver selection](@id ode_simulation_performance_error) Numerical ODE simulations [approximate an ODEs' continuous solutions as discrete vectors](https://en.wikipedia.org/wiki/Discrete_time_and_continuous_time). This introduces errors in the computed solution. The magnitude of these errors can be controlled by setting solver *tolerances*. By reducing the tolerance, solution errors will be reduced, however, this will also increase simulation run times. The (absolute and relative) tolerance of a solver can be tuned through the `abstol` and `reltol` arguments. Here we see how run time increases with larger tolerances: -```@example ode_simulation_performance_2 +```julia @btime solve(oprob, Tsit5(); abstol=1e-6, reltol=1e-6) @btime solve(oprob, Tsit5(); abstol=1e-12, reltol=1e-12) ``` @@ -143,7 +146,7 @@ nothing # hide ``` Since these methods do not depend on a Jacobian, certain Jacobian options (such as [computing it symbolically](@ref ode_simulation_performance_symbolic_jacobian)) are irrelevant to them. -### [Designating preconditioners for Jacobian-free linear solvers](@ref ode_simulation_performance_preconditioners) +### [Designating preconditioners for Jacobian-free linear solvers](@id ode_simulation_performance_preconditioners) When an implicit method solves a linear equation through an (iterative) matrix-free Newton-Krylov method, the rate of convergence depends on the numerical properties of the matrix defining the linear system. To speed up convergence, a [*preconditioner*](https://en.wikipedia.org/wiki/Preconditioner) can be applied to both sides of the linear equation, attempting to create an equation that converges faster. Preconditioners are only relevant when using matrix-free Newton-Krylov methods. In practice, preconditioners are implemented as functions with a specific set of arguments. How to implement these is non-trivial, and we recommend reading OrdinaryDiffEq's documentation pages [here](https://docs.sciml.ai/DiffEqDocs/stable/features/linear_nonlinear/#Preconditioners:-precs-Specification) and [here](https://docs.sciml.ai/DiffEqDocs/stable/tutorials/advanced_ode_example/#Adding-a-Preconditioner). In this example, we will define an [Incomplete LU](https://en.wikipedia.org/wiki/Incomplete_LU_factorization) preconditioner (which requires the [IncompleteLU.jl](https://github.com/haampie/IncompleteLU.jl) package): @@ -232,22 +235,21 @@ plot(esol.u[47]) To extract the amount of $P$ produced in each simulation, and plot this against the corresponding $kP$ value, we can use: ```@example ode_simulation_performance_4 plot(0.01:0.01:1.0, map(sol -> sol[:P][end], esol.u), xguide = "kP", yguide = "P produced", label="") +plot!(left_margin = 3Plots.Measures.mm) # hide ``` Above, we have simply used `EnsembleProblem` as a convenient interface to run a large number of similar simulations. However, these problems have the advantage that they allow the passing of an *ensemble algorithm* to the `solve` command, which describes a strategy for parallelising the simulations. By default, `EnsembleThreads` is used. This parallelises the simulations using [multithreading](https://en.wikipedia.org/wiki/Multithreading_(computer_architecture)) (parallelisation within a single process), which is typically advantageous for small problems on shared memory devices. An alternative is `EnsembleDistributed` which instead parallelises the simulations using [multiprocessing](https://en.wikipedia.org/wiki/Multiprocessing) (parallelisation across multiple processes). To do this, we simply supply this additional solver to the solve command: ```julia -esol = solve(eprob, Tsit5(), EnsembleDistributed(); trajectories=100) -nothing # hide +esol = solve(eprob, Tsit5(), EnsembleDistributed(); trajectories = 100) ``` To utilise multiple processes, you must first give Julia access to these. You can check how many processes are available using the `nprocs` (which requires the [Distributed.jl](https://github.com/JuliaLang/Distributed.jl) package): -```@example ode_simulation_performance_4 +```julia using Distributed nprocs() ``` Next, more processes can be added using `addprocs`. E.g. here we add an additional 4 processes: -```@example ode_simulation_performance_4 +```julia addprocs(4) -nothing # hide ``` Powerful personal computers and HPC clusters typically have a large number of available additional processes that can be added to improve performance. @@ -283,11 +285,12 @@ mm_model = @reaction_network begin kP, SE --> P + E d, S --> ∅ end +@unpack S, E, SE, P, kB, kD, kP, d = mm_model using OrdinaryDiffEq, Plots -u0 = @SVector [:S => 1.0f0, :E => 1.0f0, :SE => 0.0f0, :P => 0.0f0] +u0 = @SVector [S => 1.0f0, E => 1.0f0, SE => 0.0f0, P => 0.0f0] tspan = (0.0f0, 50.0f0) -p = @SVector [:kB => 1.0f0, :kD => 0.1f0, :kP => 0.5f0, :d => 0.1f0] +p = @SVector [kB => 1.0f0, kD => 0.1f0, kP => 0.5f0, d => 0.1f0] oprob = ODEProblem(mm_model, u0, tspan, p) nothing # hide ``` @@ -300,6 +303,8 @@ eprob = EnsembleProblem(oprob; prob_func = prob_func) nothing # hide ``` Here have we increased the number of simulations to 10,000, since this is a more appropriate number for GPU parallelisation (as compared to the 100 simulations we performed in our CPU example). +!!! note + Currently, declaration of static vectors requires [symbolic, rather than symbol, form](@ref ref) for species and parameters. Hence, we here first [`@unpack` these](@ref ref) before constructing `u0` and `ps` using `@SVector`. We can now simulate our model using a GPU-based ensemble algorithm. Currently, two such algorithms are available, `EnsembleGPUArray` and `EnsembleGPUKernel`. Their differences are that - Only `EnsembleGPUKernel` requires arrays to be static arrays (although it is still advantageous for `EnsembleGPUArray`). diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index 7f5eb0528a..6234dc4a0a 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -198,9 +198,13 @@ Next, let us reduce species amounts (using [`remake`](@ref simulation_structure_ sprob = remake(sprob; u0 = [:X1 => 0.33, :X2 => 0.66]) sol = solve(sprob, STrapezoid()) sol = solve(sprob, STrapezoid(); seed = 1234567) # hide +nothing # hide +``` +Here, we receive a warning that the simulation was terminated. next, if we plot the solution: +```@example simulation_intro_sde plot(sol) ``` -Here, we receive a warning that the simulation was aborted. In the plot, we also see that it is incomplete. In this case we also note that species concentrations are very low (and sometimes, due to the relatively high amount of noise, even negative). This, combined with the early termination, suggests that we are simulating our model for too low species concentration for the assumptions of the CLE to hold. Instead, [jump simulations](@ref simulation_intro_jumps) should be used. +we note that the simulation didn't reach the designated final time point ($t = 1.0$). In this case we also note that species concentrations are very low (and sometimes, due to the relatively high amount of noise, even negative). This, combined with the early termination, suggests that we are simulating our model for too low species concentration for the assumptions of the CLE to hold. Instead, [jump simulations](@ref simulation_intro_jumps) should be used. Next, let us consider a simulation for another parameter set: ```@example simulation_intro_sde @@ -223,6 +227,7 @@ two_state_model = @reaction_network begin @default_noise_scaling 0.1 (k1,k2), X1 <--> X2 end +nothing # hide ``` Here, we set the noise scaling term to `0.1`, reducing the noise with a factor $10$ (noise scaling terms $>1.0$ increase the noise, while terms $<1.0$ reduce the noise). If we re-simulate the model using the low-concentration settings used previously, we see that the noise has been reduced (in fact by so much that the model can now be simulated without issues): ```@example simulation_intro_sde @@ -242,6 +247,7 @@ two_state_model = @reaction_network begin @default_noise_scaling η (k1,k2), X1 <--> X2 end +nothing # hide ``` Now we can tune the noise through $η$'s value. E.g. here we remove the noise entirely by setting $η = 0.0$ (thereby recreating an ODE simulation's behaviour): ```@example simulation_intro_sde @@ -318,11 +324,7 @@ nothing # hide ``` Especially for large systems, the choice of aggregator is relevant to simulation performance. A guide for aggregator selection is provided [here](@ref ref). -Next, a simulation method can be provided (like for ODEs and SDEs) as the second argument to `solve`. Primarily two alternatives are available, `SSAStepper` and `FunctionMap` (other alternatives are only relevant when jump simulations are combined with ODEs/SDEs, which is described in more detail in JumpProcesses's documentation). Generally, `FunctionMap` is only used when a [continuous callback](@ref ref) is used (and `SSAStepper` otherwise). E.g. we can designate that the `FunctionMap` method should be used through: -```@example simulation_intro_jumps -sol = solve(jprob, FunctionMap()) -nothing # hide -``` +Next, a simulation method can be provided (like for ODEs and SDEs) as the second argument to `solve`. Currently, the only relevant solver is `SSAStepper()` (which is the one used throughout Catalyst's documentation). Other choices are primarily relevant to combined ODE/SDE + jump simulations, or inexact simulations. These situations are described in more detail [here](https://docs.sciml.ai/JumpProcesses/stable/jump_solve/). ### [Jump simulations where some rate depends on time](@id simulation_intro_jumps_variableratejumps) For some models, the rate of some reactions depend on time. E.g. consider the following [circadian model](https://en.wikipedia.org/wiki/Circadian_rhythm), where the production rate of some protein ($P$) depends on a sinusoid function: diff --git a/docs/src/steady_state_functionality/bifurcation_diagrams.md b/docs/src/steady_state_functionality/bifurcation_diagrams.md index d869141e03..a76ff91393 100644 --- a/docs/src/steady_state_functionality/bifurcation_diagrams.md +++ b/docs/src/steady_state_functionality/bifurcation_diagrams.md @@ -37,21 +37,21 @@ nothing # hide BifurcationKit computes bifurcation diagrams using the `bifurcationdiagram` function. From an initial point in the diagram, it tracks the solution (using a continuation algorithm) until the entire diagram is computed (BifurcationKit's continuation can be used for other purposes, however, this tutorial focuses on bifurcation diagram computation). The continuation settings are provided in a `ContinuationPar` structure. In this example, we will only specify three settings, `p_min` and `p_max` (which sets the minimum and maximum values over which the bifurcation parameter is varied) and `max_steps` (the maximum number of continuation steps to take as the bifurcation diagram is tracked). We wish to compute a bifurcation diagram over the interval $(2.0,20.0)$, and will use the following settings: ```@example ex1 p_span = (2.0, 20.0) -opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps=1000) +opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000) nothing # hide ``` Finally, we compute our bifurcation diagram using: ```@example ex1 -bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside=true) +bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside = true) nothing # hide ``` -Where `PALC()` designates that we wish to use the pseudo arclength continuation method to track our solution. The third argument (`2`) designates the maximum number of recursions when branches of branches are computed (branches appear as continuation encounters certain bifurcation points). For diagrams with highly branched structures (rare for many common small chemical reaction networks) this input is important. Finally, `bothside=true` designates that we wish to perform continuation on both sides of the initial point (which is typically the case). +Where `PALC()` designates that we wish to use the pseudo arclength continuation method to track our solution. The third argument (`2`) designates the maximum number of recursions when branches of branches are computed (branches appear as continuation encounters certain bifurcation points). For diagrams with highly branched structures (rare for many common small chemical reaction networks) this input is important. Finally, `bothside = true` designates that we wish to perform continuation on both sides of the initial point (which is typically the case). We can plot our bifurcation diagram using the Plots.jl package: ```@example ex1 using Plots -plot(bif_dia; xguide="k1", yguide="X") +plot(bif_dia; xguide = "k1", yguide = "X") ``` Here, the steady state concentration of $X$ is shown as a function of $k1$'s value. Stable steady states are shown with thick lines, unstable ones with thin lines. The two [fold bifurcation points](https://en.wikipedia.org/wiki/Saddle-node_bifurcation) are marked with "bp". @@ -69,7 +69,7 @@ opt_newton = NewtonPar(tol = 1e-9, max_iterations = 1000) opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], dsmin = 0.001, dsmax = 0.01, max_steps = 1000, newton_options = opt_newton) -bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside=true) +bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside=true) nothing # hide ``` (however, in this case these additional settings have no significant effect on the result) @@ -79,8 +79,8 @@ Let's consider the previous case, but instead compute the bifurcation diagram ov ```@example ex1 p_span = (2.0, 15.0) opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000) -bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside=true) -plot(bif_dia; xguide="k1", yguide="X") +bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br= true) +plot(bif_dia; xguide = "k1", yguide = "X") ``` Here, in the bistable region, we only see a single branch. The reason is that the continuation algorithm starts at our initial guess (here made at $k1 = 4.0$ for $(X,Y) = (5.0,2.0)$) and tracks the diagram from there. However, with the upper bound set at $k1=15.0$ the bifurcation diagram has a disjoint branch structure, preventing the full diagram from being computed by continuation alone. In this case it could be solved by increasing the bound from $k1=15.0$, however, this is not possible in all cases. In these cases, *deflation* can be used. This is described in the [BifurcationKit documentation](https://bifurcationkit.github.io/BifurcationKitDocs.jl/dev/tutorials/tutorials2/#Snaking-computed-with-deflation). @@ -99,12 +99,12 @@ end u_guess = [:K => 1.0, :X => 1.0, :Xp => 1.0] p_start = [:p => 1.0, :d => 0.5, :kP => 2.0, :kD => 5.0] u0 = [:X => 1.0, :Xp => 0.0] -bprob = BifurcationProblem(kinase_model, u_guess, p_start, :d; plot_var=:Xp, u0=u0) +bprob = BifurcationProblem(kinase_model, u_guess, p_start, :d; plot_var = :Xp, u0) p_span = (0.1, 10.0) opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000) -bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside=true) -plot(bif_dia; xguide="d", yguide="Xp") +bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside = true) +plot(bif_dia; xguide = "d", yguide = "Xp") ``` This bifurcation diagram does not contain any interesting features (such as bifurcation points), and only shows how the steady state concentration of $Xp$ is reduced as $d$ increases. diff --git a/docs/src/steady_state_functionality/dynamical_systems.md b/docs/src/steady_state_functionality/dynamical_systems.md index e818449963..45847a12f8 100644 --- a/docs/src/steady_state_functionality/dynamical_systems.md +++ b/docs/src/steady_state_functionality/dynamical_systems.md @@ -89,7 +89,7 @@ Here, the `autodiff = false` argument is required when Lyapunov spectrums are co ```@example dynamical_systems_lyapunov lyapunovspectrum(ds, 100) ``` -Here, the largest exponent is positive, suggesting that the model is chaotic (or, more accurately, it has at least one chaotic attractor, to which is approached from the initial condition $(1.5,1.5,1.5)). +Here, the largest exponent is positive, suggesting that the model is chaotic (or, more accurately, it has at least one chaotic attractor, to which is approached from the initial condition $(1.5,1.5,1.5)$). Next, we consider the [Brusselator] model. First we simulate the model for two similar initial conditions, confirming that they converge to the same limit cycle: ```@example dynamical_systems_lyapunov diff --git a/docs/src/steady_state_functionality/nonlinear_solve.md b/docs/src/steady_state_functionality/nonlinear_solve.md index 5ee247d8c1..3a97392fdf 100644 --- a/docs/src/steady_state_functionality/nonlinear_solve.md +++ b/docs/src/steady_state_functionality/nonlinear_solve.md @@ -43,6 +43,7 @@ To solve this problem, we must first designate our parameter values, and also ma p = [:pₘ => 0.5, :pₚ => 2.0, :k₁ => 5.0, :k₂ => 1.0, :d => 1.0] u_guess = [:mRNA => 1.0, :P => 1.0, :P₂ => 1.0] nlprob = NonlinearProblem(dimer_production, u_guess, p) +nothing # hide ``` Finally, we can solve it using the `solve` command, returning the steady state solution: ```@example steady_state_solving_nonlinear @@ -57,7 +58,7 @@ sol ≈ sol_ntr ``` ### [Systems with conservation laws](@id steady_state_solving_nonlinear_conservation_laws) -As described in the section on homotopy continuation, when finding the steady states of systems with conservation laws, [additional considerations have to be taken](homotopy_continuation_conservation_laws). E.g. consider the following [two-state system](@ref basic_CRN_library_two_states): +As described in the section on homotopy continuation, when finding the steady states of systems with conservation laws, [additional considerations have to be taken](@ref homotopy_continuation_conservation_laws). E.g. consider the following [two-state system](@ref basic_CRN_library_two_states): ```@example steady_state_solving_claws using Catalyst, NonlinearSolve # hide two_state_model = @reaction_network begin @@ -99,20 +100,17 @@ nothing # hide Next, we provide these as an input to a `SteadyStateProblem` ```@example steady_state_solving_simulation ssprob = SteadyStateProblem(dimer_production, u0, p) +nothing # hide ``` -Finally, we can find the steady states using the `solver` command (which requires loading the SteadyStateDiffEq package). -```@example steady_state_solving_simulation -using SteadyStateDiffEq -solve(ssprob) -``` -Note that, unlike for nonlinear system solving, `u0` is not just an initial guess of the solution, but the initial conditions from which the steady state simulation is carried out. This means that, for a system with multiple steady states, we can determine the steady states associated with specific initial conditions (which is not possible when the nonlinear solving approach is used). This also permits us to easily [handle the presence of conservation laws](@ref steady_state_solving_nonlinear_conservation_laws). The forward ODE simulation approach (unlike homotopy continuation and nonlinear solving) cannot find unstable steady states. +Finally, we can find the steady states using the `solver` command. Since `SteadyStateProblem`s are solved through forward ODE simulation, we must load the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package, and [select an ODE solver](@ref simulation_intro_solver_options). Any available ODE solver can be used, however, it has to be encapsulated by the `DynamicSS()` function. E.g. here we designate the `Rodas5P` solver: -The forward ODE solving approach uses the ODE solvers implemented by the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package. If this package is loaded, it is possible to designate a specific solver to use. Any available ODE solver can be used, however, it has to be encapsulated by the `DynamicSS()` function. E.g. here we designate the `Rodas5P` solver: +(which requires loading the SteadyStateDiffEq package). ```@example steady_state_solving_simulation -using OrdinaryDiffEq +using SteadyStateDiffEq, OrdinaryDiffEq solve(ssprob, DynamicSS(Rodas5P())) -nothing # hide ``` +Note that, unlike for nonlinear system solving, `u0` is not just an initial guess of the solution, but the initial conditions from which the steady state simulation is carried out. This means that, for a system with multiple steady states, we can determine the steady states associated with specific initial conditions (which is not possible when the nonlinear solving approach is used). This also permits us to easily [handle the presence of conservation laws](@ref steady_state_solving_nonlinear_conservation_laws). The forward ODE simulation approach (unlike homotopy continuation and nonlinear solving) cannot find unstable steady states. + Generally, `SteadyStateProblem`s can be solved using the [same options that are available for ODE simulations](@ref simulation_intro_solver_options). E.g. here we designate a specific `dt` step size: ```@example steady_state_solving_simulation solve(ssprob, DynamicSS(Rodas5P()); dt = 0.01) @@ -145,6 +143,7 @@ If you use this functionality in your research, [in addition to Catalyst](@ref c } ``` + --- ## References [^1]: [J. Nocedal, S. J. Wright, *Numerical Optimization*, Springer (2006).](https://www.math.uci.edu/~qnie/Publications/NumericalOptimization.pdf) \ No newline at end of file From a55ad5b4f524e90adb9a359643b1e5a5c5204806 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 22 May 2024 17:09:20 -0400 Subject: [PATCH 016/446] init --- docs/src/api.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/api.md b/docs/src/api.md index 9e7086e6f8..25ae93e90f 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -43,6 +43,7 @@ t = default_t() rxs = [Reaction(β, [S,I], [I], [1,1], [2]) Reaction(γ, [I], [R])] @named rs = ReactionSystem(rxs, t) +rs = complete(rs) u₀map = [S => 999.0, I => 1.0, R => 0.0] parammap = [β => 1/10000, γ => 0.01] @@ -50,18 +51,21 @@ tspan = (0.0, 250.0) # solve as ODEs odesys = convert(ODESystem, rs) +odesys = complete(odesys) oprob = ODEProblem(odesys, u₀map, tspan, parammap) sol = solve(oprob, Tsit5()) p1 = plot(sol, title = "ODE") # solve as SDEs sdesys = convert(SDESystem, rs) +sdesys = complete(sdesys) sprob = SDEProblem(sdesys, u₀map, tspan, parammap) sol = solve(sprob, EM(), dt=.01) p2 = plot(sol, title = "SDE") # solve as jump process jumpsys = convert(JumpSystem, rs) +jumpsys = complete(jumpsys) u₀map = [S => 999, I => 1, R => 0] dprob = DiscreteProblem(jumpsys, u₀map, tspan, parammap) jprob = JumpProblem(jumpsys, dprob, Direct()) From 203a7eb84dfc0d892e1e4f32386ff9734370ac88 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 22 May 2024 17:58:36 -0400 Subject: [PATCH 017/446] init --- docs/src/model_creation/parametric_stoichiometry.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/model_creation/parametric_stoichiometry.md b/docs/src/model_creation/parametric_stoichiometry.md index 434002f72c..0788f01125 100644 --- a/docs/src/model_creation/parametric_stoichiometry.md +++ b/docs/src/model_creation/parametric_stoichiometry.md @@ -58,6 +58,7 @@ stoichiometries `(F,2*H,2)`. Let's now convert `revsys` to ODEs and look at the resulting equations: ```@example s1 osys = convert(ODESystem, revsys) +osys = complete(osys) equations(osys) show(stdout, MIME"text/plain"(), equations(osys)) # hide ``` @@ -88,6 +89,7 @@ converting to an `ODESystem`). For the previous example this gives the following (different) system of ODEs ```@example s1 osys = convert(ODESystem, revsys; combinatoric_ratelaws = false) +osys = complete(osys) equations(osys) show(stdout, MIME"text/plain"(), equations(osys)) # hide ``` @@ -140,6 +142,7 @@ The parameter `b` does not need to be explicitly declared in the We next convert our network to a jump process representation ```@example s1 jsys = convert(JumpSystem, burstyrn; combinatoric_ratelaws = false) +jsys = complete(jsys) equations(jsys) show(stdout, MIME"text/plain"(), equations(jsys)) # hide ``` From 7a7acc12a27cb76b45e48c4f3a75730e7a549b92 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 22 May 2024 18:04:23 -0400 Subject: [PATCH 018/446] init --- docs/src/model_creation/compositional_modeling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/model_creation/compositional_modeling.md b/docs/src/model_creation/compositional_modeling.md index d5d547ac28..e9a00b5d5c 100644 --- a/docs/src/model_creation/compositional_modeling.md +++ b/docs/src/model_creation/compositional_modeling.md @@ -18,7 +18,7 @@ end Alternatively one can just build the `ReactionSystem` via the symbolic interface. ```@example ex0 @parameters d -@variable t +t = default_t() @species X(t) rx = Reaction(d, [X], nothing) @named degradation_component = ReactionSystem([rs], t) From c31e73b3a1c83441a6e9d3b3c1bfed97e6530509 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 22 May 2024 18:19:11 -0400 Subject: [PATCH 019/446] init --- docs/src/model_creation/examples/hodgkin_huxley_equation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/model_creation/examples/hodgkin_huxley_equation.md b/docs/src/model_creation/examples/hodgkin_huxley_equation.md index ab1f072b6b..08d98d5b6e 100644 --- a/docs/src/model_creation/examples/hodgkin_huxley_equation.md +++ b/docs/src/model_creation/examples/hodgkin_huxley_equation.md @@ -77,6 +77,7 @@ I = I₀ * sin(2*pi*t/30)^2 # get the gating variables to use in the equation for dV/dt @unpack m,n,h = hhrn +Dₜ = default_time_deriv() eqs = [Dₜ(V) ~ -1/C * (ḡK*n^4*(V-EK) + ḡNa*m^3*h*(V-ENa) + ḡL*(V-EL)) + I/C] @named voltageode = ODESystem(eqs, t) nothing # hide @@ -88,6 +89,7 @@ Finally, we add this ODE into the reaction model as ```@example hh1 @named hhmodel = extend(voltageode, hhrn) +hhmodel = complete(hhmodel) nothing # hide ``` From 007e809aec1bcf990b9213e14ab4f4123d9f8ab0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 29 May 2024 17:19:09 -0400 Subject: [PATCH 020/446] up --- docs/pages.jl | 46 +++++++++++++++++++++++----------------------- docs/src/api.md | 8 ++++---- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index fd9cfa18ab..3307989831 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -1,61 +1,61 @@ pages = Any[ "Home" => "index.md", "Introduction to Catalyst" => Any[ - "introduction_to_catalyst/catalyst_for_new_julia_users.md", + #"introduction_to_catalyst/catalyst_for_new_julia_users.md", # "introduction_to_catalyst/introduction_to_catalyst.md" # Advanced introduction. ], "Model Creation and Properties" => Any[ - "model_creation/dsl_basics.md", - "model_creation/dsl_advanced.md", + #"model_creation/dsl_basics.md", + #"model_creation/dsl_advanced.md", #"model_creation/programmatic_CRN_construction.md", #"model_creation/compositional_modeling.md", #"model_creation/constraint_equations.md", # Events. #"model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. # Loading and writing models to files. - "model_creation/model_visualisation.md", + #"model_creation/model_visualisation.md", #"model_creation/network_analysis.md", - "model_creation/chemistry_related_functionality.md", + #"model_creation/chemistry_related_functionality.md", "Model creation examples" => Any[ - "model_creation/examples/basic_CRN_library.md", - "model_creation/examples/programmatic_generative_linear_pathway.md", + #"model_creation/examples/basic_CRN_library.md", + #"model_creation/examples/programmatic_generative_linear_pathway.md", #"model_creation/examples/hodgkin_huxley_equation.md", #"model_creation/examples/smoluchowski_coagulation_equation.md" ] ], "Model simulation" => Any[ - "model_simulation/simulation_introduction.md", + #"model_simulation/simulation_introduction.md", # Simulation introduction. - "model_simulation/simulation_plotting.md", - "model_simulation/simulation_structure_interfacing.md", - "model_simulation/ensemble_simulations.md", + #"model_simulation/simulation_plotting.md", + #"model_simulation/simulation_structure_interfacing.md", + #"model_simulation/ensemble_simulations.md", # Stochastic simulation statistical analysis. - "model_simulation/ode_simulation_performance.md", + #"model_simulation/ode_simulation_performance.md", # ODE Performance considerations/advice. # SDE Performance considerations/advice. # Jump Performance considerations/advice. # Finite state projection ], "Steady state analysis" => Any[ - "steady_state_functionality/homotopy_continuation.md", - "steady_state_functionality/nonlinear_solve.md", - "steady_state_functionality/steady_state_stability_computation.md", - "steady_state_functionality/bifurcation_diagrams.md", - "steady_state_functionality/dynamical_systems.md" + #"steady_state_functionality/homotopy_continuation.md", + #"steady_state_functionality/nonlinear_solve.md", + #"steady_state_functionality/steady_state_stability_computation.md", + #"steady_state_functionality/bifurcation_diagrams.md", + #"steady_state_functionality/dynamical_systems.md" ], "Inverse Problems" => Any[ # Inverse problems introduction. - "inverse_problems/optimization_ode_param_fitting.md", + #"inverse_problems/optimization_ode_param_fitting.md", # "inverse_problems/petab_ode_param_fitting.md", # ODE parameter fitting using Turing. # SDE/Jump fitting. - "inverse_problems/behaviour_optimisation.md", - "inverse_problems/structural_identifiability.md", + #"inverse_problems/behaviour_optimisation.md", + #"inverse_problems/structural_identifiability.md", # Practical identifiability. - "inverse_problems/global_sensitivity_analysis.md", + #"inverse_problems/global_sensitivity_analysis.md", "Inverse problem examples" => Any[ - "inverse_problems/examples/ode_fitting_oscillation.md" + #"inverse_problems/examples/ode_fitting_oscillation.md" ] ], "Spatial modelling" => Any[ @@ -68,5 +68,5 @@ pages = Any[ # # Repository structure. # ], #"FAQs" => "faqs.md", - #"API" => "api.md" + "API" => "api.md" ] \ No newline at end of file diff --git a/docs/src/api.md b/docs/src/api.md index 25ae93e90f..a0f386f3a2 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -35,7 +35,7 @@ corresponding chemical reaction ODE models, chemical Langevin equation SDE models, and stochastic chemical kinetics jump process models. ```@example ex1 -using Catalyst, DifferentialEquations, Plots +using Catalyst, OrdinaryDiffEq, StochasticDiffEq, JumpProcesses, Plots t = default_t() @parameters β γ @species S(t) I(t) R(t) @@ -60,7 +60,7 @@ p1 = plot(sol, title = "ODE") sdesys = convert(SDESystem, rs) sdesys = complete(sdesys) sprob = SDEProblem(sdesys, u₀map, tspan, parammap) -sol = solve(sprob, EM(), dt=.01) +sol = solve(sprob, EM(), dt=.01, saveat = 2.0) p2 = plot(sol, title = "SDE") # solve as jump process @@ -68,8 +68,8 @@ jumpsys = convert(JumpSystem, rs) jumpsys = complete(jumpsys) u₀map = [S => 999, I => 1, R => 0] dprob = DiscreteProblem(jumpsys, u₀map, tspan, parammap) -jprob = JumpProblem(jumpsys, dprob, Direct()) -sol = solve(jprob, SSAStepper()) +jprob = JumpProblem(jumpsys, dprob, Direct(); save_positions = (false,false)) +sol = solve(jprob, SSAStepper(), saveat = 2.0) p3 = plot(sol, title = "jump") plot(p1, p2, p3; layout = (3,1)) From c54a093e8ff9a9fedd0981c58d3fd7d0427d0e97 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 29 May 2024 17:21:48 -0400 Subject: [PATCH 021/446] up --- docs/pages.jl | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index 3307989831..1b969b4680 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -1,61 +1,61 @@ pages = Any[ "Home" => "index.md", "Introduction to Catalyst" => Any[ - #"introduction_to_catalyst/catalyst_for_new_julia_users.md", + "introduction_to_catalyst/catalyst_for_new_julia_users.md", # "introduction_to_catalyst/introduction_to_catalyst.md" # Advanced introduction. ], "Model Creation and Properties" => Any[ - #"model_creation/dsl_basics.md", - #"model_creation/dsl_advanced.md", + "model_creation/dsl_basics.md", + "model_creation/dsl_advanced.md", #"model_creation/programmatic_CRN_construction.md", #"model_creation/compositional_modeling.md", #"model_creation/constraint_equations.md", # Events. #"model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. # Loading and writing models to files. - #"model_creation/model_visualisation.md", + "model_creation/model_visualisation.md", #"model_creation/network_analysis.md", - #"model_creation/chemistry_related_functionality.md", + "model_creation/chemistry_related_functionality.md", "Model creation examples" => Any[ - #"model_creation/examples/basic_CRN_library.md", - #"model_creation/examples/programmatic_generative_linear_pathway.md", + "model_creation/examples/basic_CRN_library.md", + "model_creation/examples/programmatic_generative_linear_pathway.md", #"model_creation/examples/hodgkin_huxley_equation.md", #"model_creation/examples/smoluchowski_coagulation_equation.md" ] ], "Model simulation" => Any[ - #"model_simulation/simulation_introduction.md", + "model_simulation/simulation_introduction.md", # Simulation introduction. - #"model_simulation/simulation_plotting.md", - #"model_simulation/simulation_structure_interfacing.md", - #"model_simulation/ensemble_simulations.md", + "model_simulation/simulation_plotting.md", + "model_simulation/simulation_structure_interfacing.md", + "model_simulation/ensemble_simulations.md", # Stochastic simulation statistical analysis. - #"model_simulation/ode_simulation_performance.md", + "model_simulation/ode_simulation_performance.md", # ODE Performance considerations/advice. # SDE Performance considerations/advice. # Jump Performance considerations/advice. # Finite state projection ], "Steady state analysis" => Any[ - #"steady_state_functionality/homotopy_continuation.md", - #"steady_state_functionality/nonlinear_solve.md", - #"steady_state_functionality/steady_state_stability_computation.md", - #"steady_state_functionality/bifurcation_diagrams.md", - #"steady_state_functionality/dynamical_systems.md" + "steady_state_functionality/homotopy_continuation.md", + "steady_state_functionality/nonlinear_solve.md", + "steady_state_functionality/steady_state_stability_computation.md", + "steady_state_functionality/bifurcation_diagrams.md", + "steady_state_functionality/dynamical_systems.md" ], "Inverse Problems" => Any[ # Inverse problems introduction. - #"inverse_problems/optimization_ode_param_fitting.md", + "inverse_problems/optimization_ode_param_fitting.md", # "inverse_problems/petab_ode_param_fitting.md", # ODE parameter fitting using Turing. # SDE/Jump fitting. - #"inverse_problems/behaviour_optimisation.md", - #"inverse_problems/structural_identifiability.md", + "inverse_problems/behaviour_optimisation.md", + "inverse_problems/structural_identifiability.md", # Practical identifiability. - #"inverse_problems/global_sensitivity_analysis.md", + "inverse_problems/global_sensitivity_analysis.md", "Inverse problem examples" => Any[ - #"inverse_problems/examples/ode_fitting_oscillation.md" + "inverse_problems/examples/ode_fitting_oscillation.md" ] ], "Spatial modelling" => Any[ From 377d711f493c315f690a066da15607b7db39b2a9 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 29 May 2024 17:23:58 -0400 Subject: [PATCH 022/446] up --- docs/pages.jl | 2 +- docs/src/model_creation/examples/hodgkin_huxley_equation.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index fd9cfa18ab..e4c1441a70 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -20,7 +20,7 @@ pages = Any[ "Model creation examples" => Any[ "model_creation/examples/basic_CRN_library.md", "model_creation/examples/programmatic_generative_linear_pathway.md", - #"model_creation/examples/hodgkin_huxley_equation.md", + "model_creation/examples/hodgkin_huxley_equation.md", #"model_creation/examples/smoluchowski_coagulation_equation.md" ] ], diff --git a/docs/src/model_creation/examples/hodgkin_huxley_equation.md b/docs/src/model_creation/examples/hodgkin_huxley_equation.md index 08d98d5b6e..a2091035b1 100644 --- a/docs/src/model_creation/examples/hodgkin_huxley_equation.md +++ b/docs/src/model_creation/examples/hodgkin_huxley_equation.md @@ -13,7 +13,7 @@ cells such as neurons and muscle cells. We begin by importing some necessary packages. ```@example hh1 using ModelingToolkit, Catalyst, NonlinearSolve -using DifferentialEquations, Symbolics +using OrdinaryDiffEq, Symbolics using Plots t = default_t() D = default_time_deriv() From 535ad6a2f8875b209bf7679fedbea85be0402143 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 29 May 2024 17:29:49 -0400 Subject: [PATCH 023/446] up --- docs/pages.jl | 2 +- docs/src/model_creation/compositional_modeling.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index fd9cfa18ab..84c4ab7e9f 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -9,7 +9,7 @@ pages = Any[ "model_creation/dsl_basics.md", "model_creation/dsl_advanced.md", #"model_creation/programmatic_CRN_construction.md", - #"model_creation/compositional_modeling.md", + "model_creation/compositional_modeling.md", #"model_creation/constraint_equations.md", # Events. #"model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. diff --git a/docs/src/model_creation/compositional_modeling.md b/docs/src/model_creation/compositional_modeling.md index e9a00b5d5c..82e9bfc9d3 100644 --- a/docs/src/model_creation/compositional_modeling.md +++ b/docs/src/model_creation/compositional_modeling.md @@ -21,7 +21,7 @@ Alternatively one can just build the `ReactionSystem` via the symbolic interface t = default_t() @species X(t) rx = Reaction(d, [X], nothing) -@named degradation_component = ReactionSystem([rs], t) +@named degradation_component = ReactionSystem([rx], t) ``` We can test whether a system is complete using the `ModelingToolkit.iscomplete` function: ```@example ex0 From 56acc1f21f3ed69cc0acefee50474da67cba9b57 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 29 May 2024 18:16:59 -0400 Subject: [PATCH 024/446] up --- docs/pages.jl | 2 +- docs/src/model_creation/parametric_stoichiometry.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index fd9cfa18ab..b4daba47bd 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -12,7 +12,7 @@ pages = Any[ #"model_creation/compositional_modeling.md", #"model_creation/constraint_equations.md", # Events. - #"model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. + "model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. # Loading and writing models to files. "model_creation/model_visualisation.md", #"model_creation/network_analysis.md", diff --git a/docs/src/model_creation/parametric_stoichiometry.md b/docs/src/model_creation/parametric_stoichiometry.md index 0788f01125..a0d370ef0d 100644 --- a/docs/src/model_creation/parametric_stoichiometry.md +++ b/docs/src/model_creation/parametric_stoichiometry.md @@ -7,7 +7,7 @@ use symbolic stoichiometries, and discuss several caveats to be aware of. Let's first consider a simple reversible reaction where the number of reactants is a parameter, and the number of products is the product of two parameters. ```@example s1 -using Catalyst, Latexify, DifferentialEquations, ModelingToolkit, Plots +using Catalyst, Latexify, OrdinaryDiffEq, ModelingToolkit, Plots revsys = @reaction_network revsys begin k₊, m*A --> (m*n)*B k₋, B --> A @@ -141,6 +141,7 @@ The parameter `b` does not need to be explicitly declared in the We next convert our network to a jump process representation ```@example s1 +using JumpProcesses jsys = convert(JumpSystem, burstyrn; combinatoric_ratelaws = false) jsys = complete(jsys) equations(jsys) From 5275a1d6e9eb400fa325cdacfc99ede307e3245a Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 29 May 2024 19:38:20 -0400 Subject: [PATCH 025/446] use iscall --- Project.toml | 1 + ext/CatalystHomotopyContinuationExtension.jl | 3 ++- .../homotopy_continuation_extension.jl | 4 ++-- src/Catalyst.jl | 3 ++- src/latexify_recipes.jl | 2 +- src/reaction.jl | 2 +- src/reactionsystem.jl | 4 ++-- src/reactionsystem_conversions.jl | 4 ++-- src/registered_functions.jl | 2 +- test/dsl/dsl_basic_model_construction.jl | 5 +++-- 10 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Project.toml b/Project.toml index 2b90ff7b2a..4b5f8651d4 100644 --- a/Project.toml +++ b/Project.toml @@ -23,6 +23,7 @@ Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" +TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [weakdeps] diff --git a/ext/CatalystHomotopyContinuationExtension.jl b/ext/CatalystHomotopyContinuationExtension.jl index 6e7fdac50f..6c6185112f 100644 --- a/ext/CatalystHomotopyContinuationExtension.jl +++ b/ext/CatalystHomotopyContinuationExtension.jl @@ -6,7 +6,8 @@ import DynamicPolynomials import ModelingToolkit as MT import HomotopyContinuation as HC import Setfield: @set -import Symbolics: unwrap, wrap, Rewriters, symtype, issym, istree +import Symbolics: unwrap, wrap, Rewriters, symtype, issym +using TermInterface: iscall # Creates and exports hc_steady_states function. include("CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl") diff --git a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl index 1dcc64d9ba..848e158987 100644 --- a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl +++ b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl @@ -63,7 +63,7 @@ end # Parses and expression and return a version where any exponents that are Float64 (but an int, like 2.0) are turned into Int64s. make_int_exps(expr) = wrap(Rewriters.Postwalk(Rewriters.PassThrough(___make_int_exps))(unwrap(expr))).val function ___make_int_exps(expr) - !istree(expr) && return expr + !iscall(expr) && return expr if (operation(expr) == ^) if isinteger(arguments(expr)[2]) return arguments(expr)[1] ^ Int64(arguments(expr)[2]) @@ -76,7 +76,7 @@ end # If the input is a fraction, removes the denominator. function remove_denominators(expr) s_expr = simplify_fractions(expr) - !istree(expr) && return expr + !iscall(expr) && return expr if operation(s_expr) == / return remove_denominators(arguments(s_expr)[1]) end diff --git a/src/Catalyst.jl b/src/Catalyst.jl index d80c115119..b9a89f5f2f 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -24,7 +24,8 @@ RuntimeGeneratedFunctions.init(@__MODULE__) import Symbolics: BasicSymbolic import SymbolicUtils -using ModelingToolkit: Symbolic, value, istree, get_unknowns, get_ps, get_iv, get_systems, +using TermInterface: iscall +using ModelingToolkit: Symbolic, value, get_unknowns, get_ps, get_iv, get_systems, get_eqs, get_defaults, toparam, get_var_to_name, get_observed, getvar diff --git a/src/latexify_recipes.jl b/src/latexify_recipes.jl index a7d5cf1e7c..9528bd6bed 100644 --- a/src/latexify_recipes.jl +++ b/src/latexify_recipes.jl @@ -194,7 +194,7 @@ function make_stoich_str(spec, stoich, subber; mathrm = true, kwargs...) if isequal(stoich, one(stoich)) prestr * latexraw(subber(spec); kwargs...) * poststr else - if (stoich isa Symbolic) && istree(stoich) + if (stoich isa Symbolic) && iscall(stoich) LaTeXString("(") * latexraw(subber(stoich); kwargs...) * LaTeXString(")") * diff --git a/src/reaction.jl b/src/reaction.jl index 95eb7c0e71..a68a2dc2af 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -255,7 +255,7 @@ function print_rxside(io::IO, specs, stoich) spec : MT.operation(spec) if isequal(stoich[i], one(stoich[i])) print(io, prspec) - elseif istree(stoich[i]) + elseif iscall(stoich[i]) print(io, "(", stoich[i], ")*", prspec) else print(io, stoich[i], "*", prspec) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 5c04b2e642..d691cf35eb 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -1443,7 +1443,7 @@ function validate(rs::ReactionSystem, info::String = "") # Needs additional checks because for cases: (1.0^n) and (1.0^n1)*(1.0^n2). # These are not considered (be default) considered equal to `1.0` for unitless reactions. isequal(rxunits, rateunits) && continue - if istree(rxunits) + if iscall(rxunits) unitless_exp(rxunits) && continue (operation(rxunits) == *) && all(unitless_exp(arg) for arg in arguments(rxunits)) && continue end @@ -1456,4 +1456,4 @@ function validate(rs::ReactionSystem, info::String = "") end # Checks if a unit consist of exponents with base 1 (and is this unitless). -unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) \ No newline at end of file +unitless_exp(u) = iscall(u) && (operation(u) == ^) && (arguments(u)[1] == 1) \ No newline at end of file diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 38a634ff77..1c070e10ae 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -560,7 +560,7 @@ function nonlinear_convert_differentials_check(rs::ReactionSystem) # If the contenct of the differential is not a variable (and nothing more). # If either of this is a case, throws the warning. if Symbolics._occursin(Symbolics.is_derivative, eq.rhs) || - !Symbolics.istree(eq.lhs) || + !Symbolics.iscall(eq.lhs) || !isequal(Symbolics.operation(eq.lhs), Differential(get_iv(rs))) || (length(arguments(eq.lhs)) != 1) || !any(isequal(arguments(eq.lhs)[1]), nonspecies(rs)) @@ -895,7 +895,7 @@ function to_multivariate_poly(polyeqs::AbstractVector{Symbolics.BasicSymbolic{Re pvar2sym, sym2term = SymbolicUtils.get_pvar2sym(), SymbolicUtils.get_sym2term() ps = map(polyeqs) do x - if istree(x) && operation(x) == (/) + if iscall(x) && operation(x) == (/) error("We should not be able to get here, please contact the package authors.") else PolyForm(x, pvar2sym, sym2term).p diff --git a/src/registered_functions.jl b/src/registered_functions.jl index 70f06ac080..5de41e4d0f 100644 --- a/src/registered_functions.jl +++ b/src/registered_functions.jl @@ -118,7 +118,7 @@ expand_registered_functions(expr) Takes an expression, and expands registered function expressions. E.g. `mm(X,v,K)` is replaced with v*X/(X+K). Currently supported functions: `mm`, `mmr`, `hill`, `hillr`, and `hill`. """ function expand_registered_functions(expr) - istree(expr) || return expr + iscall(expr) || return expr args = arguments(expr) if operation(expr) == Catalyst.mm return args[2]*args[1]/(args[1] + args[3]) diff --git a/test/dsl/dsl_basic_model_construction.jl b/test/dsl/dsl_basic_model_construction.jl index 72dd01f1ab..dba2bbea01 100644 --- a/test/dsl/dsl_basic_model_construction.jl +++ b/test/dsl/dsl_basic_model_construction.jl @@ -2,8 +2,9 @@ # Fetch packages. using DiffEqBase, Catalyst, Random, Test -using ModelingToolkit: operation, istree, get_unknowns, get_ps, get_eqs, get_systems, +using ModelingToolkit: operation, get_unknowns, get_ps, get_eqs, get_systems, get_iv, nameof +using TermInterface: iscall # Sets stable rng number. using StableRNGs @@ -22,7 +23,7 @@ function unpacksys(sys) get_eqs(sys), get_iv(sys), get_unknowns(sys), get_ps(sys), nameof(sys), get_systems(sys) end -opname(x) = istree(x) ? nameof(operation(x)) : nameof(x) +opname(x) = iscall(x) ? nameof(operation(x)) : nameof(x) alleq(xs, ys) = all(isequal(x, y) for (x, y) in zip(xs, ys)) # Gets all the reactants in a set of equations. From 18813a282095ff13441536c99ac2bf8d3be982a3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 29 May 2024 19:38:46 -0400 Subject: [PATCH 026/446] reactivate old tests --- test/runtests.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 8f255921d7..2de4ae3ee8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -18,7 +18,7 @@ using SafeTestsets, Test @time @safetestset "Symbolic Stoichiometry" begin include("reactionsystem_core/symbolic_stoichiometry.jl") end @time @safetestset "Parameter Type Designation" begin include("reactionsystem_core/parameter_type_designation.jl") end @time @safetestset "Custom CRN Functions" begin include("reactionsystem_core/custom_crn_functions.jl") end - # @time @safetestset "Coupled CRN/Equation Systems" begin include("reactionsystem_core/coupled_equation_crn_systems.jl") end + @time @safetestset "Coupled CRN/Equation Systems" begin include("reactionsystem_core/coupled_equation_crn_systems.jl") end @time @safetestset "Events" begin include("reactionsystem_core/events.jl") end # Tests model creation via the @reaction_network DSL. @@ -72,9 +72,9 @@ using SafeTestsets, Test end # Tests extensions. - # @time @safetestset "BifurcationKit Extension" begin include("extensions/bifurcation_kit.jl") end - # @time @safetestset "HomotopyContinuation Extension" begin include("extensions/homotopy_continuation.jl") end - # @time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end + @time @safetestset "BifurcationKit Extension" begin include("extensions/bifurcation_kit.jl") end + @time @safetestset "HomotopyContinuation Extension" begin include("extensions/homotopy_continuation.jl") end + @time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end #end end # @time From bfa8f907761dc7bc377a428de0b3d2027e253b3f Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 29 May 2024 19:47:10 -0400 Subject: [PATCH 027/446] comment out SI doc (broken on v1.10.3, need 1.10.2 or new Julia release) --- docs/pages.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages.jl b/docs/pages.jl index fd9cfa18ab..abfd24c0f1 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -51,7 +51,7 @@ pages = Any[ # ODE parameter fitting using Turing. # SDE/Jump fitting. "inverse_problems/behaviour_optimisation.md", - "inverse_problems/structural_identifiability.md", + #"inverse_problems/structural_identifiability.md", # Practical identifiability. "inverse_problems/global_sensitivity_analysis.md", "Inverse problem examples" => Any[ From dd833de03d2a993eefacf12d1dad54eda9e0ee0c Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 30 May 2024 10:50:03 -0400 Subject: [PATCH 028/446] Symbolics -> TermInterface --- src/reactionsystem_conversions.jl | 2 +- test/runtests.jl | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 1c070e10ae..70c17c3f8c 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -560,7 +560,7 @@ function nonlinear_convert_differentials_check(rs::ReactionSystem) # If the contenct of the differential is not a variable (and nothing more). # If either of this is a case, throws the warning. if Symbolics._occursin(Symbolics.is_derivative, eq.rhs) || - !Symbolics.iscall(eq.lhs) || + !iscall(eq.lhs) || !isequal(Symbolics.operation(eq.lhs), Differential(get_iv(rs))) || (length(arguments(eq.lhs)) != 1) || !any(isequal(arguments(eq.lhs)[1]), nonspecies(rs)) diff --git a/test/runtests.jl b/test/runtests.jl index 2de4ae3ee8..1a8f58c323 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,13 +11,6 @@ using SafeTestsets, Test @time begin #if GROUP == "All" || GROUP == "ModelCreation" - # Tests the `ReactionSystem` structure and its properties. - @time @safetestset "Reaction Structure" begin include("reactionsystem_core/reaction.jl") end - @time @safetestset "ReactionSystem Structure" begin include("reactionsystem_core/reactionsystem.jl") end - @time @safetestset "Higher Order Reactions" begin include("reactionsystem_core/higher_order_reactions.jl") end - @time @safetestset "Symbolic Stoichiometry" begin include("reactionsystem_core/symbolic_stoichiometry.jl") end - @time @safetestset "Parameter Type Designation" begin include("reactionsystem_core/parameter_type_designation.jl") end - @time @safetestset "Custom CRN Functions" begin include("reactionsystem_core/custom_crn_functions.jl") end @time @safetestset "Coupled CRN/Equation Systems" begin include("reactionsystem_core/coupled_equation_crn_systems.jl") end @time @safetestset "Events" begin include("reactionsystem_core/events.jl") end From c86c500aeaa1aa60262df0cd80a2b3c57da6fa48 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 30 May 2024 13:39:38 -0400 Subject: [PATCH 029/446] up --- docs/pages.jl | 4 +--- .../steady_state_functionality/bifurcation_diagrams.md | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index abfd24c0f1..260c491979 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -26,13 +26,11 @@ pages = Any[ ], "Model simulation" => Any[ "model_simulation/simulation_introduction.md", - # Simulation introduction. "model_simulation/simulation_plotting.md", "model_simulation/simulation_structure_interfacing.md", "model_simulation/ensemble_simulations.md", # Stochastic simulation statistical analysis. "model_simulation/ode_simulation_performance.md", - # ODE Performance considerations/advice. # SDE Performance considerations/advice. # Jump Performance considerations/advice. # Finite state projection @@ -51,7 +49,7 @@ pages = Any[ # ODE parameter fitting using Turing. # SDE/Jump fitting. "inverse_problems/behaviour_optimisation.md", - #"inverse_problems/structural_identifiability.md", + #"inverse_problems/structural_identifiability.md", # Broken on Julia v1.10.3, requires v1.10.2 or 1.10.4. # Practical identifiability. "inverse_problems/global_sensitivity_analysis.md", "Inverse problem examples" => Any[ diff --git a/docs/src/steady_state_functionality/bifurcation_diagrams.md b/docs/src/steady_state_functionality/bifurcation_diagrams.md index a76ff91393..cebe51dc1c 100644 --- a/docs/src/steady_state_functionality/bifurcation_diagrams.md +++ b/docs/src/steady_state_functionality/bifurcation_diagrams.md @@ -43,7 +43,7 @@ nothing # hide Finally, we compute our bifurcation diagram using: ```@example ex1 -bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside = true) +bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true) nothing # hide ``` Where `PALC()` designates that we wish to use the pseudo arclength continuation method to track our solution. The third argument (`2`) designates the maximum number of recursions when branches of branches are computed (branches appear as continuation encounters certain bifurcation points). For diagrams with highly branched structures (rare for many common small chemical reaction networks) this input is important. Finally, `bothside = true` designates that we wish to perform continuation on both sides of the initial point (which is typically the case). @@ -69,7 +69,7 @@ opt_newton = NewtonPar(tol = 1e-9, max_iterations = 1000) opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], dsmin = 0.001, dsmax = 0.01, max_steps = 1000, newton_options = opt_newton) -bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside=true) +bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true) nothing # hide ``` (however, in this case these additional settings have no significant effect on the result) @@ -79,7 +79,7 @@ Let's consider the previous case, but instead compute the bifurcation diagram ov ```@example ex1 p_span = (2.0, 15.0) opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000) -bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br= true) +bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true) plot(bif_dia; xguide = "k1", yguide = "X") ``` Here, in the bistable region, we only see a single branch. The reason is that the continuation algorithm starts at our initial guess (here made at $k1 = 4.0$ for $(X,Y) = (5.0,2.0)$) and tracks the diagram from there. However, with the upper bound set at $k1=15.0$ the bifurcation diagram has a disjoint branch structure, preventing the full diagram from being computed by continuation alone. In this case it could be solved by increasing the bound from $k1=15.0$, however, this is not possible in all cases. In these cases, *deflation* can be used. This is described in the [BifurcationKit documentation](https://bifurcationkit.github.io/BifurcationKitDocs.jl/dev/tutorials/tutorials2/#Snaking-computed-with-deflation). @@ -103,7 +103,7 @@ bprob = BifurcationProblem(kinase_model, u_guess, p_start, :d; plot_var = :Xp, u p_span = (0.1, 10.0) opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000) -bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside = true) +bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true) plot(bif_dia; xguide = "d", yguide = "Xp") ``` This bifurcation diagram does not contain any interesting features (such as bifurcation points), and only shows how the steady state concentration of $Xp$ is reduced as $d$ increases. From 25114e192a06ea48d9abf60d3e8643a35a172c5d Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 25 Apr 2024 15:40:45 -0400 Subject: [PATCH 030/446] save progress --- docs/src/home.md | 341 ++++++++++++++++++++++++++++++----------------- 1 file changed, 222 insertions(+), 119 deletions(-) diff --git a/docs/src/home.md b/docs/src/home.md index 81053b8b2c..dacff731b9 100644 --- a/docs/src/home.md +++ b/docs/src/home.md @@ -1,160 +1,263 @@ -# Catalyst.jl for Reaction Network Modeling +# [Catalyst.jl for Reaction Network Modeling](@id doc_home) -Catalyst.jl is a symbolic modeling package for analysis and high performance +Catalyst.jl is a symbolic modeling package for analysis and high-performance simulation of chemical reaction networks. Catalyst defines symbolic -[`ReactionSystem`](@ref)s, which can be created programmatically or easily -specified using Catalyst's domain specific language (DSL). Leveraging -[ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and -[Symbolics.jl](https://docs.sciml.ai/Symbolics/stable/), Catalyst enables +[`ReactionSystem`](https://docs.sciml.ai/Catalyst/stable/catalyst_functionality/programmatic_CRN_construction/)s, +which can be created programmatically or easily +specified using Catalyst's domain-specific language (DSL). Leveraging +[ModelingToolkit.jl](https://github.com/SciML/ModelingToolkit.jl) and +[Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl), Catalyst enables large-scale simulations through auto-vectorization and parallelism. Symbolic `ReactionSystem`s can be used to generate ModelingToolkit-based models, allowing the easy simulation and parameter estimation of mass action ODE models, Chemical Langevin SDE models, stochastic chemical kinetics jump process models, and more. Generated models can be used with solvers throughout the broader -[SciML](https://sciml.ai) ecosystem, including higher level SciML packages (e.g. +[SciML](https://sciml.ai) ecosystem, including higher-level SciML packages (e.g. for sensitivity analysis, parameter estimation, machine learning applications, etc). -## Features -- A DSL provides a simple and readable format for manually specifying chemical - reactions. -- Catalyst `ReactionSystem`s provide a symbolic representation of reaction networks, - built on [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and - [Symbolics.jl](https://docs.sciml.ai/Symbolics/stable/). -- Non-integer (e.g. `Float64`) stoichiometric coefficients are supported for generating - ODE models, and symbolic expressions for stoichiometric coefficients are supported for - all system types. -- The [Catalyst.jl API](@ref) provides functionality for extending networks, - building networks programmatically, network analysis, and for composing multiple - networks together. -- `ReactionSystem`s generated by the DSL can be converted to a variety of - `ModelingToolkit.AbstractSystem`s, including symbolic ODE, SDE and jump process - representations. -- Coupled differential and algebraic constraint equations can be included in - Catalyst models, and are incorporated during conversion to ODEs or steady - state equations. -- Conservation laws can be detected and applied to reduce system sizes, and - generate non-singular Jacobians, during conversion to ODEs, SDEs, and steady - state equations. +## [Features](@id doc_home_features) + +#### [Features of Catalyst](@id doc_home_features_catalyst) + +- [The Catalyst DSL](@ref ref) provides a simple and readable format for manually specifying reaction + network models using chemical reaction notation. +- Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, + built on [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and + [Symbolics.jl](https://docs.sciml.ai/Symbolics/stable/). +- The [Catalyst.jl API](http://docs.sciml.ai/Catalyst/stable/api/catalyst_api) provides functionality + for extending networks, building networks programmatically, and for composing + multiple networks together. +- Generated systems can be simulated using any + [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) + [ODE/SDE/jump solver](@ref ref), and can be used within `EnsembleProblem`s for carrying + out [parallelized parameter sweeps and statistical sampling](@ref ref). Plot recipes + are available for [visualizing of all solutions](@ref ref). +- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref ref) for generating + ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref ref) for + all system types. +- A [network analysis suite](@ref ref) permits the computation of linkage classes, deficiencies, and + reversibilities. +- [Conservation laws can be detected and utilised](@ref ref) to reduce system sizes, and to generate + non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). +- Catalyst reaction network models can be [coupled with differential and algebraic equations](@ref ref) + (which are then incorporated during conversion to ODEs, SDEs, and steady state equations). +- Models can be [coupled with events](@ref ref) that affect the system and its state during simulations. - By leveraging ModelingToolkit, users have a variety of options for generating - optimized system representations to use in solvers. These include construction - of dense or sparse Jacobians, multithreading or parallelization of generated - derivative functions, automatic classification of reactions into optimized - jump types for Gillespie type simulations, automatic construction of - dependency graphs for jump systems, and more. -- Generated systems can be solved using any - [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) - ODE/SDE/jump solver, and can be used within `EnsembleProblem`s for carrying - out parallelized parameter sweeps and statistical sampling. Plot recipes - are available for visualizing the solutions. + optimized system representations to use in solvers. These include construction + of [dense or sparse Jacobians](@ref ref), [multithreading or parallelization of generated + derivative functions](@ref ref), [automatic classification of reactions into optimized + jump types for Gillespie type simulations](@ref ref), [automatic construction of + dependency graphs for jump systems](@ref ref), and more. - [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl) symbolic - expressions and Julia `Expr`s can be obtained for all rate laws and functions - determining the deterministic and stochastic terms within resulting ODE, SDE - or jump models. -- [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to generate - LaTeX expressions corresponding to generated mathematical models or the - underlying set of reactions. -- [Graphviz](https://graphviz.org/) can be used to generate and visualize - reaction network graphs. (Reusing the Graphviz interface created in - [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/).) - -## Packages Supporting Catalyst -- Catalyst [`ReactionSystem`](@ref)s can be imported from SBML files via - [SBMLToolkit.jl](https://docs.sciml.ai/SBMLToolkit/stable/), and from BioNetGen .net - files and various stoichiometric matrix network representations using - [ReactionNetworkImporters.jl](https://docs.sciml.ai/ReactionNetworkImporters/stable/). -- [MomentClosure.jl](https://augustinas1.github.io/MomentClosure.jl/dev) allows - generation of symbolic ModelingToolkit `ODESystem`s, representing moment - closure approximations to moments of the Chemical Master Equation, from - reaction networks defined in Catalyst. -- [FiniteStateProjection.jl](https://kaandocal.github.io/FiniteStateProjection.jl/dev/) - allows the construction and numerical solution of Chemical Master Equation - models from reaction networks defined in Catalyst. -- [DelaySSAToolkit.jl](https://palmtree2013.github.io/DelaySSAToolkit.jl/dev/) can - augment Catalyst reaction network models with delays, and can simulate the - resulting stochastic chemical kinetics with delays models. + expressions and Julia `Expr`s can be obtained for all rate laws and functions determining the + deterministic and stochastic terms within resulting ODE, SDE or jump models. +- [Steady states](@ref ref) (and their [stabilities](@ref ref)) can be computed for model ODE representations. + + +#### [Features of Catalyst composing with other packages](@id doc_home_features_composed) +- [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to [perform model ODE + simulations](@ref ref). +- [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) Can be used to [perform model + SDE simulations](@ref ref). +- [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) Can be used to [model jump + simulations](@ref ref). +- Support for [parallelisation of all simulations]((@ref ref)), including parallelisation of + [ODE simulations on GPUs](@ref ref) using + [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). +- [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX + expressions](@ref ref) corresponding to generated mathematical models or the + underlying set of reactions. +- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](@ref ref) + (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/).) +- Models steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) + (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using + [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by nonlinear systems + solving using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). +- [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute + bifurcation diagrams](@ref ref) of models' steady states (including finding periodic orbits). +- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute + model [basins of attraction](@ref ref) and [Lyapunov spectrums](@ref ref). +- [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used + to [perform structural identifiability analysis](@ref ref). +- [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), + and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](@ref ref). +- [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform + [global sensitivity analysis](@ref ref) of model behaviours. + +#### [Features of packages built upon Catalyst](@id doc_home_features_other_packages) +- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref ref) via + [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), + and [from BioNetGen .net files](@ref ref) and various stoichiometric matrix network representations + using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). +- [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic + ModelingToolkit `ODESystem`s that represent moment closure approximations to moments of the + Chemical Master Equation, from reaction networks defined in Catalyst. +- [FiniteStateProjection.jl](https://github.com/kaandocal/FiniteStateProjection.jl) + allows the construction and numerical solution of Chemical Master Equation + models from reaction networks defined in Catalyst. +- [DelaySSAToolkit.jl](https://github.com/palmtree2013/DelaySSAToolkit.jl) can + augment Catalyst reaction network models with delays, and can simulate the + resulting stochastic chemical kinetics with delays models. - [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl) a package for - constructing and analyzing bond graphs models, which can take Catalyst models as input. -- [PEtab.jl](https://github.com/sebapersson/PEtab.jl) a package that implements the PEtab format for fitting reaction network ODEs to data. Input can be provided either as SBML files or as Catalyst `ReactionSystem`s. - + constructing and analyzing bond graphs models, which can take Catalyst models as input. +- [PEtab.jl](https://github.com/sebapersson/PEtab.jl) a package that implements the PEtab format for + fitting reaction network ODEs to data. Input can be provided either as SBML files or as Catalyst + `ReactionSystem`s. + -## Installation -Catalyst can be installed through the Julia package manager: +## [How to read this documentation](@id doc_home_documentation) +New users are recommended to start with either the [Introduction to Catalyst and Julia for New Julia users](@ref catalyst_for_new_julia_users) or [Introduction to Catalyst](@ref introduction_to_catalyst) sections (depending on whether they are familiar with Julia programming or not). This should be enough to carry out many basic Catalyst workflows. Next, [The Catalyst DSL](@ref ref) section gives a more throughout introduction to model creation, while the [Introduction to model simulation](@ref ref) section more through describes how simulations are carried out. Once you have gotten started using Catalyst, you can read whichever sections are relevant to your work (they should all be clearly labelled). +This documentation contains code which is dynamically run whenever it is built. If you copy the code and run it in your Julia environment it should work. The exact Julia environment that is used in this documentation can be found [here](@ref doc_home_reproducibility). + +For most code blocks in this documentation, the output of the last line of code is printed at the of the block, e.g. +```@example home1 +1 + 2 +``` +and +```@example home1 +@reaction_network begin + (p,d), 0 <--> X +end +``` +However, in some situations (e.g. when output is extensive, or irrelevant to what is currently being described) we have disabled this, e.g. like here: +```@example home1 +1 + 2 +nothing # hide +``` +and +```@example home1 +@reaction_network begin + (p,d), 0 <--> X +end +nothing # hide +``` + +## [Installation](@id doc_home_installation) +Catalyst is an officially registered Julia package, which can be installed through the Julia package manager: ```julia using Pkg Pkg.add("Catalyst") ``` -To solve Catalyst models and visualize solutions, it is also recommended to -install DifferentialEquations.jl and Plots.jl +Many Catalyst features require the installation of additional packages. E.g. for ODE-solving and simulation plotting ```julia -Pkg.add("DifferentialEquations") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` +is also needed. + +A more throughout guide for setting up Catalyst and installing Julia packages can be found [here](@ref ref). -## Illustrative Example -Here is a simple example of generating, visualizing and solving an SIR ODE -model. We first define the SIR reaction model using Catalyst -```@example ind1 -using Catalyst -rn = @reaction_network begin - α, S + I --> 2I - β, I --> R +## [Illustrative example](@id doc_home_illustrative_example) + +#### [Deterministic ODE simulation of Michaelis-Menten enzyme kinetics](@id doc_home_illustrative_example_jump) +Here we show a simple example where a model is created using the Catalyst DSL, and then simulated as +an ordinary differential equation. + +```@example home2 +# Fetch required packages. +using Catalyst, DifferentialEquations, Plots + +# Create model. +model = @reaction_network begin + kB, S + E --> SE + kD, SE --> S + E + kP, SE --> P + E end + +# Create an ODE that can be simulated. +u0 = [:S => 50, :E => 10, :SE => 0, :P => 0] +tspan = (0., 200.) +ps = (:kB => 0.01, :kD => 0.1, :kP => 0.1) +ode = ODEProblem(model, u0, tspan, ps) + +# Simulate ODE and plot results. +sol = solve(ode) +plot(sol; lw = 5) ``` -Assuming [Graphviz](https://graphviz.org/) and is installed and *command line -accessible*, the network can be visualized using the [`Graph`](@ref) command -```julia -Graph(rn) + +![](https://user-images.githubusercontent.com/1814174/87864114-3bf9dd00-c932-11ea-83a0-58f38aee8bfb.png) + +#### [Stochastic jump simulations](@id doc_home_illustrative_example_jump) +The same model can be used as input to other types of simulations. E.g. here we instead perform a +jump simulation +```@example home2 +# Create and simulate a jump process (here using Gillespie's direct algorithm). +dprob = DiscreteProblem(model, u0, tspan, ps) +jprob = JumpProblem(model, dprob, Direct()) +jump_sol = solve(jprob, SSAStepper()) +plot(jump_sol; lw = 2) ``` -which in Jupyter notebooks will give the figure -![SIR Network Graph](assets/SIR_rn.svg) -To generate and solve a mass action ODE version of the model we use -```@example ind1 -using DifferentialEquations -p = [:α => .1/1000, :β => .01] -tspan = (0.0,250.0) -u0 = [:S => 999.0, :I => 1.0, :R => 0.0] -op = ODEProblem(rn, u0, tspan, p) -sol = solve(op, Tsit5()) # use Tsit5 ODE solver +## [Elaborate example](@id doc_home_elaborate_example) + +In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). Typically the growth factor is inactive ($Gi$), but it is activated ($Ga$) by the presence of sunlight (modeled as the cyclic sinusoid $kA*(sin(t)+1)$). When the cell reaches a critical volume ($V$) it goes through cell division. First, we declare our model: +```@example home3 +using Catalyst, Plots, StochasticDiffEq # hide +cell_model = @reaction_network begin + @parameters V_thres g + @equations begin + D(V) ~ g*Ga + end + @continuous_events begin + [V ~ V_thres] => [V ~ V/2] + end + (kA*(sin(t)+1), kI), Gi <--> Ga +end ``` -which we can plot as -```@example ind1 -using Plots -plot(sol, lw=2) +Next, we can use [Latexify.jl](https://korsbo.github.io/Latexify.jl/stable/) to show the ordinary differential equations associated with this model: +```@example home3 +using Latexify +latexify(cell_model; form = :ode) +``` +In this case we would like to perform stochastic simulations, so we transform our model to an SDE: +```@example home3 +u0 = [:V => 0.5, :Gi => 1.0, :Ga => 0.0] +tspan = (0.0, 10.0) +ps = [:V_thres => 1.0, :g => 0.5, :kA => 5.0, :kI => 2.0] +sprob = SDEProblem(cell_model, u0, tspan, ps) +``` +Finally, we simulate it and plot the result. +```@example home3 +sol = solve(oprob, Tsit5()) +plot(sol) ``` -## Getting Help -Catalyst developers are active on the [Julia -Discourse](https://discourse.julialang.org/), and the [Julia -Slack's](https://julialang.slack.com) \#sciml-bridged and \#sciml-sysbio channels. -For bugs or feature requests [open an -issue](https://github.com/SciML/Catalyst.jl/issues). +## [Getting Help](@id doc_home_help) +Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/), +the [Julia Slack](https://julialang.slack.com) channels \#sciml-bridged and \#sciml-sysbio, and the +[Julia Zulip sciml-bridged channel](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged). +For bugs or feature requests, [open an issue](https://github.com/SciML/Catalyst.jl/issues). -## [Supporting and Citing Catalyst.jl](@id catalyst_citation) -The software in this ecosystem was developed as part of academic research. If you would like to help support it, -please star the repository as such metrics may help us secure funding in the future. If you use Catalyst as part -of your research, teaching, or other activities, we would be grateful if you could cite our work: +## [Supporting and Citing Catalyst.jl](@id doc_home_citation) +The software in this ecosystem was developed as part of academic research. If you would like to help +support it, please star the repository as such metrics may help us secure funding in the future. If +you use Catalyst as part of your research, teaching, or other activities, we would be grateful if you +could cite our work: ``` @article{CatalystPLOSCompBio2023, - doi = {10.1371/journal.pcbi.1011530}, - author = {Loman, Torkel E. AND Ma, Yingbo AND Ilin, Vasily AND Gowda, Shashi AND Korsbo, Niklas AND Yewale, Nikhil AND Rackauckas, Chris AND Isaacson, Samuel A.}, - journal = {PLOS Computational Biology}, - publisher = {Public Library of Science}, - title = {Catalyst: Fast and flexible modeling of reaction networks}, - year = {2023}, - month = {10}, - volume = {19}, - url = {https://doi.org/10.1371/journal.pcbi.1011530}, - pages = {1-19}, - number = {10}, + doi = {10.1371/journal.pcbi.1011530}, + author = {Loman, Torkel E. AND Ma, Yingbo AND Ilin, Vasily AND Gowda, Shashi AND Korsbo, Niklas AND Yewale, Nikhil AND Rackauckas, Chris AND Isaacson, Samuel A.}, + journal = {PLOS Computational Biology}, + publisher = {Public Library of Science}, + title = {Catalyst: Fast and flexible modeling of reaction networks}, + year = {2023}, + month = {10}, + volume = {19}, + url = {https://doi.org/10.1371/journal.pcbi.1011530}, + pages = {1-19}, + number = {10}, } ``` -## Reproducibility +We also maintain a user survey, asking basic questions about how users utilise the package. The survey +is available [here](ref), and only takes about 5 minutes to fill out. We are grateful to those who +fill out the survey, as this helps us further develop the package. + +## [Reproducibility](@id doc_home_reproducibility) ```@raw html
The documentation of this SciML package was built using these direct dependencies, ``` From 32b15fa75adb6fed77a0c6d44f2fd7c11569d059 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 28 Apr 2024 15:53:37 -0400 Subject: [PATCH 031/446] save progress --- docs/src/home.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/home.md b/docs/src/home.md index dacff731b9..b83685e7b1 100644 --- a/docs/src/home.md +++ b/docs/src/home.md @@ -107,6 +107,8 @@ etc). ## [How to read this documentation](@id doc_home_documentation) +The Catalyst documentation is separated into sections describing Catalyst's various features. Where appropriate, some sections will also give advice on best practices for various modelling workflows, and provide links with further reading. Each section also contains a set of relevant example workflows. Finally, the [API](@ref api) section contains a list of all functions exported by Catalyst (as well as descriptions of them and their inputs and outputs). + New users are recommended to start with either the [Introduction to Catalyst and Julia for New Julia users](@ref catalyst_for_new_julia_users) or [Introduction to Catalyst](@ref introduction_to_catalyst) sections (depending on whether they are familiar with Julia programming or not). This should be enough to carry out many basic Catalyst workflows. Next, [The Catalyst DSL](@ref ref) section gives a more throughout introduction to model creation, while the [Introduction to model simulation](@ref ref) section more through describes how simulations are carried out. Once you have gotten started using Catalyst, you can read whichever sections are relevant to your work (they should all be clearly labelled). This documentation contains code which is dynamically run whenever it is built. If you copy the code and run it in your Julia environment it should work. The exact Julia environment that is used in this documentation can be found [here](@ref doc_home_reproducibility). From d784d7fd0a096ed2c9121bf68885d38f07bdb73f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 18 May 2024 14:48:54 -0400 Subject: [PATCH 032/446] up --- docs/src/home.md | 90 ++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/docs/src/home.md b/docs/src/home.md index b83685e7b1..1f52d5b20c 100644 --- a/docs/src/home.md +++ b/docs/src/home.md @@ -9,17 +9,16 @@ specified using Catalyst's domain-specific language (DSL). Leveraging [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl), Catalyst enables large-scale simulations through auto-vectorization and parallelism. Symbolic `ReactionSystem`s can be used to generate ModelingToolkit-based models, allowing -the easy simulation and parameter estimation of mass action ODE models, Chemical +the easy simulation and parameter estimation of mass action ODE models, chemical Langevin SDE models, stochastic chemical kinetics jump process models, and more. Generated models can be used with solvers throughout the broader [SciML](https://sciml.ai) ecosystem, including higher-level SciML packages (e.g. for sensitivity analysis, parameter estimation, machine learning applications, etc). -## [Features](@id doc_home_features) - -#### [Features of Catalyst](@id doc_home_features_catalyst) +## Features +#### Features of Catalyst - [The Catalyst DSL](@ref ref) provides a simple and readable format for manually specifying reaction network models using chemical reaction notation. - Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, @@ -28,7 +27,7 @@ etc). - The [Catalyst.jl API](http://docs.sciml.ai/Catalyst/stable/api/catalyst_api) provides functionality for extending networks, building networks programmatically, and for composing multiple networks together. -- Generated systems can be simulated using any +- Generated models can be simulated using any [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) [ODE/SDE/jump solver](@ref ref), and can be used within `EnsembleProblem`s for carrying out [parallelized parameter sweeps and statistical sampling](@ref ref). Plot recipes @@ -54,8 +53,7 @@ etc). deterministic and stochastic terms within resulting ODE, SDE or jump models. - [Steady states](@ref ref) (and their [stabilities](@ref ref)) can be computed for model ODE representations. - -#### [Features of Catalyst composing with other packages](@id doc_home_features_composed) +#### Features of Catalyst composing with other packages - [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to [perform model ODE simulations](@ref ref). - [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) Can be used to [perform model @@ -85,7 +83,7 @@ etc). - [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform [global sensitivity analysis](@ref ref) of model behaviours. -#### [Features of packages built upon Catalyst](@id doc_home_features_other_packages) +#### Features of packages built upon Catalyst - Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref ref) via [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), and [from BioNetGen .net files](@ref ref) and various stoichiometric matrix network representations @@ -104,7 +102,6 @@ etc). - [PEtab.jl](https://github.com/sebapersson/PEtab.jl) a package that implements the PEtab format for fitting reaction network ODEs to data. Input can be provided either as SBML files or as Catalyst `ReactionSystem`s. - ## [How to read this documentation](@id doc_home_documentation) The Catalyst documentation is separated into sections describing Catalyst's various features. Where appropriate, some sections will also give advice on best practices for various modelling workflows, and provide links with further reading. Each section also contains a set of relevant example workflows. Finally, the [API](@ref api) section contains a list of all functions exported by Catalyst (as well as descriptions of them and their inputs and outputs). @@ -120,7 +117,7 @@ For most code blocks in this documentation, the output of the last line of code and ```@example home1 @reaction_network begin - (p,d), 0 <--> X + (p,d), 0 <--> X end ``` However, in some situations (e.g. when output is extensive, or irrelevant to what is currently being described) we have disabled this, e.g. like here: @@ -131,7 +128,7 @@ nothing # hide and ```@example home1 @reaction_network begin - (p,d), 0 <--> X + (p,d), 0 <--> X end nothing # hide ``` @@ -152,21 +149,21 @@ is also needed. A more throughout guide for setting up Catalyst and installing Julia packages can be found [here](@ref ref). -## [Illustrative example](@id doc_home_illustrative_example) +## Illustrative example -#### [Deterministic ODE simulation of Michaelis-Menten enzyme kinetics](@id doc_home_illustrative_example_jump) +#### Deterministic ODE simulation of Michaelis-Menten enzyme kinetics Here we show a simple example where a model is created using the Catalyst DSL, and then simulated as an ordinary differential equation. -```@example home2 +```@example home_simple_example # Fetch required packages. -using Catalyst, DifferentialEquations, Plots +using Catalyst, OrdinaryDiffEq, Plots # Create model. model = @reaction_network begin - kB, S + E --> SE - kD, SE --> S + E - kP, SE --> P + E + kB, S + E --> SE + kD, SE --> S + E + kP, SE --> P + E end # Create an ODE that can be simulated. @@ -180,53 +177,56 @@ sol = solve(ode) plot(sol; lw = 5) ``` -![](https://user-images.githubusercontent.com/1814174/87864114-3bf9dd00-c932-11ea-83a0-58f38aee8bfb.png) - -#### [Stochastic jump simulations](@id doc_home_illustrative_example_jump) +#### Stochastic jump simulations The same model can be used as input to other types of simulations. E.g. here we instead perform a jump simulation -```@example home2 +```@example home_simple_example # Create and simulate a jump process (here using Gillespie's direct algorithm). +using JumpProcesses dprob = DiscreteProblem(model, u0, tspan, ps) jprob = JumpProblem(model, dprob, Direct()) jump_sol = solve(jprob, SSAStepper()) +jump_sol = solve(jprob, SSAStepper(); seed = 1234) # hide plot(jump_sol; lw = 2) ``` - -## [Elaborate example](@id doc_home_elaborate_example) - -In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). Typically the growth factor is inactive ($Gi$), but it is activated ($Ga$) by the presence of sunlight (modeled as the cyclic sinusoid $kA*(sin(t)+1)$). When the cell reaches a critical volume ($V$) it goes through cell division. First, we declare our model: -```@example home3 -using Catalyst, Plots, StochasticDiffEq # hide +## Elaborate example +In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ ($G \to Gᴾ$) is promoted by sunlight (modelled as the cyclic sinusoid $kₐ*(sin(t)+1)$) phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($V$) it goes through cell division. First, we declare our model: +```@example home_elaborate_example +using Catalyst cell_model = @reaction_network begin - @parameters V_thres g - @equations begin - D(V) ~ g*Ga - end - @continuous_events begin - [V ~ V_thres] => [V ~ V/2] - end - (kA*(sin(t)+1), kI), Gi <--> Ga + @parameters Vₘₐₓ g Ω + @default_noise_scaling Ω + @equations begin + D(V) ~ g*Gᴾ + end + @continuous_events begin + [V ~ Vₘₐₓ] => [V ~ V/2] + end + kₚ*(sin(t)+1)/V, G --> Gᴾ + kᵢ/V, Gᴾ --> G end ``` Next, we can use [Latexify.jl](https://korsbo.github.io/Latexify.jl/stable/) to show the ordinary differential equations associated with this model: -```@example home3 +```@example home_elaborate_example using Latexify latexify(cell_model; form = :ode) ``` -In this case we would like to perform stochastic simulations, so we transform our model to an SDE: -```@example home3 -u0 = [:V => 0.5, :Gi => 1.0, :Ga => 0.0] -tspan = (0.0, 10.0) -ps = [:V_thres => 1.0, :g => 0.5, :kA => 5.0, :kI => 2.0] +In this case, we would instead like to perform stochastic simulations, so we transform our model to an SDE: +```@example home_elaborate_example +u0 = [:V => 0.5, :G => 1.0, :Gᴾ => 0.0] +tspan = (0.0, 20.0) +ps = [:Vₘₐₓ => 1.0, :g => 0.2, :kₚ => 5.0, :kᵢ => 2.0, :Ω => 0.1] sprob = SDEProblem(cell_model, u0, tspan, ps) ``` Finally, we simulate it and plot the result. -```@example home3 -sol = solve(oprob, Tsit5()) -plot(sol) +```@example home_elaborate_example +using StochasticDiffEq +sol = solve(sprob, STrapezoid()) +sol = solve(sprob, STrapezoid(); seed = 1234) # hide +plot(sol; xguide = "Time (au)", lw = 2) ``` +![Elaborate SDE simulation](docs/src/assets/readme_elaborate_sde_plot.svg) ## [Getting Help](@id doc_home_help) Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/), From 0aa69d590e21b9f48aec706f5f5fc8297d3e6f14 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 15 Apr 2024 15:59:28 -0400 Subject: [PATCH 033/446] save progress --- README.md | 57 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0f8d03e474..fa9b723e4a 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,7 @@ documentation](https://docs.sciml.ai/Catalyst/stable/). The [in-development documentation](https://docs.sciml.ai/Catalyst/dev/) describes unreleased features in the current master branch. -Several Youtube video tutorials and overviews are also available, but note these use older -Catalyst versions with slightly different notation (for example, in building reaction networks): +Several Youtube video tutorials and overviews are also available (however, these use older versions of Catalyst, and some notation may be out-of-date): - From JuliaCon 2023: A short 15 minute overview of Catalyst as of version 13 is available in the talk [Catalyst.jl, Modeling Chemical Reaction Networks](https://www.youtube.com/watch?v=yreW94n98eM&ab_channel=TheJuliaProgrammingLanguage). - From JuliaCon 2022: A three hour tutorial workshop overviewing how to use @@ -63,6 +62,8 @@ Finally, an overview of the package and its features (as of version 13) can also ## Features +#### Features of Catalyst + - A DSL provides a simple and readable format for manually specifying chemical reactions. - Catalyst `ReactionSystem`s provide a symbolic representation of reaction networks, @@ -71,17 +72,19 @@ Finally, an overview of the package and its features (as of version 13) can also - Non-integer (e.g. `Float64`) stoichiometric coefficients are supported for generating ODE models, and symbolic expressions for stoichiometric coefficients are supported for all system types. -- The [Catalyst.jl API](http://docs.sciml.ai/Catalyst/stable/api/catalyst_api) provides functionality for extending networks, - building networks programmatically, network analysis, and for composing multiple - networks together. +- The [Catalyst.jl API](http://docs.sciml.ai/Catalyst/stable/api/catalyst_api) provides functionality + for extending networks, building networks programmatically, network analysis, and for composing + multiple networks together. +- A network analysis suite permitting the computation of linkage classes, deficiencies, reversibilities. +- Conservation laws can be detected and applied to reduce system sizes, and generate + non-singular Jacobians, during conversion to ODEs, SDEs, and steady state equations. - `ReactionSystem`s generated by the DSL can be converted to a variety of `ModelingToolkit.AbstractSystem`s, including symbolic ODE, SDE and jump process representations. - Coupled differential and algebraic constraint equations can be included in - Catalyst models, and are incorporated during conversion to ODEs or steady - state equations. -- Conservation laws can be detected and applied to reduce system sizes, and generate - non-singular Jacobians, during conversion to ODEs, SDEs, and steady state equations. + Catalyst models (and are incorporated during conversion to ODEs, SDEs, and steady + state equations). +- Modelling of simulation events. - By leveraging ModelingToolkit, users have a variety of options for generating optimized system representations to use in solvers. These include construction of dense or sparse Jacobians, multithreading or parallelization of generated @@ -97,17 +100,38 @@ Finally, an overview of the package and its features (as of version 13) can also expressions and Julia `Expr`s can be obtained for all rate laws and functions determining the deterministic and stochastic terms within resulting ODE, SDE or jump models. +- Determination of steady state stabilities. + + +#### Features of Catalyst composing with other packages +- [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to perform ODE simulation of CRN models. +- [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) Can be used to perform SDE simulation of CRN models. +- [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) Can be used to perform jump simulations of CRN models. +- Support for automatic parallelisation of all simulations, including parallelisation of ODE simulations using + [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). - [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to generate LaTeX expressions corresponding to generated mathematical models or the underlying set of reactions. - [Graphviz](https://graphviz.org/) can be used to generate and visualize reaction network graphs. (Reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/).) - -## Packages supporting Catalyst +- [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) can be used to computer *all* steady states for CRN models with + multiple steady states. +- [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl) can be used to find single steady states of CRN models (offering higher + performance than the homotopy continuation approach). +- [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to compute bifurcation diagrams of CRN models' steady state + (including finding periodic orbits). +- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to computer basins of attraction and Lyaponov spectrum for CRN models. +- [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used to perform structural identifiability analysis of + CRN models. +- [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to fit CRN model + parameters to data. +- [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform global sensitivity analysis of model behaviours. + +#### Features of packages built upon Catalyst - Catalyst [`ReactionSystem`](@ref)s can be imported from SBML files via - [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), and from BioNetGen .net - files and various stoichiometric matrix network representations using + [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), + and from BioNetGen .net files and various stoichiometric matrix network representations using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). - [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic ModelingToolkit `ODESystem`s, representing moment @@ -121,10 +145,11 @@ Finally, an overview of the package and its features (as of version 13) can also resulting stochastic chemical kinetics with delays models. - [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl) a package for constructing and analyzing bond graphs models, which can take Catalyst models as input. -- [PEtab.jl](https://github.com/sebapersson/PEtab.jl) a package that implements the PEtab format for fitting reaction network ODEs to data. Input can be provided either as SBML files or as Catalyst `ReactionSystem`s. +- [PEtab.jl](https://github.com/sebapersson/PEtab.jl) a package that implements the PEtab format for +fitting reaction network ODEs to data. Input can be provided either as SBML files or as Catalyst `ReactionSystem`s. -## Illustrative examples +## Simple example #### Gillespie simulations of Michaelis-Menten enzyme kinetics ```julia @@ -164,6 +189,8 @@ ssol = solve(sprob, LambaEM(), reltol=1e-3) plot(ssol; lw = 2, title = "Adaptive SDE: Birth-Death Process") ``` +## Elaborate example + ![](https://user-images.githubusercontent.com/1814174/87864113-3bf9dd00-c932-11ea-8275-f903eef90b91.png) ## Getting help From 96439ca6a70eda722f93974861b2b047445bab90 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 25 Apr 2024 12:31:16 -0400 Subject: [PATCH 034/446] save progress --- README.md | 315 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 178 insertions(+), 137 deletions(-) diff --git a/README.md b/README.md index fa9b723e4a..6ea2ddc046 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ [![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor's%20Guide-blueviolet)](https://github.com/SciML/ColPrac) [![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle) -Catalyst.jl is a symbolic modeling package for analysis and high performance +Catalyst.jl is a symbolic modeling package for analysis and high-performance simulation of chemical reaction networks. Catalyst defines symbolic [`ReactionSystem`](https://docs.sciml.ai/Catalyst/stable/catalyst_functionality/programmatic_CRN_construction/)s, which can be created programmatically or easily -specified using Catalyst's domain specific language (DSL). Leveraging -[ModelingToolkit](https://github.com/SciML/ModelingToolkit.jl) and +specified using Catalyst's domain-specific language (DSL). Leveraging +[ModelingToolkit.jl](https://github.com/SciML/ModelingToolkit.jl) and [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl), Catalyst enables large-scale simulations through auto-vectorization and parallelism. Symbolic `ReactionSystem`s can be used to generate ModelingToolkit-based models, allowing @@ -31,10 +31,9 @@ etc). ## Breaking changes and new features -**NOTE:** version 14 is a breaking release, prompted by the release of ModelingToolkit.jl version 9. This caused several breaking changes in how Catalyst models are represented and interfaced with. +**NOTE:** Version 14 is a breaking release, prompted by the release of ModelingToolkit.jl version 9. This caused several breaking changes in how Catalyst models are represented and interfaced with. -Breaking changes and new functionality are summarized in the -[HISTORY.md](HISTORY.md) file. +Breaking changes and new functionality are summarized in the [HISTORY.md](HISTORY.md) file. This also includes a special migration guide for version 14. ## Tutorials and documentation @@ -43,20 +42,20 @@ documentation](https://docs.sciml.ai/Catalyst/stable/). The [in-development documentation](https://docs.sciml.ai/Catalyst/dev/) describes unreleased features in the current master branch. -Several Youtube video tutorials and overviews are also available (however, these use older versions of Catalyst, and some notation may be out-of-date): -- From JuliaCon 2023: A short 15 minute overview of Catalyst as of version 13 is +Several YouTube video tutorials and overviews are also available (however, these use older versions of Catalyst, and some notation may be out-of-date): +- From JuliaCon 2023: A short 15-minute overview of Catalyst (version 13) is available in the talk [Catalyst.jl, Modeling Chemical Reaction Networks](https://www.youtube.com/watch?v=yreW94n98eM&ab_channel=TheJuliaProgrammingLanguage). -- From JuliaCon 2022: A three hour tutorial workshop overviewing how to use - Catalyst and its more advanced features as of version 12.1. [Workshop - video](https://youtu.be/tVfxT09AtWQ), [Workshop Pluto.jl - Notebooks](https://github.com/SciML/JuliaCon2022_Catalyst_Workshop). -- From SIAM CSE 2021: A short 15 minute overview of Catalyst as of version 6 is +- From JuliaCon 2022: A 3-hour tutorial workshop overviewing how to use + Catalyst (version 12.1) and its more advanced features. [Workshop + video](https://youtu.be/tVfxT09AtWQ), [Workshop Pluto.jl + Notebooks](https://github.com/SciML/JuliaCon2022_Catalyst_Workshop). +- From SIAM CSE 2021: A short 15-minute overview of Catalyst (version 6) is available in the talk [Modeling Biochemical Systems with Catalyst.jl](https://www.youtube.com/watch?v=5p1PJE5A5Jw). -- From JuliaCon 2018: A short 13 minute overview of Catalyst when it was known - as DiffEqBiological in older versions is available in the talk [Efficient - Modelling of Biochemical Reaction - Networks](https://www.youtube.com/watch?v=s1e72k5XD6s) +- From JuliaCon 2018: A short 13-minute overview of Catalyst (when it was known + as DiffEqBiological) is available in the talk [Efficient + Modelling of Biochemical Reaction + Networks](https://www.youtube.com/watch?v=s1e72k5XD6s) Finally, an overview of the package and its features (as of version 13) can also be found in its corresponding research paper, [Catalyst: Fast and flexible modeling of reaction networks](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530). @@ -64,157 +63,199 @@ Finally, an overview of the package and its features (as of version 13) can also #### Features of Catalyst -- A DSL provides a simple and readable format for manually specifying chemical - reactions. -- Catalyst `ReactionSystem`s provide a symbolic representation of reaction networks, - built on [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and - [Symbolics.jl](https://docs.sciml.ai/Symbolics/stable/). -- Non-integer (e.g. `Float64`) stoichiometric coefficients are supported for generating - ODE models, and symbolic expressions for stoichiometric coefficients are supported for - all system types. +- [The Catalyst DSL](@ref ref) provides a simple and readable format for manually specifying reaction + network models using chemical reaction notation. +- Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, + built on [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and + [Symbolics.jl](https://docs.sciml.ai/Symbolics/stable/). - The [Catalyst.jl API](http://docs.sciml.ai/Catalyst/stable/api/catalyst_api) provides functionality - for extending networks, building networks programmatically, network analysis, and for composing - multiple networks together. -- A network analysis suite permitting the computation of linkage classes, deficiencies, reversibilities. -- Conservation laws can be detected and applied to reduce system sizes, and generate - non-singular Jacobians, during conversion to ODEs, SDEs, and steady state equations. -- `ReactionSystem`s generated by the DSL can be converted to a variety of - `ModelingToolkit.AbstractSystem`s, including symbolic ODE, SDE and jump process - representations. -- Coupled differential and algebraic constraint equations can be included in - Catalyst models (and are incorporated during conversion to ODEs, SDEs, and steady - state equations). -- Modelling of simulation events. + for extending networks, building networks programmatically, and for composing + multiple networks together. +- Generated systems can be simulated using any + [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) + [ODE/SDE/jump solver](@ref ref), and can be used within `EnsembleProblem`s for carrying + out [parallelized parameter sweeps and statistical sampling](@ref ref). Plot recipes + are available for [visualizing of all solutions](@ref ref). +- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref ref) for generating + ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref ref) for + all system types. +- A [network analysis suite](@ref ref) permits the computation of linkage classes, deficiencies, and + reversibilities. +- [Conservation laws can be detected and utilised](@ref ref) to reduce system sizes, and to generate + non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). +- Catalyst reaction network models can be [coupled with differential and algebraic equations](@ref ref) + (which are then incorporated during conversion to ODEs, SDEs, and steady state equations). +- Models can be [coupled with events](@ref ref) that affect the system and its state during simulations. - By leveraging ModelingToolkit, users have a variety of options for generating - optimized system representations to use in solvers. These include construction - of dense or sparse Jacobians, multithreading or parallelization of generated - derivative functions, automatic classification of reactions into optimized - jump types for Gillespie type simulations, automatic construction of - dependency graphs for jump systems, and more. -- Generated systems can be solved using any - [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) - ODE/SDE/jump solver, and can be used within `EnsembleProblem`s for carrying - out parallelized parameter sweeps and statistical sampling. Plot recipes - are available for visualizing the solutions. + optimized system representations to use in solvers. These include construction + of [dense or sparse Jacobians](@ref ref), [multithreading or parallelization of generated + derivative functions](@ref ref), [automatic classification of reactions into optimized + jump types for Gillespie type simulations](@ref ref), [automatic construction of + dependency graphs for jump systems](@ref ref), and more. - [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl) symbolic - expressions and Julia `Expr`s can be obtained for all rate laws and functions - determining the deterministic and stochastic terms within resulting ODE, SDE - or jump models. -- Determination of steady state stabilities. + expressions and Julia `Expr`s can be obtained for all rate laws and functions determining the + deterministic and stochastic terms within resulting ODE, SDE or jump models. +- [Steady states](@ref ref) (and their [stabilities](@ref ref)) can be computed for model ODE representations. #### Features of Catalyst composing with other packages -- [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to perform ODE simulation of CRN models. -- [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) Can be used to perform SDE simulation of CRN models. -- [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) Can be used to perform jump simulations of CRN models. -- Support for automatic parallelisation of all simulations, including parallelisation of ODE simulations using - [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). -- [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to generate - LaTeX expressions corresponding to generated mathematical models or the - underlying set of reactions. -- [Graphviz](https://graphviz.org/) can be used to generate and visualize - reaction network graphs. (Reusing the Graphviz interface created in - [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/).) -- [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) can be used to computer *all* steady states for CRN models with - multiple steady states. -- [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl) can be used to find single steady states of CRN models (offering higher - performance than the homotopy continuation approach). -- [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to compute bifurcation diagrams of CRN models' steady state - (including finding periodic orbits). -- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to computer basins of attraction and Lyaponov spectrum for CRN models. -- [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used to perform structural identifiability analysis of - CRN models. -- [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to fit CRN model - parameters to data. -- [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform global sensitivity analysis of model behaviours. +- [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to [perform model ODE + simulations](@ref ref). +- [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) Can be used to [perform model + SDE simulations](@ref ref). +- [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) Can be used to [model jump + simulations](@ref ref). +- Support for [parallelisation of all simulations]((@ref ref)), including parallelisation of + [ODE simulations on GPUs](@ref ref) using + [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). +- [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX + expressions](@ref ref) corresponding to generated mathematical models or the + underlying set of reactions. +- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](@ref ref) + (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/).) +- Models steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) + (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using + [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by nonlinear systems + solving using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). +- [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute + bifurcation diagrams](@ref ref) of models' steady states (including finding periodic orbits). +- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute + model [basins of attraction](@ref ref) and [Lyapunov spectrums](@ref ref). +- [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used + to [perform structural identifiability analysis](@ref ref). +- [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), + and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](@ref ref). +- [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform + [global sensitivity analysis](@ref ref) of model behaviours. #### Features of packages built upon Catalyst -- Catalyst [`ReactionSystem`](@ref)s can be imported from SBML files via - [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), - and from BioNetGen .net files and various stoichiometric matrix network representations using - [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). -- [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows - generation of symbolic ModelingToolkit `ODESystem`s, representing moment - closure approximations to moments of the Chemical Master Equation, from - reaction networks defined in Catalyst. +- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref ref) via + [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), + and [from BioNetGen .net files](@ref ref) and various stoichiometric matrix network representations + using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). +- [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic + ModelingToolkit `ODESystem`s that represent moment closure approximations to moments of the + Chemical Master Equation, from reaction networks defined in Catalyst. - [FiniteStateProjection.jl](https://github.com/kaandocal/FiniteStateProjection.jl) - allows the construction and numerical solution of Chemical Master Equation - models from reaction networks defined in Catalyst. + allows the construction and numerical solution of Chemical Master Equation + models from reaction networks defined in Catalyst. - [DelaySSAToolkit.jl](https://github.com/palmtree2013/DelaySSAToolkit.jl) can - augment Catalyst reaction network models with delays, and can simulate the - resulting stochastic chemical kinetics with delays models. + augment Catalyst reaction network models with delays, and can simulate the + resulting stochastic chemical kinetics with delays models. - [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl) a package for - constructing and analyzing bond graphs models, which can take Catalyst models as input. + constructing and analyzing bond graphs models, which can take Catalyst models as input. - [PEtab.jl](https://github.com/sebapersson/PEtab.jl) a package that implements the PEtab format for -fitting reaction network ODEs to data. Input can be provided either as SBML files or as Catalyst `ReactionSystem`s. + fitting reaction network ODEs to data. Input can be provided either as SBML files or as Catalyst + `ReactionSystem`s. -## Simple example -#### Gillespie simulations of Michaelis-Menten enzyme kinetics +## Illustrative example + +#### Deterministic ODE simulation of Michaelis-Menten enzyme kinetics +Here we show a simple example where a model is created using the Catalyst DSL, and then simulated as +an ordinary differential equation. ```julia -using Catalyst, Plots, JumpProcesses -rs = @reaction_network begin - c1, S + E --> SE - c2, SE --> S + E - c3, SE --> P + E +# Fetch required packages. +using Catalyst, DifferentialEquations, Plots + +# Create model. +model = @reaction_network begin + kB, S + E --> SE + kD, SE --> S + E + kP, SE --> P + E end -p = (:c1 => 0.00166, :c2 => 0.0001, :c3 => 0.1) -tspan = (0., 100.) -u0 = [:S => 301, :E => 100, :SE => 0, :P => 0] - -# solve JumpProblem -dprob = DiscreteProblem(rs, u0, tspan, p) -jprob = JumpProblem(rs, dprob, Direct()) -jsol = solve(jprob, SSAStepper()) -plot(jsol; lw = 2, title = "Gillespie: Michaelis-Menten Enzyme Kinetics") + +# Create an ODE that can be simulated. +u0 = [:S => 50, :E => 10, :SE => 0, :P => 0] +tspan = (0., 200.) +ps = (:kB => 0.01, :kD => 0.1, :kP => 0.1) +ode = ODEProblem(model, u0, tspan, ps) + +# Simulate ODE and plot results. +sol = solve(ode) +plot(sol; lw = 5) ``` ![](https://user-images.githubusercontent.com/1814174/87864114-3bf9dd00-c932-11ea-83a0-58f38aee8bfb.png) -#### Adaptive time stepping SDEs for a birth-death process - +#### Stochastic jump simulations +The same model can be used as input to other types of simulations. E.g. here we instead perform a +jump simulation ```julia -using Catalyst, Plots, StochasticDiffEq -rs = @reaction_network begin - c1, X --> 2X - c2, X --> 0 - c3, 0 --> X -end -p = (:c1 => 1.0, :c2 => 2.0, :c3 => 50.) -tspan = (0.,10.) -u0 = [:X => 5.] -sprob = SDEProblem(rs, u0, tspan, p) -ssol = solve(sprob, LambaEM(), reltol=1e-3) -plot(ssol; lw = 2, title = "Adaptive SDE: Birth-Death Process") +# Create and simulate a jump process (here using Gillespie's direct algorithm). +dprob = DiscreteProblem(model, u0, tspan, ps) +jprob = JumpProblem(model, dprob, Direct()) +jump_sol = solve(jprob, SSAStepper()) +plot(jump_sol; lw = 2) ``` + ## Elaborate example -![](https://user-images.githubusercontent.com/1814174/87864113-3bf9dd00-c932-11ea-8275-f903eef90b91.png) +In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). Typically the growth factor is inactive ($Gi$), but it is activated ($Ga$) by the presence of sunlight (modelled as the cyclic sinusoid $kA*(sin(t)+1)$). When the cell reaches a critical volume ($V$) it goes through cell division. First, we declare our model: +```julia +cell_model = @reaction_network begin + @parameters V_thres g + @equations begin + D(V) ~ g*Ga + end + @continuous_events begin + [V ~ V_thres] => [V ~ V/2] + end + (kA*(sin(t)+1), kI), Gi <--> Ga +end +``` +Next, we can use [Latexify.jl](https://korsbo.github.io/Latexify.jl/stable/) to show the ordinary differential equations associated with this model: +```julia +using Latexify +latexify(cell_model; form = :ode) +``` +In this case we would like to perform stochastic simulations, so we transform our model to an SDE: +```julia +u0 = [:V => 0.5, :Gi => 1.0, :Ga => 0.0] +tspan = (0.0, 10.0) +ps = [:V_thres => 1.0, :g => 0.5, :kA => 5.0, :kI => 2.0] +sprob = SDEProblem(cell_model, u0, tspan, ps) +``` +Finally, we simulate it and plot the result. +```julia +sol = solve(oprob, Tsit5()) +plot(sol) +``` -## Getting help -Catalyst developers are active on the [Julia -Discourse](https://discourse.julialang.org/), the [Julia Slack](https://julialang.slack.com) channels \#sciml-bridged and \#sciml-sysbio, and the [Julia Zulip sciml-bridged channel](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged). -For bugs or feature requests [open an issue](https://github.com/SciML/Catalyst.jl/issues). +## Getting help or getting involved +Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/), +the [Julia Slack](https://julialang.slack.com) channels \#sciml-bridged and \#sciml-sysbio, and the +[Julia Zulip sciml-bridged channel](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged). +For bugs or feature requests, [open an issue](https://github.com/SciML/Catalyst.jl/issues). +If you are interested in participating in the development of Catalyst, or integrating your package(s) +with it, developer documentation can be found [here](@ref ref). Are you a student (or similar) who +wishes to do a Google Summer of Code (or similar) project tied to Catalyst? Information on how to get +involved, including good first issues to get familiar with working on the package, can be found [here](@ref ref). ## Supporting and citing Catalyst.jl -The software in this ecosystem was developed as part of academic research. If you would like to help support it, -please star the repository as such metrics may help us secure funding in the future. If you use Catalyst as part -of your research, teaching, or other activities, we would be grateful if you could cite our work: +The software in this ecosystem was developed as part of academic research. If you would like to help +support it, please star the repository as such metrics may help us secure funding in the future. If +you use Catalyst as part of your research, teaching, or other activities, we would be grateful if you +could cite our work: ``` @article{CatalystPLOSCompBio2023, - doi = {10.1371/journal.pcbi.1011530}, - author = {Loman, Torkel E. AND Ma, Yingbo AND Ilin, Vasily AND Gowda, Shashi AND Korsbo, Niklas AND Yewale, Nikhil AND Rackauckas, Chris AND Isaacson, Samuel A.}, - journal = {PLOS Computational Biology}, - publisher = {Public Library of Science}, - title = {Catalyst: Fast and flexible modeling of reaction networks}, - year = {2023}, - month = {10}, - volume = {19}, - url = {https://doi.org/10.1371/journal.pcbi.1011530}, - pages = {1-19}, - number = {10}, + doi = {10.1371/journal.pcbi.1011530}, + author = {Loman, Torkel E. AND Ma, Yingbo AND Ilin, Vasily AND Gowda, Shashi AND Korsbo, Niklas AND Yewale, Nikhil AND Rackauckas, Chris AND Isaacson, Samuel A.}, + journal = {PLOS Computational Biology}, + publisher = {Public Library of Science}, + title = {Catalyst: Fast and flexible modeling of reaction networks}, + year = {2023}, + month = {10}, + volume = {19}, + url = {https://doi.org/10.1371/journal.pcbi.1011530}, + pages = {1-19}, + number = {10}, } ``` + +We also maintain a user survey, asking basic questions about how users utilise the package. The survey +is available [here](ref), and only takes about 5 minutes to fill out. We are grateful to those who +fill out the survey, as this helps us further develop the package. \ No newline at end of file From 1fb33cf9686f11673613b6efb3f98ecfeed07f67 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 25 Apr 2024 15:41:28 -0400 Subject: [PATCH 035/446] save progress --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ea2ddc046..56060608ac 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ large-scale simulations through auto-vectorization and parallelism. Symbolic the easy simulation and parameter estimation of mass action ODE models, Chemical Langevin SDE models, stochastic chemical kinetics jump process models, and more. Generated models can be used with solvers throughout the broader -[SciML](https://sciml.ai) ecosystem, including higher level SciML packages (e.g. +[SciML](https://sciml.ai) ecosystem, including higher-level SciML packages (e.g. for sensitivity analysis, parameter estimation, machine learning applications, etc). @@ -193,7 +193,7 @@ plot(jump_sol; lw = 2) ## Elaborate example -In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). Typically the growth factor is inactive ($Gi$), but it is activated ($Ga$) by the presence of sunlight (modelled as the cyclic sinusoid $kA*(sin(t)+1)$). When the cell reaches a critical volume ($V$) it goes through cell division. First, we declare our model: +In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). Typically the growth factor is inactive ($Gi$), but it is activated ($Ga$) by the presence of sunlight (modeled as the cyclic sinusoid $kA*(sin(t)+1)$). When the cell reaches a critical volume ($V$) it goes through cell division. First, we declare our model: ```julia cell_model = @reaction_network begin @parameters V_thres g From 8e636de6f2db03b8c51d8dfe4dafefe684e607cf Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 18 May 2024 14:40:12 -0400 Subject: [PATCH 036/446] add simulation figures --- README.md | 80 +++++++++---------- docs/src/assets/readme_elaborate_sde_plot.svg | 50 ++++++++++++ docs/src/assets/readme_jump_plot.svg | 54 +++++++++++++ docs/src/assets/readme_ode_plot.svg | 54 +++++++++++++ 4 files changed, 196 insertions(+), 42 deletions(-) create mode 100644 docs/src/assets/readme_elaborate_sde_plot.svg create mode 100644 docs/src/assets/readme_jump_plot.svg create mode 100644 docs/src/assets/readme_ode_plot.svg diff --git a/README.md b/README.md index 56060608ac..3dd84bef27 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ [![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/) [![API Stable](https://img.shields.io/badge/API-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/api/catalyst_api/) - +[![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) [![Build Status](https://github.com/SciML/Catalyst.jl/workflows/CI/badge.svg)](https://github.com/SciML/Catalyst.jl/actions?query=workflow%3ACI) [![codecov.io](https://codecov.io/gh/SciML/Catalyst.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/SciML/Catalyst.jl) @@ -22,7 +21,7 @@ specified using Catalyst's domain-specific language (DSL). Leveraging [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl), Catalyst enables large-scale simulations through auto-vectorization and parallelism. Symbolic `ReactionSystem`s can be used to generate ModelingToolkit-based models, allowing -the easy simulation and parameter estimation of mass action ODE models, Chemical +the easy simulation and parameter estimation of mass action ODE models, chemical Langevin SDE models, stochastic chemical kinetics jump process models, and more. Generated models can be used with solvers throughout the broader [SciML](https://sciml.ai) ecosystem, including higher-level SciML packages (e.g. @@ -37,27 +36,12 @@ Breaking changes and new functionality are summarized in the [HISTORY.md](HISTOR ## Tutorials and documentation -The latest tutorials and information on using the package are available in the [stable +The latest tutorials and information on using Catalyst are available in the [stable documentation](https://docs.sciml.ai/Catalyst/stable/). The [in-development documentation](https://docs.sciml.ai/Catalyst/dev/) describes unreleased features in the current master branch. -Several YouTube video tutorials and overviews are also available (however, these use older versions of Catalyst, and some notation may be out-of-date): -- From JuliaCon 2023: A short 15-minute overview of Catalyst (version 13) is -available in the talk [Catalyst.jl, Modeling Chemical Reaction Networks](https://www.youtube.com/watch?v=yreW94n98eM&ab_channel=TheJuliaProgrammingLanguage). -- From JuliaCon 2022: A 3-hour tutorial workshop overviewing how to use - Catalyst (version 12.1) and its more advanced features. [Workshop - video](https://youtu.be/tVfxT09AtWQ), [Workshop Pluto.jl - Notebooks](https://github.com/SciML/JuliaCon2022_Catalyst_Workshop). -- From SIAM CSE 2021: A short 15-minute overview of Catalyst (version 6) is -available in the talk [Modeling Biochemical Systems with -Catalyst.jl](https://www.youtube.com/watch?v=5p1PJE5A5Jw). -- From JuliaCon 2018: A short 13-minute overview of Catalyst (when it was known - as DiffEqBiological) is available in the talk [Efficient - Modelling of Biochemical Reaction - Networks](https://www.youtube.com/watch?v=s1e72k5XD6s) - -Finally, an overview of the package and its features (as of version 13) can also be found in its corresponding research paper, [Catalyst: Fast and flexible modeling of reaction networks](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530). +An overview of the package and its features (as of version 13) can also be found in its corresponding research paper, [Catalyst: Fast and flexible modeling of reaction networks](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530). ## Features @@ -71,7 +55,7 @@ Finally, an overview of the package and its features (as of version 13) can also - The [Catalyst.jl API](http://docs.sciml.ai/Catalyst/stable/api/catalyst_api) provides functionality for extending networks, building networks programmatically, and for composing multiple networks together. -- Generated systems can be simulated using any +- Generated models can be simulated using any [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) [ODE/SDE/jump solver](@ref ref), and can be used within `EnsembleProblem`s for carrying out [parallelized parameter sweeps and statistical sampling](@ref ref). Plot recipes @@ -157,13 +141,13 @@ an ordinary differential equation. ```julia # Fetch required packages. -using Catalyst, DifferentialEquations, Plots +using Catalyst, OrdinaryDiffEq, Plots # Create model. model = @reaction_network begin - kB, S + E --> SE - kD, SE --> S + E - kP, SE --> P + E + kB, S + E --> SE + kD, SE --> S + E + kP, SE --> P + E end # Create an ODE that can be simulated. @@ -176,34 +160,38 @@ ode = ODEProblem(model, u0, tspan, ps) sol = solve(ode) plot(sol; lw = 5) ``` - -![](https://user-images.githubusercontent.com/1814174/87864114-3bf9dd00-c932-11ea-83a0-58f38aee8bfb.png) +![ODE simulation](docs/src/assets/readme_ode_plot.svg) #### Stochastic jump simulations The same model can be used as input to other types of simulations. E.g. here we instead perform a jump simulation ```julia # Create and simulate a jump process (here using Gillespie's direct algorithm). +using JumpProcesses dprob = DiscreteProblem(model, u0, tspan, ps) jprob = JumpProblem(model, dprob, Direct()) jump_sol = solve(jprob, SSAStepper()) plot(jump_sol; lw = 2) ``` +![Jump simulation](docs/src/assets/readme_jump_plot.svg) ## Elaborate example -In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). Typically the growth factor is inactive ($Gi$), but it is activated ($Ga$) by the presence of sunlight (modeled as the cyclic sinusoid $kA*(sin(t)+1)$). When the cell reaches a critical volume ($V$) it goes through cell division. First, we declare our model: +In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ ($G \to Gᴾ$) is promoted by sunlight (modelled as the cyclic sinusoid $kₐ*(sin(t)+1)$) phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($V$) it goes through cell division. First, we declare our model: ```julia +using Catalyst cell_model = @reaction_network begin - @parameters V_thres g - @equations begin - D(V) ~ g*Ga - end - @continuous_events begin - [V ~ V_thres] => [V ~ V/2] - end - (kA*(sin(t)+1), kI), Gi <--> Ga + @parameters Vₘₐₓ g Ω + @default_noise_scaling Ω + @equations begin + D(V) ~ g*Gᴾ + end + @continuous_events begin + [V ~ Vₘₐₓ] => [V ~ V/2] + end + kₚ*(sin(t)+1)/V, G --> Gᴾ + kᵢ/V, Gᴾ --> G end ``` Next, we can use [Latexify.jl](https://korsbo.github.io/Latexify.jl/stable/) to show the ordinary differential equations associated with this model: @@ -211,18 +199,26 @@ Next, we can use [Latexify.jl](https://korsbo.github.io/Latexify.jl/stable/) to using Latexify latexify(cell_model; form = :ode) ``` -In this case we would like to perform stochastic simulations, so we transform our model to an SDE: +In this case, we would instead like to perform stochastic simulations, so we transform our model to an SDE: ```julia -u0 = [:V => 0.5, :Gi => 1.0, :Ga => 0.0] -tspan = (0.0, 10.0) -ps = [:V_thres => 1.0, :g => 0.5, :kA => 5.0, :kI => 2.0] +u0 = [:V => 0.5, :G => 1.0, :Gᴾ => 0.0] +tspan = (0.0, 20.0) +ps = [:Vₘₐₓ => 1.0, :g => 0.2, :kₚ => 5.0, :kᵢ => 2.0, :Ω => 0.1] sprob = SDEProblem(cell_model, u0, tspan, ps) ``` Finally, we simulate it and plot the result. ```julia -sol = solve(oprob, Tsit5()) -plot(sol) +using StochasticDiffEq +sol = solve(sprob, STrapezoid()) +plot(sol; xguide = "Time (au)", lw = 2) ``` +![Elaborate SDE simulation](docs/src/assets/readme_elaborate_sde_plot.svg) + +Some features we used here: +- The SDE was [simulated using StochasticDiffEq.jl], where we [scaled the SDE noise terms](@ref ref). +- The cell volume was [modelled as a differential equation, which was coupled to the reaction network model](@ref ref). +- The cell divisions were created by [incorporating events into the model](@ref ref). +- The model equations were [displayed using Latexify.jl](@ref ref), and the simulation [plotted using Plots.jl](@ref ref). ## Getting help or getting involved Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/), diff --git a/docs/src/assets/readme_elaborate_sde_plot.svg b/docs/src/assets/readme_elaborate_sde_plot.svg new file mode 100644 index 0000000000..eeb1d0f2fd --- /dev/null +++ b/docs/src/assets/readme_elaborate_sde_plot.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/readme_jump_plot.svg b/docs/src/assets/readme_jump_plot.svg new file mode 100644 index 0000000000..5c45563c97 --- /dev/null +++ b/docs/src/assets/readme_jump_plot.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/readme_ode_plot.svg b/docs/src/assets/readme_ode_plot.svg new file mode 100644 index 0000000000..df9c2eb095 --- /dev/null +++ b/docs/src/assets/readme_ode_plot.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 966355ed4a02e18b11f70223786bc175cedd9df4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 18 May 2024 17:42:25 -0400 Subject: [PATCH 037/446] up --- README.md | 5 +- docs/Project.toml | 1 - docs/src/assets/readme_elaborate_sde_plot.svg | 72 +++++++++---------- 3 files changed, 37 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 3dd84bef27..0567a3a052 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Catalyst.jl -[![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/) [![API Stable](https://img.shields.io/badge/API-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/api/catalyst_api/) [![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) +[![Citation](https://img.shields.io/badge/Publication-389826)](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530) [![Build Status](https://github.com/SciML/Catalyst.jl/workflows/CI/badge.svg)](https://github.com/SciML/Catalyst.jl/actions?query=workflow%3ACI) [![codecov.io](https://codecov.io/gh/SciML/Catalyst.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/SciML/Catalyst.jl) @@ -46,7 +46,6 @@ An overview of the package and its features (as of version 13) can also be found ## Features #### Features of Catalyst - - [The Catalyst DSL](@ref ref) provides a simple and readable format for manually specifying reaction network models using chemical reaction notation. - Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, @@ -81,7 +80,6 @@ An overview of the package and its features (as of version 13) can also be found deterministic and stochastic terms within resulting ODE, SDE or jump models. - [Steady states](@ref ref) (and their [stabilities](@ref ref)) can be computed for model ODE representations. - #### Features of Catalyst composing with other packages - [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to [perform model ODE simulations](@ref ref). @@ -177,7 +175,6 @@ plot(jump_sol; lw = 2) ## Elaborate example - In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ ($G \to Gᴾ$) is promoted by sunlight (modelled as the cyclic sinusoid $kₐ*(sin(t)+1)$) phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($V$) it goes through cell division. First, we declare our model: ```julia using Catalyst diff --git a/docs/Project.toml b/docs/Project.toml index 28d446f257..bdd31db760 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -26,7 +26,6 @@ OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e" OptimizationOptimisers = "42dfb2eb-d2b4-4451-abcd-913932933ac1" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -QuasiMonteCarlo = "8a4e6c94-4038-4cdc-81c3-7e6ffdb2a71b" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" diff --git a/docs/src/assets/readme_elaborate_sde_plot.svg b/docs/src/assets/readme_elaborate_sde_plot.svg index eeb1d0f2fd..a906bbbfc9 100644 --- a/docs/src/assets/readme_elaborate_sde_plot.svg +++ b/docs/src/assets/readme_elaborate_sde_plot.svg @@ -1,50 +1,50 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 16579e7fc73090bddedf8671bc25d39ab16d18ed Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 18 May 2024 17:45:44 -0400 Subject: [PATCH 038/446] add missing compat entry --- docs/Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Project.toml b/docs/Project.toml index bdd31db760..28d446f257 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -26,6 +26,7 @@ OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e" OptimizationOptimisers = "42dfb2eb-d2b4-4451-abcd-913932933ac1" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +QuasiMonteCarlo = "8a4e6c94-4038-4cdc-81c3-7e6ffdb2a71b" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" From 8096b7e5fbbd724ee6ce3b7388c513032d966227 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Wed, 22 May 2024 05:08:23 +0200 Subject: [PATCH 039/446] Update README.md Co-authored-by: Sam Isaacson --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0567a3a052..b130a28059 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ documentation](https://docs.sciml.ai/Catalyst/stable/). The [in-development documentation](https://docs.sciml.ai/Catalyst/dev/) describes unreleased features in the current master branch. -An overview of the package and its features (as of version 13) can also be found in its corresponding research paper, [Catalyst: Fast and flexible modeling of reaction networks](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530). +An overview of the package, its features, and comparative benchmarking (as of version 13) can also be found in its corresponding research paper, [Catalyst: Fast and flexible modeling of reaction networks](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530). ## Features From db34c5c45164e7b34d016df9d3e9ea0121e2b8b0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 30 May 2024 17:27:42 -0400 Subject: [PATCH 040/446] add dev doc link back in --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b130a28059..8ee61c6382 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/) [![API Stable](https://img.shields.io/badge/API-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/api/catalyst_api/) +[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://docs.sciml.ai/Catalyst/dev/) +[![API Dev](https://img.shields.io/badge/API-dev-blue.svg)](https://docs.sciml.ai/Catalyst/dev/api/catalyst_api/) [![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) -[![Citation](https://img.shields.io/badge/Publication-389826)](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530) [![Build Status](https://github.com/SciML/Catalyst.jl/workflows/CI/badge.svg)](https://github.com/SciML/Catalyst.jl/actions?query=workflow%3ACI) [![codecov.io](https://codecov.io/gh/SciML/Catalyst.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/SciML/Catalyst.jl) @@ -11,6 +12,7 @@ [![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor's%20Guide-blueviolet)](https://github.com/SciML/ColPrac) [![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle) +[![Citation](https://img.shields.io/badge/Publication-389826)](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530) Catalyst.jl is a symbolic modeling package for analysis and high-performance simulation of chemical reaction networks. Catalyst defines symbolic From 039c1a02703b3c9101c04171e457e0e69a68488a Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 30 May 2024 20:04:47 -0400 Subject: [PATCH 041/446] change some instances of UK english to US --- README.md | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8ee61c6382..f539a1e80a 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,11 @@ etc). ## Breaking changes and new features -**NOTE:** Version 14 is a breaking release, prompted by the release of ModelingToolkit.jl version 9. This caused several breaking changes in how Catalyst models are represented and interfaced with. +**NOTE:** Version 14 is a breaking release, prompted by the release of ModelingToolkit.jl version 9. +This caused several breaking changes in how Catalyst models are represented and interfaced with. -Breaking changes and new functionality are summarized in the [HISTORY.md](HISTORY.md) file. This also includes a special migration guide for version 14. +Breaking changes and new functionality are summarized in the [HISTORY.md](HISTORY.md) file. +This also includes a special migration guide for version 14. ## Tutorials and documentation @@ -43,7 +45,8 @@ documentation](https://docs.sciml.ai/Catalyst/stable/). The [in-development documentation](https://docs.sciml.ai/Catalyst/dev/) describes unreleased features in the current master branch. -An overview of the package, its features, and comparative benchmarking (as of version 13) can also be found in its corresponding research paper, [Catalyst: Fast and flexible modeling of reaction networks](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530). +An overview of the package, its features, and comparative benchmarking (as of version 13) can also +be found in its corresponding research paper, [Catalyst: Fast and flexible modeling of reaction networks](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530). ## Features @@ -60,13 +63,13 @@ An overview of the package, its features, and comparative benchmarking (as of ve [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) [ODE/SDE/jump solver](@ref ref), and can be used within `EnsembleProblem`s for carrying out [parallelized parameter sweeps and statistical sampling](@ref ref). Plot recipes - are available for [visualizing of all solutions](@ref ref). + are available for [visualization of all solutions](@ref ref). - Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref ref) for generating ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref ref) for all system types. - A [network analysis suite](@ref ref) permits the computation of linkage classes, deficiencies, and reversibilities. -- [Conservation laws can be detected and utilised](@ref ref) to reduce system sizes, and to generate +- [Conservation laws can be detected and utilized](@ref ref) to reduce system sizes, and to generate non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). - Catalyst reaction network models can be [coupled with differential and algebraic equations](@ref ref) (which are then incorporated during conversion to ODEs, SDEs, and steady state equations). @@ -89,7 +92,7 @@ An overview of the package, its features, and comparative benchmarking (as of ve SDE simulations](@ref ref). - [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) Can be used to [model jump simulations](@ref ref). -- Support for [parallelisation of all simulations]((@ref ref)), including parallelisation of +- Support for [parallelization of all simulations](@ref ref), including parallelization of [ODE simulations on GPUs](@ref ref) using [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). - [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX @@ -97,7 +100,7 @@ An overview of the package, its features, and comparative benchmarking (as of ve underlying set of reactions. - [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](@ref ref) (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/).) -- Models steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) +- Model steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by nonlinear systems solving using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). @@ -110,7 +113,7 @@ An overview of the package, its features, and comparative benchmarking (as of ve - [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](@ref ref). - [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform - [global sensitivity analysis](@ref ref) of model behaviours. + [global sensitivity analysis](@ref ref) of model behaviors. #### Features of packages built upon Catalyst - Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref ref) via @@ -177,7 +180,13 @@ plot(jump_sol; lw = 2) ## Elaborate example -In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ ($G \to Gᴾ$) is promoted by sunlight (modelled as the cyclic sinusoid $kₐ*(sin(t)+1)$) phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($V$) it goes through cell division. First, we declare our model: +In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we +instead show how various Catalyst features can compose to create a much more advanced model. Our +model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth +factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ +($G \to Gᴾ$) is promoted by sunlight (modelld as the cyclic sinusoid $kₐ*(sin(t)+1)$), which +phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($V$) +it undergoes through cell division. First, we declare our model: ```julia using Catalyst cell_model = @reaction_network begin @@ -193,7 +202,8 @@ cell_model = @reaction_network begin kᵢ/V, Gᴾ --> G end ``` -Next, we can use [Latexify.jl](https://korsbo.github.io/Latexify.jl/stable/) to show the ordinary differential equations associated with this model: +Next, we can use [Latexify.jl](https://korsbo.github.io/Latexify.jl/stable/) to show the ordinary +differential equations associated with this model: ```julia using Latexify latexify(cell_model; form = :ode) @@ -214,8 +224,8 @@ plot(sol; xguide = "Time (au)", lw = 2) ![Elaborate SDE simulation](docs/src/assets/readme_elaborate_sde_plot.svg) Some features we used here: -- The SDE was [simulated using StochasticDiffEq.jl], where we [scaled the SDE noise terms](@ref ref). -- The cell volume was [modelled as a differential equation, which was coupled to the reaction network model](@ref ref). +- The SDE was [simulated using StochasticDiffEq.jl]. We also [scaled the SDE noise terms](@ref ref). +- The cell volume was [modeled as a differential equation, which was coupled to the reaction network model](@ref ref). - The cell divisions were created by [incorporating events into the model](@ref ref). - The model equations were [displayed using Latexify.jl](@ref ref), and the simulation [plotted using Plots.jl](@ref ref). From 8e4a3ef6b6b170cee61cfef83e0022d747b5ab81 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 30 May 2024 20:19:04 -0400 Subject: [PATCH 042/446] writing improvement --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f539a1e80a..259f110e8c 100644 --- a/README.md +++ b/README.md @@ -129,9 +129,9 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model - [DelaySSAToolkit.jl](https://github.com/palmtree2013/DelaySSAToolkit.jl) can augment Catalyst reaction network models with delays, and can simulate the resulting stochastic chemical kinetics with delays models. -- [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl) a package for +- [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl), a package for constructing and analyzing bond graphs models, which can take Catalyst models as input. -- [PEtab.jl](https://github.com/sebapersson/PEtab.jl) a package that implements the PEtab format for +- [PEtab.jl](https://github.com/sebapersson/PEtab.jl), a package that implements the PEtab format for fitting reaction network ODEs to data. Input can be provided either as SBML files or as Catalyst `ReactionSystem`s. From bd705be739402b7aab34511d601be5388d823e05 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 30 May 2024 20:25:00 -0400 Subject: [PATCH 043/446] up --- docs/src/home.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/docs/src/home.md b/docs/src/home.md index 1f52d5b20c..199cc25dac 100644 --- a/docs/src/home.md +++ b/docs/src/home.md @@ -31,13 +31,13 @@ etc). [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) [ODE/SDE/jump solver](@ref ref), and can be used within `EnsembleProblem`s for carrying out [parallelized parameter sweeps and statistical sampling](@ref ref). Plot recipes - are available for [visualizing of all solutions](@ref ref). + are available for [visualization of all solutions](@ref ref). - Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref ref) for generating ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref ref) for all system types. - A [network analysis suite](@ref ref) permits the computation of linkage classes, deficiencies, and reversibilities. -- [Conservation laws can be detected and utilised](@ref ref) to reduce system sizes, and to generate +- [Conservation laws can be detected and utilized](@ref ref) to reduce system sizes, and to generate non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). - Catalyst reaction network models can be [coupled with differential and algebraic equations](@ref ref) (which are then incorporated during conversion to ODEs, SDEs, and steady state equations). @@ -60,7 +60,7 @@ etc). SDE simulations](@ref ref). - [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) Can be used to [model jump simulations](@ref ref). -- Support for [parallelisation of all simulations]((@ref ref)), including parallelisation of +- Support for [parallelization of all simulations](@ref ref), including parallelization of [ODE simulations on GPUs](@ref ref) using [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). - [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX @@ -68,7 +68,7 @@ etc). underlying set of reactions. - [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](@ref ref) (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/).) -- Models steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) +- Model steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by nonlinear systems solving using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). @@ -81,7 +81,7 @@ etc). - [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](@ref ref). - [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform - [global sensitivity analysis](@ref ref) of model behaviours. + [global sensitivity analysis](@ref ref) of model behaviors. #### Features of packages built upon Catalyst - Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref ref) via @@ -97,14 +97,14 @@ etc). - [DelaySSAToolkit.jl](https://github.com/palmtree2013/DelaySSAToolkit.jl) can augment Catalyst reaction network models with delays, and can simulate the resulting stochastic chemical kinetics with delays models. -- [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl) a package for +- [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl), a package for constructing and analyzing bond graphs models, which can take Catalyst models as input. -- [PEtab.jl](https://github.com/sebapersson/PEtab.jl) a package that implements the PEtab format for +- [PEtab.jl](https://github.com/sebapersson/PEtab.jl), a package that implements the PEtab format for fitting reaction network ODEs to data. Input can be provided either as SBML files or as Catalyst `ReactionSystem`s. ## [How to read this documentation](@id doc_home_documentation) -The Catalyst documentation is separated into sections describing Catalyst's various features. Where appropriate, some sections will also give advice on best practices for various modelling workflows, and provide links with further reading. Each section also contains a set of relevant example workflows. Finally, the [API](@ref api) section contains a list of all functions exported by Catalyst (as well as descriptions of them and their inputs and outputs). +The Catalyst documentation is separated into sections describing Catalyst's various features. Where appropriate, some sections will also give advice on best practices for various modeling workflows, and provide links with further reading. Each section also contains a set of relevant example workflows. Finally, the [API](@ref api) section contains a list of all functions exported by Catalyst (as well as descriptions of them and their inputs and outputs). New users are recommended to start with either the [Introduction to Catalyst and Julia for New Julia users](@ref catalyst_for_new_julia_users) or [Introduction to Catalyst](@ref introduction_to_catalyst) sections (depending on whether they are familiar with Julia programming or not). This should be enough to carry out many basic Catalyst workflows. Next, [The Catalyst DSL](@ref ref) section gives a more throughout introduction to model creation, while the [Introduction to model simulation](@ref ref) section more through describes how simulations are carried out. Once you have gotten started using Catalyst, you can read whichever sections are relevant to your work (they should all be clearly labelled). @@ -125,7 +125,7 @@ However, in some situations (e.g. when output is extensive, or irrelevant to wha 1 + 2 nothing # hide ``` -and +and here: ```@example home1 @reaction_network begin (p,d), 0 <--> X @@ -147,7 +147,7 @@ Pkg.add("Plots") ``` is also needed. -A more throughout guide for setting up Catalyst and installing Julia packages can be found [here](@ref ref). +A more throughout guide for setting up Catalyst and installing Julia packages can be found [here](@ref catalyst_for_new_julia_users_packages). ## Illustrative example @@ -191,7 +191,13 @@ plot(jump_sol; lw = 2) ``` ## Elaborate example -In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ ($G \to Gᴾ$) is promoted by sunlight (modelled as the cyclic sinusoid $kₐ*(sin(t)+1)$) phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($V$) it goes through cell division. First, we declare our model: +In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we +instead show how various Catalyst features can compose to create a much more advanced model. Our +model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth +factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ +($G \to Gᴾ$) is promoted by sunlight (modeled as the cyclic sinusoid $kₐ*(sin(t)+1)$), which +phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($V$) +it undergoes through cell division. First, we declare our model: ```@example home_elaborate_example using Catalyst cell_model = @reaction_network begin @@ -226,7 +232,6 @@ sol = solve(sprob, STrapezoid()) sol = solve(sprob, STrapezoid(); seed = 1234) # hide plot(sol; xguide = "Time (au)", lw = 2) ``` -![Elaborate SDE simulation](docs/src/assets/readme_elaborate_sde_plot.svg) ## [Getting Help](@id doc_home_help) Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/), From 1a2c1ab4b7dd679c70797a99245d5339203b109a Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 30 May 2024 20:25:22 -0400 Subject: [PATCH 044/446] up --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 259f110e8c..1e3ab7922a 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ In the above example, we used basic Catalyst-based workflows to simulate a simpl instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ -($G \to Gᴾ$) is promoted by sunlight (modelld as the cyclic sinusoid $kₐ*(sin(t)+1)$), which +($G \to Gᴾ$) is promoted by sunlight (modeled as the cyclic sinusoid $kₐ*(sin(t)+1)$), which phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($V$) it undergoes through cell division. First, we declare our model: ```julia From 839c480592b0465f6e46cf94ed433cde20eb229d Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 30 May 2024 20:29:37 -0400 Subject: [PATCH 045/446] add links --- docs/src/home.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/src/home.md b/docs/src/home.md index 199cc25dac..5200de003f 100644 --- a/docs/src/home.md +++ b/docs/src/home.md @@ -16,9 +16,9 @@ Generated models can be used with solvers throughout the broader for sensitivity analysis, parameter estimation, machine learning applications, etc). -## Features +## [Features](@id doc_home_features) -#### Features of Catalyst +#### [Features of Catalyst](@id doc_home_features_catalyst) - [The Catalyst DSL](@ref ref) provides a simple and readable format for manually specifying reaction network models using chemical reaction notation. - Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, @@ -53,7 +53,7 @@ etc). deterministic and stochastic terms within resulting ODE, SDE or jump models. - [Steady states](@ref ref) (and their [stabilities](@ref ref)) can be computed for model ODE representations. -#### Features of Catalyst composing with other packages +#### [Features of Catalyst composing with other packages](@id doc_home_features_composed) - [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to [perform model ODE simulations](@ref ref). - [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) Can be used to [perform model @@ -83,7 +83,7 @@ etc). - [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform [global sensitivity analysis](@ref ref) of model behaviors. -#### Features of packages built upon Catalyst +#### [Features of packages built upon Catalyst](@id doc_home_features_other_packages) - Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref ref) via [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), and [from BioNetGen .net files](@ref ref) and various stoichiometric matrix network representations @@ -149,9 +149,9 @@ is also needed. A more throughout guide for setting up Catalyst and installing Julia packages can be found [here](@ref catalyst_for_new_julia_users_packages). -## Illustrative example +## [Illustrative example](@id doc_home_example) -#### Deterministic ODE simulation of Michaelis-Menten enzyme kinetics +#### [Deterministic ODE simulation of Michaelis-Menten enzyme kinetics](@id doc_home_example_ode) Here we show a simple example where a model is created using the Catalyst DSL, and then simulated as an ordinary differential equation. @@ -177,7 +177,7 @@ sol = solve(ode) plot(sol; lw = 5) ``` -#### Stochastic jump simulations +#### [Stochastic jump simulations](@id doc_home_example_jump) The same model can be used as input to other types of simulations. E.g. here we instead perform a jump simulation ```@example home_simple_example @@ -190,7 +190,7 @@ jump_sol = solve(jprob, SSAStepper(); seed = 1234) # hide plot(jump_sol; lw = 2) ``` -## Elaborate example +## [Elaborate example](@id doc_home_elaborate_example) In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth From fe3e21aa88f923a8fe956629d395adab10f08cf1 Mon Sep 17 00:00:00 2001 From: vydu Date: Sun, 12 May 2024 00:07:15 -0400 Subject: [PATCH 046/446] Implemented complex balance checking --- src/Project.toml | 2 + src/networkapi.jl | 1801 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1803 insertions(+) create mode 100644 src/Project.toml create mode 100644 src/networkapi.jl diff --git a/src/Project.toml b/src/Project.toml new file mode 100644 index 0000000000..e7b949696b --- /dev/null +++ b/src/Project.toml @@ -0,0 +1,2 @@ +[deps] +Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83" diff --git a/src/networkapi.jl b/src/networkapi.jl new file mode 100644 index 0000000000..f0cc164856 --- /dev/null +++ b/src/networkapi.jl @@ -0,0 +1,1801 @@ + +######### Accessors: ######### + +function filter_nonrxsys(network) + systems = get_systems(network) + rxsystems = ReactionSystem[] + for sys in systems + (sys isa ReactionSystem) && push!(rxsystems, sys) + end + rxsystems +end + +function species(network::ReactionSystem, sts) + [MT.renamespace(network, st) for st in sts] +end + +""" + species(network) + +Given a [`ReactionSystem`](@ref), return a vector of all species defined in the system and +any subsystems that are of type `ReactionSystem`. To get the species and non-species +variables in the system and all subsystems, including non-`ReactionSystem` subsystems, uses +`states(network)`. + +Notes: +- If `ModelingToolkit.get_systems(network)` is non-empty will allocate. +""" +function species(network) + sts = get_species(network) + systems = filter_nonrxsys(network) + isempty(systems) && return sts + unique([sts; reduce(vcat, map(sys -> species(sys, species(sys)), systems))]) +end + +""" + nonspecies(network) + +Return the non-species variables within the network, i.e. those states for which `isspecies +== false`. + +Notes: +- Allocates a new array to store the non-species variables. +""" +function nonspecies(network) + states(network)[(numspecies(network) + 1):end] +end + +""" + reactionparams(network) + +Given a [`ReactionSystem`](@ref), return a vector of all parameters defined +within the system and any subsystems that are of type `ReactionSystem`. To get +the parameters in the system and all subsystems, including non-`ReactionSystem` +subsystems, use `parameters(network)`. + +Notes: +- Allocates and has to calculate these dynamically by comparison for each reaction. +""" +function reactionparams(network) + ps = get_ps(network) + systems = filter_nonrxsys(network) + isempty(systems) && return ps + unique([ps; reduce(vcat, map(sys -> species(sys, reactionparams(sys)), systems))]) +end + +""" + numparams(network) + +Return the total number of parameters within the given system and all subsystems. +""" +function numparams(network) + nps = length(get_ps(network)) + for sys in get_systems(network) + nps += numparams(sys) + end + nps +end + +function namespace_reactions(network::ReactionSystem) + rxs = reactions(network) + isempty(rxs) && return Reaction[] + map(rx -> namespace_equation(rx, network), rxs) +end + +""" + reactions(network) + +Given a [`ReactionSystem`](@ref), return a vector of all `Reactions` in the system. + +Notes: +- If `ModelingToolkit.get_systems(network)` is not empty, will allocate. +""" +function reactions(network) + rxs = get_rxs(network) + systems = filter_nonrxsys(network) + isempty(systems) && (return rxs) + [rxs; reduce(vcat, namespace_reactions.(systems); init = Reaction[])] +end + +""" + speciesmap(network) + +Given a [`ReactionSystem`](@ref), return a Dictionary mapping species that +participate in `Reaction`s to their index within [`species(network)`](@ref). +""" +function speciesmap(network) + nps = get_networkproperties(network) + if isempty(nps.speciesmap) + nps.speciesmap = Dict(S => i for (i, S) in enumerate(species(network))) + end + nps.speciesmap +end + +""" + paramsmap(network) + +Given a [`ReactionSystem`](@ref), return a Dictionary mapping from all +parameters that appear within the system to their index within +`parameters(network)`. +""" +function paramsmap(network) + Dict(p => i for (i, p) in enumerate(parameters(network))) +end + +""" + reactionparamsmap(network) + +Given a [`ReactionSystem`](@ref), return a Dictionary mapping from parameters that +appear within `Reaction`s to their index within [`reactionparams(network)`](@ref). +""" +function reactionparamsmap(network) + Dict(p => i for (i, p) in enumerate(reactionparams(network))) +end + +""" + numspecies(network) + +Return the total number of species within the given [`ReactionSystem`](@ref) and +subsystems that are `ReactionSystem`s. +""" +function numspecies(network) + numspcs = length(get_species(network)) + for sys in get_systems(network) + (sys isa ReactionSystem) && (numspcs += numspecies(sys)) + end + numspcs +end + +""" + numreactions(network) + +Return the total number of reactions within the given [`ReactionSystem`](@ref) +and subsystems that are `ReactionSystem`s. +""" +function numreactions(network) + nr = length(get_rxs(network)) + for sys in get_systems(network) + (sys isa ReactionSystem) && (nr += numreactions(sys)) + end + nr +end + +""" + numreactionparams(network) + +Return the total number of parameters within the given [`ReactionSystem`](@ref) +and subsystems that are `ReactionSystem`s. + +Notes +- If there are no subsystems this will be fast. +- As this calls [`reactionparams`](@ref), it can be slow and will allocate if + there are any subsystems. +""" +function numreactionparams(network) + length(reactionparams(network)) +end + +""" + dependents(rx, network) + +Given a [`Reaction`](@ref) and a [`ReactionSystem`](@ref), return a vector of the +*non-constant* species and variables the reaction rate law depends on. e.g., for + +`k*W, 2X + 3Y --> 5Z + W` + +the returned vector would be `[W(t),X(t),Y(t)]`. + +Notes: +- Allocates +- Does not check for dependents within any subsystems. +- Constant species are not considered dependents since they are internally treated as + parameters. +- If the rate expression depends on a non-species state variable that will be included in + the dependents, i.e. in + ```julia + @parameters k + @variables t V(t) + @species A(t) B(t) C(t) + rx = Reaction(k*V, [A, B], [C]) + @named rs = ReactionSystem([rx], t) + issetequal(dependents(rx, rs), [A,B,V]) == true + ``` +""" +function dependents(rx, network) + if rx.rate isa Number + return rx.substrates + else + rvars = get_variables(rx.rate, states(network)) + return union!(rvars, rx.substrates) + end +end + +""" + dependents(rx, network) + +See documentation for [`dependents`](@ref). +""" +function dependants(rx, network) + dependents(rx, network) +end + +""" + reactionrates(network) + +Given a [`ReactionSystem`](@ref), returns a vector of the symbolic reaction +rates for each reaction. +""" +function reactionrates(rn) + [r.rate for r in reactions(rn)] +end + +""" + substoichmat(rn; sparse=false) + +Returns the substrate stoichiometry matrix, ``S``, with ``S_{i j}`` the stoichiometric +coefficient of the ith substrate within the jth reaction. + +Note: +- Set sparse=true for a sparse matrix representation +- Note that constant species are not considered substrates, but just components that modify + the associated rate law. +""" +function substoichmat(::Type{SparseMatrixCSC{T, Int}}, + rn::ReactionSystem) where {T <: Number} + Is = Int[] + Js = Int[] + Vs = T[] + smap = speciesmap(rn) + for (k, rx) in enumerate(reactions(rn)) + stoich = rx.substoich + for (i, sub) in enumerate(rx.substrates) + isconstant(sub) && continue + push!(Js, k) + push!(Is, smap[sub]) + push!(Vs, stoich[i]) + end + end + sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) +end +function substoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} + smap = speciesmap(rn) + smat = zeros(T, numspecies(rn), numreactions(rn)) + for (k, rx) in enumerate(reactions(rn)) + stoich = rx.substoich + for (i, sub) in enumerate(rx.substrates) + isconstant(sub) && continue + smat[smap[sub], k] = stoich[i] + end + end + smat +end +function substoichmat(rn::ReactionSystem; sparse::Bool = false) + isempty(get_systems(rn)) || error("substoichmat does not currently support subsystems.") + T = reduce(promote_type, eltype(rx.substoich) for rx in reactions(rn)) + (T == Any) && + error("Stoichiometry matrices with symbolic stoichiometry are not supported") + sparse ? substoichmat(SparseMatrixCSC{T, Int}, rn) : substoichmat(Matrix{T}, rn) +end + +""" + prodstoichmat(rn; sparse=false) + +Returns the product stoichiometry matrix, ``P``, with ``P_{i j}`` the stoichiometric +coefficient of the ith product within the jth reaction. + +Note: +- Set sparse=true for a sparse matrix representation +- Note that constant species are not treated as products, but just components that modify + the associated rate law. +""" +function prodstoichmat(::Type{SparseMatrixCSC{T, Int}}, + rn::ReactionSystem) where {T <: Number} + Is = Int[] + Js = Int[] + Vs = T[] + smap = speciesmap(rn) + for (k, rx) in enumerate(reactions(rn)) + stoich = rx.prodstoich + for (i, prod) in enumerate(rx.products) + isconstant(prod) && continue + push!(Js, k) + push!(Is, smap[prod]) + push!(Vs, stoich[i]) + end + end + sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) +end +function prodstoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} + smap = speciesmap(rn) + pmat = zeros(T, numspecies(rn), numreactions(rn)) + for (k, rx) in enumerate(reactions(rn)) + stoich = rx.prodstoich + for (i, prod) in enumerate(rx.products) + isconstant(prod) && continue + pmat[smap[prod], k] = stoich[i] + end + end + pmat +end +function prodstoichmat(rn::ReactionSystem; sparse = false) + isempty(get_systems(rn)) || + error("prodstoichmat does not currently support subsystems.") + + T = reduce(promote_type, eltype(rx.prodstoich) for rx in reactions(rn)) + (T == Any) && + error("Stoichiometry matrices with symbolic stoichiometry are not supported") + sparse ? prodstoichmat(SparseMatrixCSC{T, Int}, rn) : prodstoichmat(Matrix{T}, rn) +end + +""" + netstoichmat(rn, sparse=false) + +Returns the net stoichiometry matrix, ``N``, with ``N_{i j}`` the net stoichiometric +coefficient of the ith species within the jth reaction. + +Notes: +- Set sparse=true for a sparse matrix representation +- Caches the matrix internally within `rn` so subsequent calls are fast. +- Note that constant species are not treated as reactants, but just components that modify + the associated rate law. As such they do not contribute to the net stoichiometry matrix. +""" +function netstoichmat(::Type{SparseMatrixCSC{T, Int}}, + rn::ReactionSystem) where {T <: Number} + Is = Int[] + Js = Int[] + Vs = Vector{T}() + smap = speciesmap(rn) + for (k, rx) in pairs(reactions(rn)) + for (spec, coef) in rx.netstoich + isconstant(spec) && continue + push!(Js, k) + push!(Is, smap[spec]) + push!(Vs, coef) + end + end + sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) +end +function netstoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} + smap = speciesmap(rn) + nmat = zeros(T, numspecies(rn), numreactions(rn)) + for (k, rx) in pairs(reactions(rn)) + for (spec, coef) in rx.netstoich + isconstant(spec) && continue + nmat[smap[spec], k] = coef + end + end + nmat +end + +netstoichtype(::Vector{Pair{S, T}}) where {S, T} = T + +function netstoichmat(rn::ReactionSystem; sparse = false) + isempty(get_systems(rn)) || + error("netstoichmat does not currently support subsystems, please create a flattened system before calling.") + + nps = get_networkproperties(rn) + + # if it is already calculated and has the right type + !isempty(nps.netstoichmat) && (sparse == issparse(nps.netstoichmat)) && + (return nps.netstoichmat) + + # identify a common stoichiometry type + T = reduce(promote_type, netstoichtype(rx.netstoich) for rx in reactions(rn)) + (T == Any) && + error("Stoichiometry matrices are not supported with symbolic stoichiometry.") + + if sparse + nsmat = netstoichmat(SparseMatrixCSC{T, Int}, rn) + else + nsmat = netstoichmat(Matrix{T}, rn) + end + + # only cache if it is integer + if T == Int + nps.netstoichmat = nsmat + end + + nsmat +end + +# the following function is adapted from SymbolicUtils.jl v.19 +# later on (Spetember 2023) modified by Torkel and Shashi (now assumes input not on polynomial form, which is handled elsewhere, previous version failed in these cases anyway). +# Copyright (c) 2020: Shashi Gowda, Yingbo Ma, Mason Protter, Julia Computing. +# MIT license +""" + to_multivariate_poly(polyeqs::AbstractVector{BasicSymbolic{Real}}) + +Convert the given system of polynomial equations to multivariate polynomial representation. +For example, this can be used in HomotopyContinuation.jl functions. +""" +function to_multivariate_poly(polyeqs::AbstractVector{Symbolics.BasicSymbolic{Real}}) + @assert length(polyeqs)>=1 "At least one expression must be passed to `multivariate_poly`." + + pvar2sym, sym2term = SymbolicUtils.get_pvar2sym(), SymbolicUtils.get_sym2term() + ps = map(polyeqs) do x + if istree(x) && operation(x) == (/) + error("We should not be able to get here, please contact the package authors.") + else + PolyForm(x, pvar2sym, sym2term).p + end + end + + ps +end +""" + setdefaults!(rn, newdefs) + +Sets the default (initial) values of parameters and species in the +`ReactionSystem`, `rn`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +setdefaults!(sir, [:S => 999.0, :I => 1.0, :R => 1.0, :β => 1e-4, :ν => .01]) + +# or +@parameter β ν +@variables t +@species S(t) I(t) R(t) +setdefaults!(sir, [S => 999.0, I => 1.0, R => 0.0, β => 1e-4, ν => .01]) +``` +gives initial/default values to each of `S`, `I` and `β` + +Notes: +- Can not be used to set default values for species, variables or parameters of + subsystems or constraint systems. Either set defaults for those systems + directly, or [`flatten`](@ref) to collate them into one system before setting + defaults. +- Defaults can be specified in any iterable container of symbols to value pairs + or symbolics to value pairs. +""" +function setdefaults!(rn, newdefs) + defs = eltype(newdefs) <: Pair{Symbol} ? symmap_to_varmap(rn, newdefs) : newdefs + rndefs = MT.get_defaults(rn) + for (var, val) in defs + rndefs[value(var)] = value(val) + end + nothing +end + +function __unpacksys(rn) + ex = :(begin end) + for key in keys(get_var_to_name(rn)) + var = MT.getproperty(rn, key, namespace = false) + push!(ex.args, :($key = $var)) + end + ex +end + +""" + @unpacksys sys::ModelingToolkit.AbstractSystem + +Loads all species, variables, parameters, and observables defined in `sys` as +variables within the calling module. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +@unpacksys sir +``` +will load the symbolic variables, `S`, `I`, `R`, `ν` and `β`. + +Notes: +- Can not be used to load species, variables, or parameters of subsystems or + constraints. Either call `@unpacksys` on those systems directly, or + [`flatten`](@ref) to collate them into one system before calling. +- Note that this places symbolic variables within the calling module's scope, so + calling from a function defined in a script or the REPL will still result in + the symbolic variables being defined in the `Main` module. +""" +macro unpacksys(rn) + quote + ex = Catalyst.__unpacksys($(esc(rn))) + Base.eval($(__module__), ex) + end +end + +# convert symbol of the form :sys.a.b.c to a symbolic a.b.c +function _symbol_to_var(sys, sym) + if hasproperty(sys, sym) + var = getproperty(sys, sym, namespace = false) + else + strs = split(String(sym), "₊") # need to check if this should be split of not!!! + if length(strs) > 1 + var = getproperty(sys, Symbol(strs[1]), namespace = false) + for str in view(strs, 2:length(strs)) + var = getproperty(var, Symbol(str), namespace = true) + end + else + throw(ArgumentError("System $(nameof(sys)): variable $sym does not exist")) + end + end + var +end + +""" + symmap_to_varmap(sys, symmap) + +Given a system and map of `Symbol`s to values, generates a map from +corresponding symbolic variables/parameters to the values that can be used to +pass initial conditions and parameter mappings. + +For example, +```julia +sir = @reaction_network sir begin + β, S + I --> 2I + ν, I --> R +end +subsys = @reaction_network subsys begin + k, A --> B +end +@named sys = compose(sir, [subsys]) +``` +gives +``` +Model sys with 3 equations +States (5): + S(t) + I(t) + R(t) + subsys₊A(t) + subsys₊B(t) +Parameters (3): + β + ν + subsys₊k +``` +to specify initial condition and parameter mappings from *symbols* we can use +```julia +symmap = [:S => 1.0, :I => 1.0, :R => 1.0, :subsys₊A => 1.0, :subsys₊B => 1.0] +u0map = symmap_to_varmap(sys, symmap) +pmap = symmap_to_varmap(sys, [:β => 1.0, :ν => 1.0, :subsys₊k => 1.0]) +``` +`u0map` and `pmap` can then be used as input to various problem types. + +Notes: +- Any `Symbol`, `sym`, within `symmap` must be a valid field of `sys`. i.e. + `sys.sym` must be defined. +""" +function symmap_to_varmap(sys, symmap::Tuple) + if all(p -> p isa Pair{Symbol}, symmap) + return ((_symbol_to_var(sys, sym) => val for (sym, val) in symmap)...,) + else # if not all entries map a symbol to value pass through + return symmap + end +end + +function symmap_to_varmap(sys, symmap::AbstractArray{Pair{Symbol, T}}) where {T} + [_symbol_to_var(sys, sym) => val for (sym, val) in symmap] +end + +function symmap_to_varmap(sys, symmap::Dict{Symbol, T}) where {T} + Dict(_symbol_to_var(sys, sym) => val for (sym, val) in symmap) +end + +# don't permute any other types and let varmap_to_vars handle erroring +symmap_to_varmap(sys, symmap) = symmap +#error("symmap_to_varmap requires a Dict, AbstractArray or Tuple to map Symbols to values.") + +######################## reaction complexes and reaction rates ############################### + +""" + reset_networkproperties!(rn::ReactionSystem) + +Clears the cache of various properties (like the netstoichiometry matrix). Use if such +properties need to be recalculated for some reason. +""" +function reset_networkproperties!(rn::ReactionSystem) + reset!(get_networkproperties(rn)) + nothing +end + +# get the species indices and stoichiometry while filtering out constant species. +function filter_constspecs(specs, stoich::AbstractVector{V}, smap) where {V <: Integer} + isempty(specs) && (return Vector{Int}(), Vector{V}()) + + if any(isconstant, specs) + ids = Vector{Int}() + filtered_stoich = Vector{V}() + for (i, s) in enumerate(specs) + if !isconstant(s) + push!(ids, smap[s]) + push!(filtered_stoich, stoich[i]) + end + end + else + ids = map(Base.Fix1(getindex, smap), specs) + filtered_stoich = copy(stoich) + end + ids, filtered_stoich +end + +""" + reactioncomplexmap(rn::ReactionSystem) + +Find each [`ReactionComplex`](@ref) within the specified system, constructing a mapping from +the complex to vectors that indicate which reactions it appears in as substrates and +products. + +Notes: +- Each [`ReactionComplex`](@ref) is mapped to a vector of pairs, with each pair having the + form `reactionidx => ± 1`, where `-1` indicates the complex appears as a substrate and + `+1` as a product in the reaction with integer label `reactionidx`. +- Constant species are ignored as part of a complex. i.e. if species `A` is constant then + the reaction `A + B --> C + D` is considered to consist of the complexes `B` and `C + D`. + Likewise `A --> B` would be treated as the same as `0 --> B`. +""" +function reactioncomplexmap(rn::ReactionSystem) + isempty(get_systems(rn)) || + error("reactioncomplexmap does not currently support subsystems.") + + # check if previously calculated and hence cached + nps = get_networkproperties(rn) + !isempty(nps.complextorxsmap) && return nps.complextorxsmap + complextorxsmap = nps.complextorxsmap + + rxs = reactions(rn) + smap = speciesmap(rn) + numreactions(rn) > 0 || + error("There must be at least one reaction to find reaction complexes.") + for (i, rx) in enumerate(rxs) + subids, substoich = filter_constspecs(rx.substrates, rx.substoich, smap) + subrc = sort!(ReactionComplex(subids, substoich)) + if haskey(complextorxsmap, subrc) + push!(complextorxsmap[subrc], i => -1) + else + complextorxsmap[subrc] = [i => -1] + end + + prodids, prodstoich = filter_constspecs(rx.products, rx.prodstoich, smap) + prodrc = sort!(ReactionComplex(prodids, prodstoich)) + if haskey(complextorxsmap, prodrc) + push!(complextorxsmap[prodrc], i => 1) + else + complextorxsmap[prodrc] = [i => 1] + end + end + complextorxsmap +end + +function reactioncomplexes(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, + complextorxsmap) + complexes = collect(keys(complextorxsmap)) + Is = Int[] + Js = Int[] + Vs = Int[] + for (i, c) in enumerate(complexes) + for (j, σ) in complextorxsmap[c] + push!(Is, i) + push!(Js, j) + push!(Vs, σ) + end + end + B = sparse(Is, Js, Vs, length(complexes), numreactions(rn)) + complexes, B +end +function reactioncomplexes(::Type{Matrix{Int}}, rn::ReactionSystem, complextorxsmap) + complexes = collect(keys(complextorxsmap)) + B = zeros(Int, length(complexes), numreactions(rn)) + for (i, c) in enumerate(complexes) + for (j, σ) in complextorxsmap[c] + B[i, j] = σ + end + end + complexes, B +end + +@doc raw""" + reactioncomplexes(network::ReactionSystem; sparse=false) + +Calculate the reaction complexes and complex incidence matrix for the given +[`ReactionSystem`](@ref). + +Notes: +- returns a pair of a vector of [`ReactionComplex`](@ref)s and the complex incidence matrix. +- An empty [`ReactionComplex`](@ref) denotes the null (∅) state (from reactions like ∅ -> A + or A -> ∅). +- Constant species are ignored in generating a reaction complex. i.e. if A is constant then + A --> B consists of the complexes ∅ and B. +- The complex incidence matrix, ``B``, is number of complexes by number of reactions with +```math +B_{i j} = \begin{cases} +-1, &\text{if the i'th complex is the substrate of the j'th reaction},\\ +1, &\text{if the i'th complex is the product of the j'th reaction},\\ +0, &\text{otherwise.} +\end{cases} +``` +- Set sparse=true for a sparse matrix representation of the incidence matrix +""" +function reactioncomplexes(rn::ReactionSystem; sparse = false) + isempty(get_systems(rn)) || + error("reactioncomplexes does not currently support subsystems.") + nps = get_networkproperties(rn) + if isempty(nps.complexes) || (sparse != issparse(nps.complexes)) + complextorxsmap = reactioncomplexmap(rn) + nps.complexes, nps.incidencemat = if sparse + reactioncomplexes(SparseMatrixCSC{Int, Int}, rn, complextorxsmap) + else + reactioncomplexes(Matrix{Int}, rn, complextorxsmap) + end + end + nps.complexes, nps.incidencemat +end + +""" + incidencemat(rn::ReactionSystem; sparse=false) + +Calculate the incidence matrix of `rn`, see [`reactioncomplexes`](@ref). + +Notes: +- Is cached in `rn` so that future calls, assuming the same sparsity, will also be fast. +""" +incidencemat(rn::ReactionSystem; sparse = false) = reactioncomplexes(rn; sparse)[2] + +function complexstoichmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, rcs) + Is = Int[] + Js = Int[] + Vs = Int[] + for (i, rc) in enumerate(rcs) + for rcel in rc + push!(Is, rcel.speciesid) + push!(Js, i) + push!(Vs, rcel.speciesstoich) + end + end + Z = sparse(Is, Js, Vs, numspecies(rn), length(rcs)) +end +function complexstoichmat(::Type{Matrix{Int}}, rn::ReactionSystem, rcs) + Z = zeros(Int, numspecies(rn), length(rcs)) + for (i, rc) in enumerate(rcs) + for rcel in rc + Z[rcel.speciesid, i] = rcel.speciesstoich + end + end + Z +end + +""" + complexstoichmat(network::ReactionSystem; sparse=false) + +Given a [`ReactionSystem`](@ref) and vector of reaction complexes, return a +matrix with positive entries of size number of species by number of complexes, +where the non-zero positive entries in the kth column denote stoichiometric +coefficients of the species participating in the kth reaction complex. + +Notes: +- Set sparse=true for a sparse matrix representation +""" +function complexstoichmat(rn::ReactionSystem; sparse = false) + isempty(get_systems(rn)) || + error("complexstoichmat does not currently support subsystems.") + nps = get_networkproperties(rn) + if isempty(nps.complexstoichmat) || (sparse != issparse(nps.complexstoichmat)) + nps.complexstoichmat = if sparse + complexstoichmat(SparseMatrixCSC{Int, Int}, rn, keys(reactioncomplexmap(rn))) + else + complexstoichmat(Matrix{Int}, rn, keys(reactioncomplexmap(rn))) + end + end + nps.complexstoichmat +end + +function complexoutgoingmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, B) + n = size(B, 2) + rows = rowvals(B) + vals = nonzeros(B) + Is = Int[] + Js = Int[] + Vs = Int[] + sizehint!(Is, div(length(vals), 2)) + sizehint!(Js, div(length(vals), 2)) + sizehint!(Vs, div(length(vals), 2)) + for j in 1:n + for i in nzrange(B, j) + if vals[i] != one(eltype(vals)) + push!(Is, rows[i]) + push!(Js, j) + push!(Vs, vals[i]) + end + end + end + sparse(Is, Js, Vs, size(B, 1), size(B, 2)) +end +function complexoutgoingmat(::Type{Matrix{Int}}, rn::ReactionSystem, B) + Δ = copy(B) + for (I, b) in pairs(Δ) + (b == 1) && (Δ[I] = 0) + end + Δ +end + +@doc raw""" + complexoutgoingmat(network::ReactionSystem; sparse=false) + +Given a [`ReactionSystem`](@ref) and complex incidence matrix, ``B``, return a +matrix of size num of complexes by num of reactions that identifies substrate +complexes. + +Notes: +- The complex outgoing matrix, ``\Delta``, is defined by +```math +\Delta_{i j} = \begin{cases} + = 0, &\text{if } B_{i j} = 1, \\ + = B_{i j}, &\text{otherwise.} +\end{cases} +``` +- Set sparse=true for a sparse matrix representation +""" +function complexoutgoingmat(rn::ReactionSystem; sparse = false) + isempty(get_systems(rn)) || + error("complexoutgoingmat does not currently support subsystems.") + nps = get_networkproperties(rn) + if isempty(nps.complexoutgoingmat) || (sparse != issparse(nps.complexoutgoingmat)) + B = reactioncomplexes(rn, sparse = sparse)[2] + nps.complexoutgoingmat = if sparse + complexoutgoingmat(SparseMatrixCSC{Int, Int}, rn, B) + else + complexoutgoingmat(Matrix{Int}, rn, B) + end + end + nps.complexoutgoingmat +end + +function incidencematgraph(incidencemat::Matrix{Int}) + @assert all(∈([-1, 0, 1]), incidencemat) + n = size(incidencemat, 1) # no. of nodes/complexes + graph = Graphs.DiGraph(n) + for col in eachcol(incidencemat) + src = 0 + dst = 0 + for i in eachindex(col) + (col[i] == -1) && (src = i) + (col[i] == 1) && (dst = i) + (src != 0) && (dst != 0) && break + end + Graphs.add_edge!(graph, src, dst) + end + return graph +end +function incidencematgraph(incidencemat::SparseMatrixCSC{Int, Int}) + @assert all(∈([-1, 0, 1]), incidencemat) + m, n = size(incidencemat) + graph = Graphs.DiGraph(m) + rows = rowvals(incidencemat) + vals = nonzeros(incidencemat) + for j in 1:n + inds = nzrange(incidencemat, j) + row = rows[inds] + val = vals[inds] + if val[1] == -1 + Graphs.add_edge!(graph, row[1], row[2]) + else + Graphs.add_edge!(graph, row[2], row[1]) + end + end + return graph +end + +""" + incidencematgraph(rn::ReactionSystem) + +Construct a directed simple graph where nodes correspond to reaction complexes and directed +edges to reactions converting between two complexes. + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +complexes,incidencemat = reactioncomplexes(sir) +incidencematgraph(sir) +``` +""" +function incidencematgraph(rn::ReactionSystem) + nps = get_networkproperties(rn) + if Graphs.nv(nps.incidencegraph) == 0 + isempty(nps.incidencemat) && + error("Please call reactioncomplexes(rn) first to construct the incidence matrix.") + nps.incidencegraph = incidencematgraph(nps.incidencemat) + end + nps.incidencegraph +end + +linkageclasses(incidencegraph) = Graphs.connected_components(incidencegraph) + +""" + linkageclasses(rn::ReactionSystem) + +Given the incidence graph of a reaction network, return a vector of the +connected components of the graph (i.e. sub-groups of reaction complexes that +are connected in the incidence graph). + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +complexes,incidencemat = reactioncomplexes(sir) +linkageclasses(sir) +``` +gives +```julia +2-element Vector{Vector{Int64}}: + [1, 2] + [3, 4] +``` +""" +function linkageclasses(rn::ReactionSystem) + nps = get_networkproperties(rn) + if isempty(nps.linkageclasses) + nps.linkageclasses = linkageclasses(incidencematgraph(rn)) + end + nps.linkageclasses +end + +@doc raw""" + deficiency(rn::ReactionSystem) + +Calculate the deficiency of a reaction network. + +Here the deficiency, ``\delta``, of a network with ``n`` reaction complexes, +``\ell`` linkage classes and a rank ``s`` stoichiometric matrix is + +```math +\delta = n - \ell - s +``` + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +rcs,incidencemat = reactioncomplexes(sir) +δ = deficiency(sir) +``` +""" +function deficiency(rn::ReactionSystem) + nps = get_networkproperties(rn) + conservationlaws(rn) + r = nps.rank + ig = incidencematgraph(rn) + lc = linkageclasses(rn) + nps.deficiency = Graphs.nv(ig) - length(lc) - r + nps.deficiency +end + +function subnetworkmapping(linkageclass, allrxs, complextorxsmap, p) + rxinds = sort!(collect(Set(rxidx for rcidx in linkageclass + for rxidx in complextorxsmap[rcidx]))) + rxs = allrxs[rxinds] + specset = Set(s for rx in rxs for s in rx.substrates if !isconstant(s)) + for rx in rxs + for product in rx.products + !isconstant(product) && push!(specset, product) + end + end + specs = collect(specset) + newps = Vector{eltype(p)}() + for rx in rxs + Symbolics.get_variables!(newps, rx.rate, p) + end + rxs, specs, newps # reactions and species involved in reactions of subnetwork +end + +""" + subnetworks(rn::ReactionSystem) + +Find subnetworks corresponding to each linkage class of the reaction network. + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +complexes,incidencemat = reactioncomplexes(sir) +subnetworks(sir) +``` +""" +function subnetworks(rs::ReactionSystem) + isempty(get_systems(rs)) || error("subnetworks does not currently support subsystems.") + lcs = linkageclasses(rs) + rxs = reactions(rs) + p = parameters(rs) + t = get_iv(rs) + spatial_ivs = get_sivs(rs) + complextorxsmap = [map(first, rcmap) for rcmap in values(reactioncomplexmap(rs))] + subnetworks = Vector{ReactionSystem}() + for i in 1:length(lcs) + reacs, specs, newps = subnetworkmapping(lcs[i], rxs, complextorxsmap, p) + newname = Symbol(nameof(rs), "_", i) + push!(subnetworks, + ReactionSystem(reacs, t, specs, newps; name = newname, spatial_ivs)) + end + subnetworks +end + +""" + linkagedeficiencies(network::ReactionSystem) + +Calculates the deficiency of each sub-reaction network within `network`. + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +rcs,incidencemat = reactioncomplexes(sir) +linkage_deficiencies = linkagedeficiencies(sir) +``` +""" +function linkagedeficiencies(rs::ReactionSystem) + lcs = linkageclasses(rs) + subnets = subnetworks(rs) + δ = zeros(Int, length(lcs)) + for (i, subnet) in enumerate(subnets) + conservationlaws(subnet) + nps = get_networkproperties(subnet) + δ[i] = length(lcs[i]) - 1 - nps.rank + end + δ +end + +""" + isreversible(rn::ReactionSystem) + +Given a reaction network, returns if the network is reversible or not. + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +rcs,incidencemat = reactioncomplexes(sir) +isreversible(sir) +``` +""" +function isreversible(rn::ReactionSystem) + ig = incidencematgraph(rn) + Graphs.reverse(ig) == ig +end + +""" + isweaklyreversible(rn::ReactionSystem, subnetworks) + +Determine if the reaction network with the given subnetworks is weakly reversible or not. + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +rcs,incidencemat = reactioncomplexes(sir) +subnets = subnetworks(rn) +isweaklyreversible(rn, subnets) +``` +""" +function isweaklyreversible(rn::ReactionSystem, subnets) + im = get_networkproperties(rn).incidencemat + isempty(im) && + error("Error, please call reactioncomplexes(rn::ReactionSystem) to ensure the incidence matrix has been cached.") + sparseig = issparse(im) + for subnet in subnets + nps = get_networkproperties(subnet) + isempty(nps.incidencemat) && reactioncomplexes(subnet; sparse = sparseig) + end + all(Graphs.is_strongly_connected ∘ incidencematgraph, subnets) +end + +############################################################################################ +######################## conservation laws ############################### + +""" + conservedequations(rn::ReactionSystem) + +Calculate symbolic equations from conservation laws, writing dependent variables as +functions of independent variables and the conservation law constants. + +Notes: +- Caches the resulting equations in `rn`, so will be fast on subsequent calls. + +Examples: +```@repl +rn = @reaction_network begin + k, A + B --> C + k2, C --> A + B + end +conservedequations(rn) +``` +gives +``` +2-element Vector{Equation}: + B(t) ~ A(t) + Γ[1] + C(t) ~ Γ[2] - A(t) +``` +""" +function conservedequations(rn::ReactionSystem) + conservationlaws(rn) + nps = get_networkproperties(rn) + nps.conservedeqs +end + +""" + conservationlaw_constants(rn::ReactionSystem) + +Calculate symbolic equations from conservation laws, writing the conservation law constants +in terms of the dependent and independent variables. + +Notes: +- Caches the resulting equations in `rn`, so will be fast on subsequent calls. + +Examples: +```@julia +rn = @reaction_network begin + k, A + B --> C + k2, C --> A + B + end +conservationlaw_constants(rn) +``` +gives +``` +2-element Vector{Equation}: + Γ[1] ~ B(t) - A(t) + Γ[2] ~ A(t) + C(t) +``` +""" +function conservationlaw_constants(rn::ReactionSystem) + conservationlaws(rn) + nps = get_networkproperties(rn) + nps.constantdefs +end + +""" + conservationlaws(netstoichmat::AbstractMatrix)::Matrix + +Given the net stoichiometry matrix of a reaction system, computes a matrix of +conservation laws, each represented as a row in the output. +""" +function conservationlaws(nsm::T; col_order = nothing) where {T <: AbstractMatrix} + + # compute the left nullspace over the integers + N = MT.nullspace(nsm'; col_order) + + # if all coefficients for a conservation law are negative, make positive + for Nrow in eachcol(N) + all(r -> r <= 0, Nrow) && (Nrow .*= -1) + end + + # check we haven't overflowed + iszero(N' * nsm) || error("Calculation of the conservation law matrix was inaccurate, " + * "likely due to numerical overflow. Please use a larger integer " + * "type like Int128 or BigInt for the net stoichiometry matrix.") + + T(N') +end + +function cache_conservationlaw_eqs!(rn::ReactionSystem, N::AbstractMatrix, col_order) + nullity = size(N, 1) + r = numspecies(rn) - nullity # rank of the netstoichmat + sts = species(rn) + indepidxs = col_order[begin:r] + indepspecs = sts[indepidxs] + depidxs = col_order[(r + 1):end] + depspecs = sts[depidxs] + constants = MT.unwrap.(MT.scalarize((@parameters Γ[1:nullity])[1])) + + conservedeqs = Equation[] + constantdefs = Equation[] + for (i, depidx) in enumerate(depidxs) + scaleby = (N[i, depidx] != 1) ? N[i, depidx] : one(eltype(N)) + (scaleby != 0) || error("Error, found a zero in the conservation law matrix where " + * + "one was not expected.") + coefs = @view N[i, indepidxs] + terms = sum(p -> p[1] / scaleby * p[2], zip(coefs, indepspecs)) + eq = depspecs[i] ~ constants[i] - terms + push!(conservedeqs, eq) + eq = constants[i] ~ depspecs[i] + terms + push!(constantdefs, eq) + end + + # cache in the system + nps = get_networkproperties(rn) + nps.rank = r + nps.nullity = nullity + nps.indepspecs = Set(indepspecs) + nps.depspecs = Set(depspecs) + nps.conservedeqs = conservedeqs + nps.constantdefs = constantdefs + + nothing +end + +""" + conservationlaws(rs::ReactionSystem) + +Return the conservation law matrix of the given `ReactionSystem`, calculating it if it is +not already stored within the system, or returning an alias to it. + +Notes: +- The first time being called it is calculated and cached in `rn`, subsequent calls should + be fast. +""" +function conservationlaws(rs::ReactionSystem) + nps = get_networkproperties(rs) + !isempty(nps.conservationmat) && (return nps.conservationmat) + nsm = netstoichmat(rs) + nps.conservationmat = conservationlaws(nsm; col_order = nps.col_order) + cache_conservationlaw_eqs!(rs, nps.conservationmat, nps.col_order) + nps.conservationmat +end + +""" + conservedquantities(state, cons_laws) + +Compute conserved quantities for a system with the given conservation laws. +""" +conservedquantities(state, cons_laws) = cons_laws * state + + +# If u0s are not given while conservation laws are present, throws an error. +# Used in HomotopyContinuation and BifurcationKit extensions. +# Currently only checks if any u0s are given +# (not whether these are enough for computing conserved quantitites, this will yield a less informative error). +function conservationlaw_errorcheck(rs, pre_varmap) + vars_with_vals = Set(p[1] for p in pre_varmap) + any(s -> s in vars_with_vals, species(rs)) && return + isempty(conservedequations(Catalyst.flatten(rs))) || + error("The system has conservation laws but initial conditions were not provided for some species.") +end + +######################## reaction network operators ####################### + +""" + ==(rx1::Reaction, rx2::Reaction) + +Tests whether two [`Reaction`](@ref)s are identical. + +Notes: +- Ignores the order in which stoichiometry components are listed. +- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be + considered different than `(A+1)^2`. +""" +function (==)(rx1::Reaction, rx2::Reaction) + isequal(rx1.rate, rx2.rate) || return false + issetequal(zip(rx1.substrates, rx1.substoich), zip(rx2.substrates, rx2.substoich)) || + return false + issetequal(zip(rx1.products, rx1.prodstoich), zip(rx2.products, rx2.prodstoich)) || + return false + issetequal(rx1.netstoich, rx2.netstoich) || return false + rx1.only_use_rate == rx2.only_use_rate +end + +function hash(rx::Reaction, h::UInt) + h = Base.hash(rx.rate, h) + for s in Iterators.flatten((rx.substrates, rx.products)) + h ⊻= hash(s) + end + for s in Iterators.flatten((rx.substoich, rx.prodstoich)) + h ⊻= hash(s) + end + for s in rx.netstoich + h ⊻= hash(s) + end + Base.hash(rx.only_use_rate, h) +end + +""" + isequivalent(rn1::ReactionSystem, rn2::ReactionSystem; ignorenames = true) + +Tests whether the underlying species, parameters and reactions are the same in +the two [`ReactionSystem`](@ref)s. Ignores the names of the systems in testing +equality. + +Notes: +- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be + considered different than `(A+1)^2`. +- Does not include `defaults` in determining equality. +""" +function isequivalent(rn1::ReactionSystem, rn2::ReactionSystem; ignorenames = true) + if !ignorenames + (nameof(rn1) == nameof(rn2)) || return false + end + + (get_combinatoric_ratelaws(rn1) == get_combinatoric_ratelaws(rn2)) || return false + isequal(get_iv(rn1), get_iv(rn2)) || return false + issetequal(get_sivs(rn1), get_sivs(rn2)) || return false + issetequal(get_states(rn1), get_states(rn2)) || return false + issetequal(get_ps(rn1), get_ps(rn2)) || return false + issetequal(MT.get_observed(rn1), MT.get_observed(rn2)) || return false + issetequal(get_eqs(rn1), get_eqs(rn2)) || return false + + # subsystems + (length(get_systems(rn1)) == length(get_systems(rn2))) || return false + issetequal(get_systems(rn1), get_systems(rn2)) || return false + + true +end + +function isequal_ignore_names(rn1, rn2) + Base.depwarn("Catalyst.isequal_ignore_names has been deprecated. Use isequivalent(rn1, rn2) instead.", + :isequal_ignore_names; force = true) + isequivalent(rn1, rn2) +end + +""" + ==(rn1::ReactionSystem, rn2::ReactionSystem) + +Tests whether the underlying species, parameters and reactions are the same in +the two [`ReactionSystem`](@ref)s. Requires the systems to have the same names +too. + +Notes: +- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be + considered different than `(A+1)^2`. +- Does not include `defaults` in determining equality. +""" +function (==)(rn1::ReactionSystem, rn2::ReactionSystem) + isequivalent(rn1, rn2; ignorenames = false) +end + +######################## functions to extend a network #################### + +""" + make_empty_network(; iv=DEFAULT_IV, name=gensym(:ReactionSystem)) + +Construct an empty [`ReactionSystem`](@ref). `iv` is the independent variable, +usually time, and `name` is the name to give the `ReactionSystem`. +""" +function make_empty_network(; iv = DEFAULT_IV, name = gensym(:ReactionSystem)) + ReactionSystem(Reaction[], iv, [], []; name = name) +end + +""" + addspecies!(network::ReactionSystem, s::Symbolic; disablechecks=false) + +Given a [`ReactionSystem`](@ref), add the species corresponding to the variable +`s` to the network (if it is not already defined). Returns the integer id of the +species within the system. + +Notes: +- `disablechecks` will disable checking for whether the passed in variable is + already defined, which is useful when adding many new variables to the system. + *Do not disable checks* unless you are sure the passed in variable is a new + variable, as this will potentially leave the system in an undefined state. +""" +function addspecies!(network::ReactionSystem, s::Symbolic; disablechecks = false) + reset_networkproperties!(network) + + isconstant(s) && error("Constant species should be added via addparams!.") + isspecies(s) || + error("$s is not a valid symbolic species. Please use @species to declare it.") + + # we don't check subsystems since we will add it to the top-level system... + curidx = disablechecks ? nothing : findfirst(S -> isequal(S, s), get_states(network)) + if curidx === nothing + push!(get_states(network), s) + sort!(get_states(network); by = !isspecies) + push!(get_species(network), s) + MT.process_variables!(get_var_to_name(network), get_defaults(network), [s]) + return length(get_species(network)) + else + return curidx + end +end + +""" + addspecies!(network::ReactionSystem, s::Num; disablechecks=false) + +Given a [`ReactionSystem`](@ref), add the species corresponding to the +variable `s` to the network (if it is not already defined). Returns the +integer id of the species within the system. + +- `disablechecks` will disable checking for whether the passed in variable is + already defined, which is useful when adding many new variables to the system. + *Do not disable checks* unless you are sure the passed in variable is a new + variable, as this will potentially leave the system in an undefined state. +""" +function addspecies!(network::ReactionSystem, s::Num; disablechecks = false) + addspecies!(network, value(s), disablechecks = disablechecks) +end + +""" + reorder_states!(rn, neworder) + +Given a [`ReactionSystem`](@ref) and a vector `neworder`, reorders the states of `rn`, i.e. +`get_states(rn)`, according to `neworder`. + +Notes: +- Currently only supports `ReactionSystem`s without subsystems. +""" +function reorder_states!(rn, neworder) + reset_networkproperties!(rn) + + permute!(get_states(rn), neworder) + if !issorted(get_states(rn); by = !isspecies) + @warn "New ordering has resulted in a non-species state preceding a species state. This is not allowed so states have been resorted to ensure species precede non-species." + sort!(get_states(rn); by = !isspecies) + end + get_species(rn) .= Iterators.filter(isspecies, get_states(rn)) + nothing +end + +""" + addparam!(network::ReactionSystem, p::Symbolic; disablechecks=false) + +Given a [`ReactionSystem`](@ref), add the parameter corresponding to the +variable `p` to the network (if it is not already defined). Returns the integer +id of the parameter within the system. + +- `disablechecks` will disable checking for whether the passed in variable is + already defined, which is useful when adding many new variables to the system. + *Do not disable checks* unless you are sure the passed in variable is a new + variable, as this will potentially leave the system in an undefined state. +""" +function addparam!(network::ReactionSystem, p::Symbolic; disablechecks = false) + reset_networkproperties!(network) + + # we don't check subsystems since we will add it to the top-level system... + if istree(p) && !(operation(p) isa Symbolic && !istree(operation(p))) + error("If the passed in parameter is an expression, it must correspond to an underlying Variable.") + end + curidx = disablechecks ? nothing : findfirst(S -> isequal(S, p), get_ps(network)) + if curidx === nothing + push!(get_ps(network), p) + MT.process_variables!(get_var_to_name(network), get_defaults(network), [p]) + return length(get_ps(network)) + else + return curidx + end +end + +""" + addparam!(network::ReactionSystem, p::Num; disablechecks=false) + +Given a [`ReactionSystem`](@ref), add the parameter corresponding to the +variable `p` to the network (if it is not already defined). Returns the +integer id of the parameter within the system. + +- `disablechecks` will disable checking for whether the passed in variable is + already defined, which is useful when adding many new variables to the system. + *Do not disable checks* unless you are sure the passed in variable is a new + variable, as this will potentially leave the system in an undefined state. +""" +function addparam!(network::ReactionSystem, p::Num; disablechecks = false) + addparam!(network, value(p); disablechecks = disablechecks) +end + +""" + addreaction!(network::ReactionSystem, rx::Reaction) + +Add the passed in reaction to the [`ReactionSystem`](@ref). Returns the +integer id of `rx` in the list of `Reaction`s within `network`. + +Notes: +- Any new species or parameters used in `rx` should be separately added to + `network` using [`addspecies!`](@ref) and [`addparam!`](@ref). +""" +function addreaction!(network::ReactionSystem, rx::Reaction) + reset_networkproperties!(network) + push!(get_eqs(network), rx) + sort(get_eqs(network); by = eqsortby) + push!(get_rxs(network), rx) + length(get_rxs(network)) +end + +""" + merge!(network1::ReactionSystem, network2::ReactionSystem) + +Merge `network2` into `network1`. + +Notes: +- Duplicate reactions between the two networks are not filtered out. +- [`Reaction`](@ref)s are not deepcopied to minimize allocations, so both + networks will share underlying data arrays. +- Subsystems are not deepcopied between the two networks and will hence be + shared. +- Returns `network1`. +- `combinatoric_ratelaws` is the value of `network1`. +""" +function Base.merge!(network1::ReactionSystem, network2::ReactionSystem) + isequal(get_iv(network1), get_iv(network2)) || + error("Reaction networks must have the same independent variable to be mergable.") + union!(get_sivs(network1), get_sivs(network2)) + + union!(get_eqs(network1), get_eqs(network2)) + sort!(get_eqs(network1), by = eqsortby) + union!(get_rxs(network1), get_rxs(network2)) + (count(eq -> eq isa Reaction, get_eqs(network1)) == length(get_rxs(network1))) || + error("Unequal number of reactions from get_rxs(sys) and get_eqs(sys) after merging.") + + union!(get_states(network1), get_states(network2)) + sort!(get_states(network1), by = !isspecies) + union!(get_species(network1), get_species(network2)) + (count(isspecies, get_states(network1)) == length(get_species(network1))) || + error("Unequal number of species from get_species(sys) and get_states(sys) after merging.") + + union!(get_ps(network1), get_ps(network2)) + union!(get_observed(network1), get_observed(network2)) + union!(get_systems(network1), get_systems(network2)) + merge!(get_defaults(network1), get_defaults(network2)) + union!(MT.get_continuous_events(network1), MT.get_continuous_events(network2)) + union!(MT.get_discrete_events(network1), MT.get_discrete_events(network2)) + + reset_networkproperties!(network1) + network1 +end + +############################### units ##################################### + +""" + validate(rx::Reaction; info::String = "") + +Check that all substrates and products within the given [`Reaction`](@ref) have +the same units, and that the units of the reaction's rate expression are +internally consistent (i.e. if the rate involves sums, each term in the sum has +the same units). + +""" +function validate(rx::Reaction; info::String = "") + validated = MT._validate([rx.rate], [string(rx, ": rate")], info = info) + + subunits = isempty(rx.substrates) ? nothing : get_unit(rx.substrates[1]) + for i in 2:length(rx.substrates) + if get_unit(rx.substrates[i]) != subunits + validated = false + @warn(string("In ", rx, " the substrates have differing units.")) + end + end + + produnits = isempty(rx.products) ? nothing : get_unit(rx.products[1]) + for i in 2:length(rx.products) + if get_unit(rx.products[i]) != produnits + validated = false + @warn(string("In ", rx, " the products have differing units.")) + end + end + + if (subunits !== nothing) && (produnits !== nothing) && (subunits != produnits) + validated = false + @warn(string("in ", rx, + " the substrate units are not consistent with the product units.")) + end + + validated +end + +""" + validate(rs::ReactionSystem, info::String="") + +Check that all species in the [`ReactionSystem`](@ref) have the same units, and +that the rate laws of all reactions reduce to units of (species units) / (time +units). + +Notes: +- Does not check subsystems, constraint equations, or non-species variables. +""" +function validate(rs::ReactionSystem, info::String = "") + specs = get_species(rs) + + # if there are no species we don't check units on the system + isempty(specs) && return true + + specunits = get_unit(specs[1]) + validated = true + for spec in specs + if get_unit(spec) != specunits + validated = false + @warn(string("Species are expected to have units of ", specunits, + " however, species ", spec, " has units ", get_unit(spec), ".")) + end + end + timeunits = get_unit(get_iv(rs)) + + # no units for species, time or parameters then assume validated + (specunits in (MT.unitless, nothing)) && (timeunits in (MT.unitless, nothing)) && + MT.all_dimensionless(get_ps(rs)) && return true + + rateunits = specunits / timeunits + for rx in get_rxs(rs) + rxunits = get_unit(rx.rate) + for (i, sub) in enumerate(rx.substrates) + rxunits *= get_unit(sub)^rx.substoich[i] + end + + if rxunits != rateunits + validated = false + @warn(string("Reaction rate laws are expected to have units of ", rateunits, + " however, ", rx, " has units of ", rxunits, ".")) + end + end + + validated +end + +function iscomplexbalanced(rs::ReactionSystem, conc::Vector) + sm = speciesmap(rs) + cm = reactioncomplexmap(rs) + complexes, incidencemat = reactioncomplexes(rs) + complexbalanced = true + + for c in complexes + if (sum([massactionrate(rs, rxn..., conc) for rxn in cm[c]]) != 0) + return false + end + end + complexbalanced +end + +""" + complexbalanced(rs::ReactionSystem, rates::Vector) + +Constructively compute whether a network will have complex-balanced equilibrium +solutions, following the method in this paper. + +Notes: +- Does not check subsystems, constraint equations, or non-species variables. +""" + +using MetaGraphs, Combinatorics, LinearAlgebra + +function complexbalanced(rs::ReactionSystem, rates::Vector) + if length(rates) != numparams(rs) + error("The number of reaction rates must be equal to the number of parameters") + end + rxm = Dict(zip(reactionparams(rs), rates)) + sm = speciesmap(rs) + cm = reactioncomplexmap(rs) + complexes, D = reactioncomplexes(rs) + rxns = reactions(rs) + nc = length(complexes); nr = numreactions(rs); nm = numspecies(rs) + + # Construct kinetic matrix, K + K = zeros(nr, nc) + for c in 1:nc + complex = complexes[c] + for (r, dir) in cm[complex] + rxn = rxns[r] + if dir == -1 + K[r, c] = rxm[rxn.rate] + end + end + end + + L = -D*K + S = netstoichmat(rs) + + # Compute ρ using the matrix-tree theorem + ρ = zeros(nc) + lcs = linkageclasses(rs) + rwg = rateweightedgraph(rs, rates) + + for lc in lcs + sg, vmap = Graphs.induced_subgraph(rwg, lc) + ρ_j = matrixtree(sg) + ρ[lc] = ρ_j + end + + # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T + if all(x -> x > 0, ρ) + img = D'*log.(ρ) + if rank(S') == rank(hcat(S', img)) + return true + else + return false + end + else + return false + end +end + +function rateweightedgraph(rs::ReactionSystem, rates::Vector) + if length(rates) != numparams(rs) + error("The number of reaction rates must be equal to the number of parameters") + end + rm = Dict(zip(reactionparams(rs), rates)) + + complexes, D = reactioncomplexes(rs) + rxns = reactions(rs) + + g = incidencematgraph(rs) + rwg = MetaDiGraph(g) + + for v in vertices(rwg) + set_prop!(rwg, v, :complex, complexes[v]) + end + + for (i, e) in collect(enumerate(edges(rwg))) + rxn = rxns[i] + set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :reaction, rxn) + set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :rate, rm[rxn.rate]) + end + + rwg +end + +function matrixtree(g::MetaDiGraph) + # generate all spanning trees + # TODO: implement Winter's algorithm for generating spanning trees + n = nv(g) + ug = SimpleGraph(SimpleDiGraph(g)) + trees = collect(Combinatorics.combinations(collect(edges(ug)), n-1)) + trees = SimpleGraph.(trees) + trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) + + π = zeros(n) + + function treeweight(t::SimpleDiGraph) + prod = 1 + for e in edges(t) + rate = Graphs.has_edge(g, Graphs.src(e), Graphs.dst(e)) ? get_prop(g, e, :rate) : 0 + prod *= rate + end + prod + end + + # constructed rooted trees for every edge, compute sum + for v in 1:n + rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir=:in)) for t in trees] + π[v] = sum([treeweight(t) for t in rootedTrees]) + end + + # sum the contributions + return π +end + +function massactionrate(rs::ReactionSystem, rxn_idx::Int, dir::Int, conc::Vector) + if dir != -1 && dir != 1 + error("Direction must be either +1 in the case of production or -1 in the case of consumption") + end + + rxn = reactions(rs)[rxn_idx] + sm = speciesmap(rs) + rate = rxn.rate + + species = rxn.substrates + stoich = rxn.substoich + species_idx = [sm[s] for s in species] + + dir * rate * prod(conc[species_idx].^stoich) +end From a51d1457eebc0e0b4e450c570ae6ebe19bdfb474 Mon Sep 17 00:00:00 2001 From: vydu Date: Sun, 12 May 2024 00:28:18 -0400 Subject: [PATCH 047/446] Added tests for complex balancing --- test/network_analysis/network_properties.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/network_analysis/network_properties.jl b/test/network_analysis/network_properties.jl index b5d488642a..f474c1bd60 100644 --- a/test/network_analysis/network_properties.jl +++ b/test/network_analysis/network_properties.jl @@ -40,6 +40,9 @@ let @test isweaklyreversible(MAPK, subnetworks(MAPK)) == false cls = conservationlaws(MAPK) @test Catalyst.get_networkproperties(MAPK).rank == 15 + + rates = rand(numparams(MAPK)) + @test Catalyst.complexbalanced(MAPK, rates) == false # i=0; # for lcs in linkageclasses(MAPK) # i=i+1 @@ -77,6 +80,9 @@ let @test isweaklyreversible(rn2, subnetworks(rn2)) == false cls = conservationlaws(rn2) @test Catalyst.get_networkproperties(rn2).rank == 6 + + rates = rand(numparams(rn2)) + @test Catalyst.complexbalanced(rn2, rates) == false # i=0; # for lcs in linkageclasses(rn2) # i=i+1 @@ -117,6 +123,9 @@ let @test isweaklyreversible(rn3, subnetworks(rn3)) == false cls = conservationlaws(rn3) @test Catalyst.get_networkproperties(rn3).rank == 10 + + rates = rand(numparams(rn3)) + @test Catalyst.complexbalanced(rn3, rates) == false # i=0; # for lcs in linkageclasses(rn3) # i=i+1 From b8d59a0f9542d9c31aae3c0c2ee7626060601888 Mon Sep 17 00:00:00 2001 From: vydu Date: Sun, 12 May 2024 17:46:42 -0400 Subject: [PATCH 048/446] added documentation --- Project.toml | 3 +++ src/networkapi.jl | 24 ++++++------------------ 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/Project.toml b/Project.toml index 2b90ff7b2a..920ddcf6d2 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,7 @@ uuid = "479239e8-5488-4da2-87a7-35f2df7eef83" version = "13.5.1" [deps] +Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" @@ -14,10 +15,12 @@ LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +MetaGraphs = "626554b9-1ddb-594c-aa3c-2596fe9399a5" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Requires = "ae029012-a4dd-5104-9daa-d747884805df" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" RuntimeGeneratedFunctions = "7e49a35a-f44a-4d26-94aa-eba1b4ca6b47" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" diff --git a/src/networkapi.jl b/src/networkapi.jl index f0cc164856..61d226de2c 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1653,28 +1653,11 @@ function validate(rs::ReactionSystem, info::String = "") validated end -function iscomplexbalanced(rs::ReactionSystem, conc::Vector) - sm = speciesmap(rs) - cm = reactioncomplexmap(rs) - complexes, incidencemat = reactioncomplexes(rs) - complexbalanced = true - - for c in complexes - if (sum([massactionrate(rs, rxn..., conc) for rxn in cm[c]]) != 0) - return false - end - end - complexbalanced -end - """ complexbalanced(rs::ReactionSystem, rates::Vector) Constructively compute whether a network will have complex-balanced equilibrium -solutions, following the method in this paper. - -Notes: -- Does not check subsystems, constraint equations, or non-species variables. +solutions, following the method in [this paper](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). """ using MetaGraphs, Combinatorics, LinearAlgebra @@ -1729,6 +1712,11 @@ function complexbalanced(rs::ReactionSystem, rates::Vector) end end +""" + rateweightedgraph(rs::ReactionSystem, rates::Vector) + +Generate an annotated reaction complex graph of a reaction system, where the nodes are annotated with the reaction complex they correspond to and the edges are annotated with the reaction they correspond to and the rate of the reaction. +""" function rateweightedgraph(rs::ReactionSystem, rates::Vector) if length(rates) != numparams(rs) error("The number of reaction rates must be equal to the number of parameters") From 7aa1eb6d9909c5dd0b712029841ec98304222c15 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 16 May 2024 10:59:35 -0400 Subject: [PATCH 049/446] Restructured files --- Project.toml | 2 +- src/Project.toml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 src/Project.toml diff --git a/Project.toml b/Project.toml index 920ddcf6d2..ac53aaee49 100644 --- a/Project.toml +++ b/Project.toml @@ -20,7 +20,6 @@ ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Requires = "ae029012-a4dd-5104-9daa-d747884805df" -Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" RuntimeGeneratedFunctions = "7e49a35a-f44a-4d26-94aa-eba1b4ca6b47" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" @@ -41,6 +40,7 @@ CatalystStructuralIdentifiabilityExtension = "StructuralIdentifiability" [compat] BifurcationKit = "0.3" DataStructures = "0.18" +Combinatorics = "1.0.2" DiffEqBase = "6.83.0" DocStringExtensions = "0.8, 0.9" DynamicQuantities = "0.13.2" diff --git a/src/Project.toml b/src/Project.toml deleted file mode 100644 index e7b949696b..0000000000 --- a/src/Project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[deps] -Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83" From a2312eb59a62afe8df0ac60abf3cb90ca6bada34 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 16 May 2024 17:08:40 -0400 Subject: [PATCH 050/446] Refactored complex balance to not use MetaGraphs --- src/networkapi.jl | 143 ++++++++++++-------- test/network_analysis/network_properties.jl | 74 ++++++++-- 2 files changed, 153 insertions(+), 64 deletions(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index 61d226de2c..bf9e31a3dc 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1654,25 +1654,27 @@ function validate(rs::ReactionSystem, info::String = "") end """ - complexbalanced(rs::ReactionSystem, rates::Vector) + iscomplexbalanced(rs::ReactionSystem, rates::Vector) Constructively compute whether a network will have complex-balanced equilibrium -solutions, following the method in [this paper](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). +solutions, following the method in [this paper](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a map of rates [k1 => 1.0, k2 => 2.0,...]k2 """ -using MetaGraphs, Combinatorics, LinearAlgebra +using Combinatorics, LinearAlgebra -function complexbalanced(rs::ReactionSystem, rates::Vector) +function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) if length(rates) != numparams(rs) error("The number of reaction rates must be equal to the number of parameters") end - rxm = Dict(zip(reactionparams(rs), rates)) + sm = speciesmap(rs) cm = reactioncomplexmap(rs) complexes, D = reactioncomplexes(rs) rxns = reactions(rs) nc = length(complexes); nr = numreactions(rs); nm = numspecies(rs) + if !all(r->ismassaction(r, rs), rxns) error("Not mass action") end + # Construct kinetic matrix, K K = zeros(nr, nc) for c in 1:nc @@ -1680,7 +1682,7 @@ function complexbalanced(rs::ReactionSystem, rates::Vector) for (r, dir) in cm[complex] rxn = rxns[r] if dir == -1 - K[r, c] = rxm[rxn.rate] + K[r, c] = rates[rxn.rate] end end end @@ -1689,93 +1691,122 @@ function complexbalanced(rs::ReactionSystem, rates::Vector) S = netstoichmat(rs) # Compute ρ using the matrix-tree theorem - ρ = zeros(nc) - lcs = linkageclasses(rs) - rwg = rateweightedgraph(rs, rates) + g = incidencematgraph(rs); R = ratematrix(rs, rates) + ρ = matrixtree(g, R) + @assert isapprox(L*ρ, zeros(nc), atol=1e-12) - for lc in lcs - sg, vmap = Graphs.induced_subgraph(rwg, lc) - ρ_j = matrixtree(sg) - ρ[lc] = ρ_j - end - # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T if all(x -> x > 0, ρ) img = D'*log.(ρ) - if rank(S') == rank(hcat(S', img)) - return true - else - return false - end + if rank(S') == rank(hcat(S', img)) return true else return false end else return false end end -""" - rateweightedgraph(rs::ReactionSystem, rates::Vector) - -Generate an annotated reaction complex graph of a reaction system, where the nodes are annotated with the reaction complex they correspond to and the edges are annotated with the reaction they correspond to and the rate of the reaction. -""" -function rateweightedgraph(rs::ReactionSystem, rates::Vector) +# """ +# rateweightedgraph(rs::ReactionSystem, rates::Vector) +# +# Generate an annotated reaction complex graph of a reaction system, where the nodes are annotated with the reaction complex they correspond to and the edges are annotated with the reaction they correspond to and the rate of the reaction. +# """ +# +# function rateweightedgraph(rs::ReactionSystem, rates::Dict{Any, Float64}) +# if length(rates) != numparams(rs) +# error("The number of reaction rates must be equal to the number of parameters") +# end +# +# complexes, D = reactioncomplexes(rs) +# rxns = reactions(rs) +# +# g = incidencematgraph(rs) +# rwg = MetaDiGraph(g) +# +# for v in vertices(rwg) +# set_prop!(rwg, v, :complex, complexes[v]) +# end +# +# for (i, e) in collect(enumerate(edges(rwg))) +# rxn = rxns[i] +# set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :reaction, rxn) +# set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :rate, rates[rxn.rate]) +# end +# +# rwg +# end + +function ratematrix(rs::ReactionSystem, rates::Dict{Any, Float64}) if length(rates) != numparams(rs) error("The number of reaction rates must be equal to the number of parameters") end - rm = Dict(zip(reactionparams(rs), rates)) complexes, D = reactioncomplexes(rs) + n = length(complexes) rxns = reactions(rs) + ratematrix = zeros(n, n) - g = incidencematgraph(rs) - rwg = MetaDiGraph(g) - - for v in vertices(rwg) - set_prop!(rwg, v, :complex, complexes[v]) + for r in 1:length(rxns) + rxn = rxns[r] + s = findfirst(x->x==-1, D[:,r]) + p = findfirst(x->x==1, D[:,r]) + ratematrix[s, p] = rates[rxn.rate] end + ratematrix +end - for (i, e) in collect(enumerate(edges(rwg))) - rxn = rxns[i] - set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :reaction, rxn) - set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :rate, rm[rxn.rate]) +""" +""" +function matrixtree(g::SimpleDiGraph, distmx::Matrix) + n = nv(g) + if size(distmx) != (n, n) + error("Size of distance matrix is incorrect") end - rwg -end + π = zeros(n) + + if !Graphs.is_connected(g) + ccs = Graphs.connected_components(g) + for cc in ccs + sg, vmap = Graphs.induced_subgraph(g, cc) + distmx_s = distmx[cc, cc] + π_j = matrixtree(sg, distmx_s) + π[cc] = π_j + end + return π + end -function matrixtree(g::MetaDiGraph) # generate all spanning trees - # TODO: implement Winter's algorithm for generating spanning trees - n = nv(g) ug = SimpleGraph(SimpleDiGraph(g)) trees = collect(Combinatorics.combinations(collect(edges(ug)), n-1)) trees = SimpleGraph.(trees) trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) - - π = zeros(n) - - function treeweight(t::SimpleDiGraph) - prod = 1 - for e in edges(t) - rate = Graphs.has_edge(g, Graphs.src(e), Graphs.dst(e)) ? get_prop(g, e, :rate) : 0 - prod *= rate - end - prod - end + # trees = spanningtrees(g) # constructed rooted trees for every edge, compute sum for v in 1:n rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir=:in)) for t in trees] - π[v] = sum([treeweight(t) for t in rootedTrees]) + π[v] = sum([treeweight(t, g, distmx) for t in rootedTrees]) end # sum the contributions return π end -function massactionrate(rs::ReactionSystem, rxn_idx::Int, dir::Int, conc::Vector) - if dir != -1 && dir != 1 - error("Direction must be either +1 in the case of production or -1 in the case of consumption") +function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) + prod = 1 + for e in edges(t) + s = Graphs.src(e); t = Graphs.dst(e) + prod *= distmx[s, t] end + prod +end + +# TODO: implement Winter's algorithm for generating spanning trees +function spanningtrees(g::SimpleGraph) + +end + +# Checks if a unit consist of exponents with base 1 (and is this unitless). +unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) rxn = reactions(rs)[rxn_idx] sm = speciesmap(rs) diff --git a/test/network_analysis/network_properties.jl b/test/network_analysis/network_properties.jl index f474c1bd60..35f5b40c2a 100644 --- a/test/network_analysis/network_properties.jl +++ b/test/network_analysis/network_properties.jl @@ -1,7 +1,9 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, LinearAlgebra, Test +using Catalyst, LinearAlgebra, Test, StableRNGs + +rng = StableRNG(514) ### Basic Tests ### @@ -41,8 +43,9 @@ let cls = conservationlaws(MAPK) @test Catalyst.get_networkproperties(MAPK).rank == 15 - rates = rand(numparams(MAPK)) - @test Catalyst.complexbalanced(MAPK, rates) == false + k = rand(rng, numparams(MAPK)) + rates = Dict(zip(reactionparams(MAPK), k)) + @test Catalyst.iscomplexbalanced(MAPK, rates) == false # i=0; # for lcs in linkageclasses(MAPK) # i=i+1 @@ -81,8 +84,9 @@ let cls = conservationlaws(rn2) @test Catalyst.get_networkproperties(rn2).rank == 6 - rates = rand(numparams(rn2)) - @test Catalyst.complexbalanced(rn2, rates) == false + k = rand(rng, numparams(rn2)) + rates = Dict(zip(reactionparams(rn2), k)) + @test Catalyst.iscomplexbalanced(rn2, rates) == false # i=0; # for lcs in linkageclasses(rn2) # i=i+1 @@ -123,9 +127,10 @@ let @test isweaklyreversible(rn3, subnetworks(rn3)) == false cls = conservationlaws(rn3) @test Catalyst.get_networkproperties(rn3).rank == 10 - - rates = rand(numparams(rn3)) - @test Catalyst.complexbalanced(rn3, rates) == false + + k = rand(rng, numparams(rn3)) + rates = Dict(zip(reactionparams(rn3), k)) + @test Catalyst.iscomplexbalanced(rn3, rates) == false # i=0; # for lcs in linkageclasses(rn3) # i=i+1 @@ -141,6 +146,18 @@ let # end end +let + rn4 = @reaction_network begin + (k1, k2), C1 <--> C2 + (k3, k4), C2 <--> C3 + (k5, k6), C3 <--> C1 + end + + k = rand(rng, numparams(rn4)) + rates = Dict(zip(reactionparams(rn4), k)) + @test Catalyst.iscomplexbalanced(rn4, rates) == true +end + ### Tests Reversibility ### # Test function. @@ -163,7 +180,12 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end + let rn = @reaction_network begin (k2, k1), A1 <--> A2 + A3 @@ -176,6 +198,10 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end let rn = @reaction_network begin @@ -185,6 +211,9 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end let rn = @reaction_network begin @@ -195,6 +224,10 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end let rn = @reaction_network begin @@ -206,6 +239,11 @@ let rev = false weak_rev = true testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + # Breaks when a reaction has multiple rates + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + # @test Catalyst.iscomplexbalanced(rn, rates) == true end let rn = @reaction_network begin @@ -215,6 +253,10 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end let rn = @reaction_network begin @@ -224,12 +266,20 @@ let rev = true weak_rev = true testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == true end let rn = @reaction_network begin (k2, k1), A + B <--> 2A end rev = true weak_rev = true testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == true end let rn = @reaction_network begin @@ -241,6 +291,10 @@ let rev = false weak_rev = true testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == true end let rn = @reaction_network begin @@ -252,4 +306,8 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end \ No newline at end of file From 07a7e2176673982f0226699a00ae8a82698b783b Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 23 May 2024 09:44:58 -0400 Subject: [PATCH 051/446] some edits --- src/networkapi.jl | 57 ++++++++--------------------------------------- 1 file changed, 9 insertions(+), 48 deletions(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index bf9e31a3dc..08c01885bd 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1653,6 +1653,9 @@ function validate(rs::ReactionSystem, info::String = "") validated end +# Checks if a unit consist of exponents with base 1 (and is this unitless). +unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) + """ iscomplexbalanced(rs::ReactionSystem, rates::Vector) @@ -1696,44 +1699,14 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) @assert isapprox(L*ρ, zeros(nc), atol=1e-12) # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T - if all(x -> x > 0, ρ) + if all(>(0), ρ) img = D'*log.(ρ) - if rank(S') == rank(hcat(S', img)) return true else return false end + rank(S') == rank(hcat(S', img)) ? return true : return false else return false end end -# """ -# rateweightedgraph(rs::ReactionSystem, rates::Vector) -# -# Generate an annotated reaction complex graph of a reaction system, where the nodes are annotated with the reaction complex they correspond to and the edges are annotated with the reaction they correspond to and the rate of the reaction. -# """ -# -# function rateweightedgraph(rs::ReactionSystem, rates::Dict{Any, Float64}) -# if length(rates) != numparams(rs) -# error("The number of reaction rates must be equal to the number of parameters") -# end -# -# complexes, D = reactioncomplexes(rs) -# rxns = reactions(rs) -# -# g = incidencematgraph(rs) -# rwg = MetaDiGraph(g) -# -# for v in vertices(rwg) -# set_prop!(rwg, v, :complex, complexes[v]) -# end -# -# for (i, e) in collect(enumerate(edges(rwg))) -# rxn = rxns[i] -# set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :reaction, rxn) -# set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :rate, rates[rxn.rate]) -# end -# -# rwg -# end - function ratematrix(rs::ReactionSystem, rates::Dict{Any, Float64}) if length(rates) != numparams(rs) error("The number of reaction rates must be equal to the number of parameters") @@ -1746,8 +1719,8 @@ function ratematrix(rs::ReactionSystem, rates::Dict{Any, Float64}) for r in 1:length(rxns) rxn = rxns[r] - s = findfirst(x->x==-1, D[:,r]) - p = findfirst(x->x==1, D[:,r]) + s = findfirst(==(-1), @view D[:,r]) + p = findfirst(==(1), @view D[:,r]) ratematrix[s, p] = rates[rxn.rate] end ratematrix @@ -1781,7 +1754,7 @@ function matrixtree(g::SimpleDiGraph, distmx::Matrix) trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) # trees = spanningtrees(g) - # constructed rooted trees for every edge, compute sum + # constructed rooted trees for every vertex, compute sum for v in 1:n rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir=:in)) for t in trees] π[v] = sum([treeweight(t, g, distmx) for t in rootedTrees]) @@ -1805,16 +1778,4 @@ function spanningtrees(g::SimpleGraph) end -# Checks if a unit consist of exponents with base 1 (and is this unitless). -unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) - - rxn = reactions(rs)[rxn_idx] - sm = speciesmap(rs) - rate = rxn.rate - - species = rxn.substrates - stoich = rxn.substoich - species_idx = [sm[s] for s in species] - - dir * rate * prod(conc[species_idx].^stoich) -end + sm = speciesmap(rs) \ No newline at end of file From a0c455531769e38d182f7d65b050e5f557ed6f2f Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 23 May 2024 09:54:55 -0400 Subject: [PATCH 052/446] some edits --- src/networkapi.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index 08c01885bd..02cc43983d 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1663,8 +1663,6 @@ Constructively compute whether a network will have complex-balanced equilibrium solutions, following the method in [this paper](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a map of rates [k1 => 1.0, k2 => 2.0,...]k2 """ -using Combinatorics, LinearAlgebra - function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) if length(rates) != numparams(rs) error("The number of reaction rates must be equal to the number of parameters") @@ -1694,7 +1692,8 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) S = netstoichmat(rs) # Compute ρ using the matrix-tree theorem - g = incidencematgraph(rs); R = ratematrix(rs, rates) + g = incidencematgraph(rs) + R = ratematrix(rs, rates) ρ = matrixtree(g, R) @assert isapprox(L*ρ, zeros(nc), atol=1e-12) From f091b19df12c60b2cb4dff2b0acff7401750c879 Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Thu, 23 May 2024 09:55:08 -0400 Subject: [PATCH 053/446] error update Co-authored-by: Sam Isaacson --- src/networkapi.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index 02cc43983d..db17a28924 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1674,7 +1674,9 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) rxns = reactions(rs) nc = length(complexes); nr = numreactions(rs); nm = numspecies(rs) - if !all(r->ismassaction(r, rs), rxns) error("Not mass action") end + if !all(r->ismassaction(r, rs), rxns) + error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.") + end # Construct kinetic matrix, K K = zeros(nr, nc) From dba37ea2bb4fc23cb8494435c8e39e11a7b8d80f Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 23 May 2024 09:59:25 -0400 Subject: [PATCH 054/446] added deps --- src/Catalyst.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index d80c115119..f8276c614d 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -6,6 +6,7 @@ module Catalyst using DocStringExtensions using SparseArrays, DiffEqBase, Reexport, Setfield using LaTeXStrings, Latexify, Requires +using LinearAlgebra, Combinatorics using JumpProcesses: JumpProcesses, JumpProblem, MassActionJump, ConstantRateJump, VariableRateJump From 916e0888b26450c584116fb33d15d63659fe5527 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 23 May 2024 14:47:05 -0400 Subject: [PATCH 055/446] retooling input map --- src/networkapi.jl | 78 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index db17a28924..9a2278b85a 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1657,17 +1657,20 @@ end unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) """ - iscomplexbalanced(rs::ReactionSystem, rates::Vector) + iscomplexbalanced(rs::ReactionSystem, parametermap) Constructively compute whether a network will have complex-balanced equilibrium -solutions, following the method in [this paper](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a map of rates [k1 => 1.0, k2 => 2.0,...]k2 +solutions, following the method in van der Schaft et al., [2015](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a dictionary, vector, or tuple ofvariable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...]. """ -function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) - if length(rates) != numparams(rs) - error("The number of reaction rates must be equal to the number of parameters") +function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) + if length(parametermap) != numparams(rs) + error("Incorrect number of parameters specified.") end + pmap = symmap_to_varmap(rs, parametermap) + pmap = Dict(ModelingToolkit.value(k) => v for (k,v) in pmap) + sm = speciesmap(rs) cm = reactioncomplexmap(rs) complexes, D = reactioncomplexes(rs) @@ -1678,6 +1681,8 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.") end + rates = [substitute(rate, pmap) for rate in reactionrates(rs)] + # Construct kinetic matrix, K K = zeros(nr, nc) for c in 1:nc @@ -1685,7 +1690,7 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) for (r, dir) in cm[complex] rxn = rxns[r] if dir == -1 - K[r, c] = rates[rxn.rate] + K[r, c] = rates[r] end end end @@ -1697,22 +1702,36 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) g = incidencematgraph(rs) R = ratematrix(rs, rates) ρ = matrixtree(g, R) - @assert isapprox(L*ρ, zeros(nc), atol=1e-12) # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T if all(>(0), ρ) img = D'*log.(ρ) - rank(S') == rank(hcat(S', img)) ? return true : return false + if rank(S') == rank(hcat(S', img)) return true else return false end else return false end end -function ratematrix(rs::ReactionSystem, rates::Dict{Any, Float64}) - if length(rates) != numparams(rs) - error("The number of reaction rates must be equal to the number of parameters") - end +function iscomplexbalanced(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + iscomplexbalanced(rs, pdict) +end + +function iscomplexbalanced(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + iscomplexbalanced(rs, pdict) +end +iscomplexbalanced(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") + + +""" + ratematrix(rs::ReactionSystem, parametermap) + + Given a reaction system with n complexes, outputs an n-by-n matrix where R_{ij} is the rate constant of the reaction between complex i and complex j. +""" + +function ratematrix(rs::ReactionSystem, rates::Vector{Float64}) complexes, D = reactioncomplexes(rs) n = length(complexes) rxns = reactions(rs) @@ -1722,13 +1741,34 @@ function ratematrix(rs::ReactionSystem, rates::Dict{Any, Float64}) rxn = rxns[r] s = findfirst(==(-1), @view D[:,r]) p = findfirst(==(1), @view D[:,r]) - ratematrix[s, p] = rates[rxn.rate] + ratematrix[s, p] = rates[r] end ratematrix end -""" -""" +function ratematrix(rs::ReactionSystem, parametermap::Dict{Symbol, Float64}) + if length(parametermap) != numparams(rs) + error("The number of reaction rates must be equal to the number of parameters") + end + pmap = symmap_to_varmap(rs, parametermap) + rates = [substitute(rate, pmap) for rate in reactionrates(rs)] + ratematrix(rs, rates) +end + +function ratematrix(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + ratematrix(rs, pdict) +end + +function ratematrix(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + ratematrix(rs, pdict) +end + +ratematrix(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") + +### BELOW: Helper functions for iscomplexbalanced + function matrixtree(g::SimpleDiGraph, distmx::Matrix) n = nv(g) if size(distmx) != (n, n) @@ -1753,7 +1793,6 @@ function matrixtree(g::SimpleDiGraph, distmx::Matrix) trees = collect(Combinatorics.combinations(collect(edges(ug)), n-1)) trees = SimpleGraph.(trees) trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) - # trees = spanningtrees(g) # constructed rooted trees for every vertex, compute sum for v in 1:n @@ -1773,10 +1812,3 @@ function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) end prod end - -# TODO: implement Winter's algorithm for generating spanning trees -function spanningtrees(g::SimpleGraph) - -end - - sm = speciesmap(rs) \ No newline at end of file From 4dc7fbacf9fc3ae4e6e58ef69f6ec13d8c947dd5 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 23 May 2024 14:49:54 -0400 Subject: [PATCH 056/446] updated test file --- test/network_analysis/network_properties.jl | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/test/network_analysis/network_properties.jl b/test/network_analysis/network_properties.jl index 35f5b40c2a..a716d04c36 100644 --- a/test/network_analysis/network_properties.jl +++ b/test/network_analysis/network_properties.jl @@ -232,7 +232,7 @@ end let rn = @reaction_network begin (k2, k1), A <--> 2B - (k4, k3), A + C --> D + (k4, k3), A + C <--> D k5, D --> B + E k6, B + E --> A + C end @@ -240,10 +240,9 @@ let weak_rev = true testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) - # Breaks when a reaction has multiple rates k = rand(rng, numparams(rn)) rates = Dict(zip(reactionparams(rn), k)) - # @test Catalyst.iscomplexbalanced(rn, rates) == true + @test Catalyst.iscomplexbalanced(rn, rates) == true end let rn = @reaction_network begin @@ -310,4 +309,20 @@ let k = rand(rng, numparams(rn)) rates = Dict(zip(reactionparams(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false -end \ No newline at end of file +end + +let + rn = @reaction_network begin + k1, 3A + 2B --> 3C + k2, B + 4D --> 2E + k3, 2E --> 3C + (k4, k5), B + 4D <--> 3A + 2B + k6, F --> B + 4D + k7, 3C --> F + end + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == true +end + From c59a839f6c235755f12e5409ed1292915ce73098 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 10:26:50 -0400 Subject: [PATCH 057/446] added networkanalysis --- src/network_analysis.jl | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index cc33f9b63e..7dbec6f11c 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -718,3 +718,54 @@ function conservationlaw_errorcheck(rs, pre_varmap) isempty(conservedequations(Catalyst.flatten(rs))) || error("The system has conservation laws but initial conditions were not provided for some species.") end + +""" + iscomplexbalanced(rs::ReactionSystem, parametermap) + +Constructively compute whether a network will have complex-balanced equilibrium +solutions, following the method in van der Schaft et al., [2015](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a dictionary, vector, or tuple ofvariable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...]. +""" + +function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) + if length(parametermap) != numparams(rs) + error("Incorrect number of parameters specified.") + end + + pmap = symmap_to_varmap(rs, parametermap) + pmap = Dict(ModelingToolkit.value(k) => v for (k,v) in pmap) + + sm = speciesmap(rs) + cm = reactioncomplexmap(rs) + complexes, D = reactioncomplexes(rs) + rxns = reactions(rs) + nc = length(complexes); nr = numreactions(rs); nm = numspecies(rs) + + if !all(r->ismassaction(r, rs), rxns) + error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.") + end + + rates = [substitute(rate, pmap) for rate in reactionrates(rs)] + + # Construct kinetic matrix, K + K = zeros(nr, nc) + for c in 1:nc + complex = complexes[c] + for (r, dir) in cm[complex] + rxn = rxns[r] + if dir == -1 + K[r, c] = rates[r] + end + end + end + + L = -D*K + S = netstoichmat(rs) + + # Compute ρ using the matrix-tree theorem + g = incidencematgraph(rs) + R = ratematrix(rs, rates) + ρ = matrixtree(g, R) + + # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T + if all(>(0), ρ) + img = D'*log.(ρ) From 3cac6cdf8e0d1904e901793b03e199c683bd3317 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 10:32:47 -0400 Subject: [PATCH 058/446] up --- src/networkapi.jl | 156 ---------------------------------------------- 1 file changed, 156 deletions(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index 9a2278b85a..ac4e76b6ee 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1656,159 +1656,3 @@ end # Checks if a unit consist of exponents with base 1 (and is this unitless). unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) -""" - iscomplexbalanced(rs::ReactionSystem, parametermap) - -Constructively compute whether a network will have complex-balanced equilibrium -solutions, following the method in van der Schaft et al., [2015](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a dictionary, vector, or tuple ofvariable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...]. -""" - -function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) - if length(parametermap) != numparams(rs) - error("Incorrect number of parameters specified.") - end - - pmap = symmap_to_varmap(rs, parametermap) - pmap = Dict(ModelingToolkit.value(k) => v for (k,v) in pmap) - - sm = speciesmap(rs) - cm = reactioncomplexmap(rs) - complexes, D = reactioncomplexes(rs) - rxns = reactions(rs) - nc = length(complexes); nr = numreactions(rs); nm = numspecies(rs) - - if !all(r->ismassaction(r, rs), rxns) - error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.") - end - - rates = [substitute(rate, pmap) for rate in reactionrates(rs)] - - # Construct kinetic matrix, K - K = zeros(nr, nc) - for c in 1:nc - complex = complexes[c] - for (r, dir) in cm[complex] - rxn = rxns[r] - if dir == -1 - K[r, c] = rates[r] - end - end - end - - L = -D*K - S = netstoichmat(rs) - - # Compute ρ using the matrix-tree theorem - g = incidencematgraph(rs) - R = ratematrix(rs, rates) - ρ = matrixtree(g, R) - - # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T - if all(>(0), ρ) - img = D'*log.(ρ) - if rank(S') == rank(hcat(S', img)) return true else return false end - else - return false - end -end - -function iscomplexbalanced(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) - pdict = Dict(parametermap) - iscomplexbalanced(rs, pdict) -end - -function iscomplexbalanced(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) - pdict = Dict(parametermap) - iscomplexbalanced(rs, pdict) -end - -iscomplexbalanced(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") - - -""" - ratematrix(rs::ReactionSystem, parametermap) - - Given a reaction system with n complexes, outputs an n-by-n matrix where R_{ij} is the rate constant of the reaction between complex i and complex j. -""" - -function ratematrix(rs::ReactionSystem, rates::Vector{Float64}) - complexes, D = reactioncomplexes(rs) - n = length(complexes) - rxns = reactions(rs) - ratematrix = zeros(n, n) - - for r in 1:length(rxns) - rxn = rxns[r] - s = findfirst(==(-1), @view D[:,r]) - p = findfirst(==(1), @view D[:,r]) - ratematrix[s, p] = rates[r] - end - ratematrix -end - -function ratematrix(rs::ReactionSystem, parametermap::Dict{Symbol, Float64}) - if length(parametermap) != numparams(rs) - error("The number of reaction rates must be equal to the number of parameters") - end - pmap = symmap_to_varmap(rs, parametermap) - rates = [substitute(rate, pmap) for rate in reactionrates(rs)] - ratematrix(rs, rates) -end - -function ratematrix(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) - pdict = Dict(parametermap) - ratematrix(rs, pdict) -end - -function ratematrix(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) - pdict = Dict(parametermap) - ratematrix(rs, pdict) -end - -ratematrix(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") - -### BELOW: Helper functions for iscomplexbalanced - -function matrixtree(g::SimpleDiGraph, distmx::Matrix) - n = nv(g) - if size(distmx) != (n, n) - error("Size of distance matrix is incorrect") - end - - π = zeros(n) - - if !Graphs.is_connected(g) - ccs = Graphs.connected_components(g) - for cc in ccs - sg, vmap = Graphs.induced_subgraph(g, cc) - distmx_s = distmx[cc, cc] - π_j = matrixtree(sg, distmx_s) - π[cc] = π_j - end - return π - end - - # generate all spanning trees - ug = SimpleGraph(SimpleDiGraph(g)) - trees = collect(Combinatorics.combinations(collect(edges(ug)), n-1)) - trees = SimpleGraph.(trees) - trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) - - # constructed rooted trees for every vertex, compute sum - for v in 1:n - rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir=:in)) for t in trees] - π[v] = sum([treeweight(t, g, distmx) for t in rootedTrees]) - end - - # sum the contributions - return π -end - -function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) - prod = 1 - for e in edges(t) - s = Graphs.src(e); t = Graphs.dst(e) - prod *= distmx[s, t] - end - prod -end From 7e6b4f6ba3aa64f1dd7bd36d07ea0dd705698e74 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 10:35:53 -0400 Subject: [PATCH 059/446] added network_analysis --- src/network_analysis.jl | 107 +++ src/networkapi.jl | 1658 --------------------------------------- 2 files changed, 107 insertions(+), 1658 deletions(-) delete mode 100644 src/networkapi.jl diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 7dbec6f11c..4201404602 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -769,3 +769,110 @@ function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T if all(>(0), ρ) img = D'*log.(ρ) + if rank(S') == rank(hcat(S', img)) return true else return false end + else + return false + end +end + +function iscomplexbalanced(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + iscomplexbalanced(rs, pdict) +end + +function iscomplexbalanced(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + iscomplexbalanced(rs, pdict) +end + +iscomplexbalanced(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") + + +""" + ratematrix(rs::ReactionSystem, parametermap) + + Given a reaction system with n complexes, outputs an n-by-n matrix where R_{ij} is the rate constant of the reaction between complex i and complex j. +""" + +function ratematrix(rs::ReactionSystem, rates::Vector{Float64}) + complexes, D = reactioncomplexes(rs) + n = length(complexes) + rxns = reactions(rs) + ratematrix = zeros(n, n) + + for r in 1:length(rxns) + rxn = rxns[r] + s = findfirst(==(-1), @view D[:,r]) + p = findfirst(==(1), @view D[:,r]) + ratematrix[s, p] = rates[r] + end + ratematrix +end + +function ratematrix(rs::ReactionSystem, parametermap::Dict{Symbol, Float64}) + if length(parametermap) != numparams(rs) + error("The number of reaction rates must be equal to the number of parameters") + end + pmap = symmap_to_varmap(rs, parametermap) + rates = [substitute(rate, pmap) for rate in reactionrates(rs)] + ratematrix(rs, rates) +end + +function ratematrix(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + ratematrix(rs, pdict) +end + +function ratematrix(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + ratematrix(rs, pdict) +end + +ratematrix(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") + +### BELOW: Helper functions for iscomplexbalanced + +function matrixtree(g::SimpleDiGraph, distmx::Matrix) + n = nv(g) + if size(distmx) != (n, n) + error("Size of distance matrix is incorrect") + end + + π = zeros(n) + + if !Graphs.is_connected(g) + ccs = Graphs.connected_components(g) + for cc in ccs + sg, vmap = Graphs.induced_subgraph(g, cc) + distmx_s = distmx[cc, cc] + π_j = matrixtree(sg, distmx_s) + π[cc] = π_j + end + return π + end + + # generate all spanning trees + ug = SimpleGraph(SimpleDiGraph(g)) + trees = collect(Combinatorics.combinations(collect(edges(ug)), n-1)) + trees = SimpleGraph.(trees) + trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) + + # constructed rooted trees for every vertex, compute sum + for v in 1:n + rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir=:in)) for t in trees] + π[v] = sum([treeweight(t, g, distmx) for t in rootedTrees]) + end + + # sum the contributions + return π +end + +function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) + prod = 1 + for e in edges(t) + s = Graphs.src(e); t = Graphs.dst(e) + prod *= distmx[s, t] + end + prod +end + diff --git a/src/networkapi.jl b/src/networkapi.jl deleted file mode 100644 index ac4e76b6ee..0000000000 --- a/src/networkapi.jl +++ /dev/null @@ -1,1658 +0,0 @@ - -######### Accessors: ######### - -function filter_nonrxsys(network) - systems = get_systems(network) - rxsystems = ReactionSystem[] - for sys in systems - (sys isa ReactionSystem) && push!(rxsystems, sys) - end - rxsystems -end - -function species(network::ReactionSystem, sts) - [MT.renamespace(network, st) for st in sts] -end - -""" - species(network) - -Given a [`ReactionSystem`](@ref), return a vector of all species defined in the system and -any subsystems that are of type `ReactionSystem`. To get the species and non-species -variables in the system and all subsystems, including non-`ReactionSystem` subsystems, uses -`states(network)`. - -Notes: -- If `ModelingToolkit.get_systems(network)` is non-empty will allocate. -""" -function species(network) - sts = get_species(network) - systems = filter_nonrxsys(network) - isempty(systems) && return sts - unique([sts; reduce(vcat, map(sys -> species(sys, species(sys)), systems))]) -end - -""" - nonspecies(network) - -Return the non-species variables within the network, i.e. those states for which `isspecies -== false`. - -Notes: -- Allocates a new array to store the non-species variables. -""" -function nonspecies(network) - states(network)[(numspecies(network) + 1):end] -end - -""" - reactionparams(network) - -Given a [`ReactionSystem`](@ref), return a vector of all parameters defined -within the system and any subsystems that are of type `ReactionSystem`. To get -the parameters in the system and all subsystems, including non-`ReactionSystem` -subsystems, use `parameters(network)`. - -Notes: -- Allocates and has to calculate these dynamically by comparison for each reaction. -""" -function reactionparams(network) - ps = get_ps(network) - systems = filter_nonrxsys(network) - isempty(systems) && return ps - unique([ps; reduce(vcat, map(sys -> species(sys, reactionparams(sys)), systems))]) -end - -""" - numparams(network) - -Return the total number of parameters within the given system and all subsystems. -""" -function numparams(network) - nps = length(get_ps(network)) - for sys in get_systems(network) - nps += numparams(sys) - end - nps -end - -function namespace_reactions(network::ReactionSystem) - rxs = reactions(network) - isempty(rxs) && return Reaction[] - map(rx -> namespace_equation(rx, network), rxs) -end - -""" - reactions(network) - -Given a [`ReactionSystem`](@ref), return a vector of all `Reactions` in the system. - -Notes: -- If `ModelingToolkit.get_systems(network)` is not empty, will allocate. -""" -function reactions(network) - rxs = get_rxs(network) - systems = filter_nonrxsys(network) - isempty(systems) && (return rxs) - [rxs; reduce(vcat, namespace_reactions.(systems); init = Reaction[])] -end - -""" - speciesmap(network) - -Given a [`ReactionSystem`](@ref), return a Dictionary mapping species that -participate in `Reaction`s to their index within [`species(network)`](@ref). -""" -function speciesmap(network) - nps = get_networkproperties(network) - if isempty(nps.speciesmap) - nps.speciesmap = Dict(S => i for (i, S) in enumerate(species(network))) - end - nps.speciesmap -end - -""" - paramsmap(network) - -Given a [`ReactionSystem`](@ref), return a Dictionary mapping from all -parameters that appear within the system to their index within -`parameters(network)`. -""" -function paramsmap(network) - Dict(p => i for (i, p) in enumerate(parameters(network))) -end - -""" - reactionparamsmap(network) - -Given a [`ReactionSystem`](@ref), return a Dictionary mapping from parameters that -appear within `Reaction`s to their index within [`reactionparams(network)`](@ref). -""" -function reactionparamsmap(network) - Dict(p => i for (i, p) in enumerate(reactionparams(network))) -end - -""" - numspecies(network) - -Return the total number of species within the given [`ReactionSystem`](@ref) and -subsystems that are `ReactionSystem`s. -""" -function numspecies(network) - numspcs = length(get_species(network)) - for sys in get_systems(network) - (sys isa ReactionSystem) && (numspcs += numspecies(sys)) - end - numspcs -end - -""" - numreactions(network) - -Return the total number of reactions within the given [`ReactionSystem`](@ref) -and subsystems that are `ReactionSystem`s. -""" -function numreactions(network) - nr = length(get_rxs(network)) - for sys in get_systems(network) - (sys isa ReactionSystem) && (nr += numreactions(sys)) - end - nr -end - -""" - numreactionparams(network) - -Return the total number of parameters within the given [`ReactionSystem`](@ref) -and subsystems that are `ReactionSystem`s. - -Notes -- If there are no subsystems this will be fast. -- As this calls [`reactionparams`](@ref), it can be slow and will allocate if - there are any subsystems. -""" -function numreactionparams(network) - length(reactionparams(network)) -end - -""" - dependents(rx, network) - -Given a [`Reaction`](@ref) and a [`ReactionSystem`](@ref), return a vector of the -*non-constant* species and variables the reaction rate law depends on. e.g., for - -`k*W, 2X + 3Y --> 5Z + W` - -the returned vector would be `[W(t),X(t),Y(t)]`. - -Notes: -- Allocates -- Does not check for dependents within any subsystems. -- Constant species are not considered dependents since they are internally treated as - parameters. -- If the rate expression depends on a non-species state variable that will be included in - the dependents, i.e. in - ```julia - @parameters k - @variables t V(t) - @species A(t) B(t) C(t) - rx = Reaction(k*V, [A, B], [C]) - @named rs = ReactionSystem([rx], t) - issetequal(dependents(rx, rs), [A,B,V]) == true - ``` -""" -function dependents(rx, network) - if rx.rate isa Number - return rx.substrates - else - rvars = get_variables(rx.rate, states(network)) - return union!(rvars, rx.substrates) - end -end - -""" - dependents(rx, network) - -See documentation for [`dependents`](@ref). -""" -function dependants(rx, network) - dependents(rx, network) -end - -""" - reactionrates(network) - -Given a [`ReactionSystem`](@ref), returns a vector of the symbolic reaction -rates for each reaction. -""" -function reactionrates(rn) - [r.rate for r in reactions(rn)] -end - -""" - substoichmat(rn; sparse=false) - -Returns the substrate stoichiometry matrix, ``S``, with ``S_{i j}`` the stoichiometric -coefficient of the ith substrate within the jth reaction. - -Note: -- Set sparse=true for a sparse matrix representation -- Note that constant species are not considered substrates, but just components that modify - the associated rate law. -""" -function substoichmat(::Type{SparseMatrixCSC{T, Int}}, - rn::ReactionSystem) where {T <: Number} - Is = Int[] - Js = Int[] - Vs = T[] - smap = speciesmap(rn) - for (k, rx) in enumerate(reactions(rn)) - stoich = rx.substoich - for (i, sub) in enumerate(rx.substrates) - isconstant(sub) && continue - push!(Js, k) - push!(Is, smap[sub]) - push!(Vs, stoich[i]) - end - end - sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) -end -function substoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} - smap = speciesmap(rn) - smat = zeros(T, numspecies(rn), numreactions(rn)) - for (k, rx) in enumerate(reactions(rn)) - stoich = rx.substoich - for (i, sub) in enumerate(rx.substrates) - isconstant(sub) && continue - smat[smap[sub], k] = stoich[i] - end - end - smat -end -function substoichmat(rn::ReactionSystem; sparse::Bool = false) - isempty(get_systems(rn)) || error("substoichmat does not currently support subsystems.") - T = reduce(promote_type, eltype(rx.substoich) for rx in reactions(rn)) - (T == Any) && - error("Stoichiometry matrices with symbolic stoichiometry are not supported") - sparse ? substoichmat(SparseMatrixCSC{T, Int}, rn) : substoichmat(Matrix{T}, rn) -end - -""" - prodstoichmat(rn; sparse=false) - -Returns the product stoichiometry matrix, ``P``, with ``P_{i j}`` the stoichiometric -coefficient of the ith product within the jth reaction. - -Note: -- Set sparse=true for a sparse matrix representation -- Note that constant species are not treated as products, but just components that modify - the associated rate law. -""" -function prodstoichmat(::Type{SparseMatrixCSC{T, Int}}, - rn::ReactionSystem) where {T <: Number} - Is = Int[] - Js = Int[] - Vs = T[] - smap = speciesmap(rn) - for (k, rx) in enumerate(reactions(rn)) - stoich = rx.prodstoich - for (i, prod) in enumerate(rx.products) - isconstant(prod) && continue - push!(Js, k) - push!(Is, smap[prod]) - push!(Vs, stoich[i]) - end - end - sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) -end -function prodstoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} - smap = speciesmap(rn) - pmat = zeros(T, numspecies(rn), numreactions(rn)) - for (k, rx) in enumerate(reactions(rn)) - stoich = rx.prodstoich - for (i, prod) in enumerate(rx.products) - isconstant(prod) && continue - pmat[smap[prod], k] = stoich[i] - end - end - pmat -end -function prodstoichmat(rn::ReactionSystem; sparse = false) - isempty(get_systems(rn)) || - error("prodstoichmat does not currently support subsystems.") - - T = reduce(promote_type, eltype(rx.prodstoich) for rx in reactions(rn)) - (T == Any) && - error("Stoichiometry matrices with symbolic stoichiometry are not supported") - sparse ? prodstoichmat(SparseMatrixCSC{T, Int}, rn) : prodstoichmat(Matrix{T}, rn) -end - -""" - netstoichmat(rn, sparse=false) - -Returns the net stoichiometry matrix, ``N``, with ``N_{i j}`` the net stoichiometric -coefficient of the ith species within the jth reaction. - -Notes: -- Set sparse=true for a sparse matrix representation -- Caches the matrix internally within `rn` so subsequent calls are fast. -- Note that constant species are not treated as reactants, but just components that modify - the associated rate law. As such they do not contribute to the net stoichiometry matrix. -""" -function netstoichmat(::Type{SparseMatrixCSC{T, Int}}, - rn::ReactionSystem) where {T <: Number} - Is = Int[] - Js = Int[] - Vs = Vector{T}() - smap = speciesmap(rn) - for (k, rx) in pairs(reactions(rn)) - for (spec, coef) in rx.netstoich - isconstant(spec) && continue - push!(Js, k) - push!(Is, smap[spec]) - push!(Vs, coef) - end - end - sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) -end -function netstoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} - smap = speciesmap(rn) - nmat = zeros(T, numspecies(rn), numreactions(rn)) - for (k, rx) in pairs(reactions(rn)) - for (spec, coef) in rx.netstoich - isconstant(spec) && continue - nmat[smap[spec], k] = coef - end - end - nmat -end - -netstoichtype(::Vector{Pair{S, T}}) where {S, T} = T - -function netstoichmat(rn::ReactionSystem; sparse = false) - isempty(get_systems(rn)) || - error("netstoichmat does not currently support subsystems, please create a flattened system before calling.") - - nps = get_networkproperties(rn) - - # if it is already calculated and has the right type - !isempty(nps.netstoichmat) && (sparse == issparse(nps.netstoichmat)) && - (return nps.netstoichmat) - - # identify a common stoichiometry type - T = reduce(promote_type, netstoichtype(rx.netstoich) for rx in reactions(rn)) - (T == Any) && - error("Stoichiometry matrices are not supported with symbolic stoichiometry.") - - if sparse - nsmat = netstoichmat(SparseMatrixCSC{T, Int}, rn) - else - nsmat = netstoichmat(Matrix{T}, rn) - end - - # only cache if it is integer - if T == Int - nps.netstoichmat = nsmat - end - - nsmat -end - -# the following function is adapted from SymbolicUtils.jl v.19 -# later on (Spetember 2023) modified by Torkel and Shashi (now assumes input not on polynomial form, which is handled elsewhere, previous version failed in these cases anyway). -# Copyright (c) 2020: Shashi Gowda, Yingbo Ma, Mason Protter, Julia Computing. -# MIT license -""" - to_multivariate_poly(polyeqs::AbstractVector{BasicSymbolic{Real}}) - -Convert the given system of polynomial equations to multivariate polynomial representation. -For example, this can be used in HomotopyContinuation.jl functions. -""" -function to_multivariate_poly(polyeqs::AbstractVector{Symbolics.BasicSymbolic{Real}}) - @assert length(polyeqs)>=1 "At least one expression must be passed to `multivariate_poly`." - - pvar2sym, sym2term = SymbolicUtils.get_pvar2sym(), SymbolicUtils.get_sym2term() - ps = map(polyeqs) do x - if istree(x) && operation(x) == (/) - error("We should not be able to get here, please contact the package authors.") - else - PolyForm(x, pvar2sym, sym2term).p - end - end - - ps -end -""" - setdefaults!(rn, newdefs) - -Sets the default (initial) values of parameters and species in the -`ReactionSystem`, `rn`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -setdefaults!(sir, [:S => 999.0, :I => 1.0, :R => 1.0, :β => 1e-4, :ν => .01]) - -# or -@parameter β ν -@variables t -@species S(t) I(t) R(t) -setdefaults!(sir, [S => 999.0, I => 1.0, R => 0.0, β => 1e-4, ν => .01]) -``` -gives initial/default values to each of `S`, `I` and `β` - -Notes: -- Can not be used to set default values for species, variables or parameters of - subsystems or constraint systems. Either set defaults for those systems - directly, or [`flatten`](@ref) to collate them into one system before setting - defaults. -- Defaults can be specified in any iterable container of symbols to value pairs - or symbolics to value pairs. -""" -function setdefaults!(rn, newdefs) - defs = eltype(newdefs) <: Pair{Symbol} ? symmap_to_varmap(rn, newdefs) : newdefs - rndefs = MT.get_defaults(rn) - for (var, val) in defs - rndefs[value(var)] = value(val) - end - nothing -end - -function __unpacksys(rn) - ex = :(begin end) - for key in keys(get_var_to_name(rn)) - var = MT.getproperty(rn, key, namespace = false) - push!(ex.args, :($key = $var)) - end - ex -end - -""" - @unpacksys sys::ModelingToolkit.AbstractSystem - -Loads all species, variables, parameters, and observables defined in `sys` as -variables within the calling module. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -@unpacksys sir -``` -will load the symbolic variables, `S`, `I`, `R`, `ν` and `β`. - -Notes: -- Can not be used to load species, variables, or parameters of subsystems or - constraints. Either call `@unpacksys` on those systems directly, or - [`flatten`](@ref) to collate them into one system before calling. -- Note that this places symbolic variables within the calling module's scope, so - calling from a function defined in a script or the REPL will still result in - the symbolic variables being defined in the `Main` module. -""" -macro unpacksys(rn) - quote - ex = Catalyst.__unpacksys($(esc(rn))) - Base.eval($(__module__), ex) - end -end - -# convert symbol of the form :sys.a.b.c to a symbolic a.b.c -function _symbol_to_var(sys, sym) - if hasproperty(sys, sym) - var = getproperty(sys, sym, namespace = false) - else - strs = split(String(sym), "₊") # need to check if this should be split of not!!! - if length(strs) > 1 - var = getproperty(sys, Symbol(strs[1]), namespace = false) - for str in view(strs, 2:length(strs)) - var = getproperty(var, Symbol(str), namespace = true) - end - else - throw(ArgumentError("System $(nameof(sys)): variable $sym does not exist")) - end - end - var -end - -""" - symmap_to_varmap(sys, symmap) - -Given a system and map of `Symbol`s to values, generates a map from -corresponding symbolic variables/parameters to the values that can be used to -pass initial conditions and parameter mappings. - -For example, -```julia -sir = @reaction_network sir begin - β, S + I --> 2I - ν, I --> R -end -subsys = @reaction_network subsys begin - k, A --> B -end -@named sys = compose(sir, [subsys]) -``` -gives -``` -Model sys with 3 equations -States (5): - S(t) - I(t) - R(t) - subsys₊A(t) - subsys₊B(t) -Parameters (3): - β - ν - subsys₊k -``` -to specify initial condition and parameter mappings from *symbols* we can use -```julia -symmap = [:S => 1.0, :I => 1.0, :R => 1.0, :subsys₊A => 1.0, :subsys₊B => 1.0] -u0map = symmap_to_varmap(sys, symmap) -pmap = symmap_to_varmap(sys, [:β => 1.0, :ν => 1.0, :subsys₊k => 1.0]) -``` -`u0map` and `pmap` can then be used as input to various problem types. - -Notes: -- Any `Symbol`, `sym`, within `symmap` must be a valid field of `sys`. i.e. - `sys.sym` must be defined. -""" -function symmap_to_varmap(sys, symmap::Tuple) - if all(p -> p isa Pair{Symbol}, symmap) - return ((_symbol_to_var(sys, sym) => val for (sym, val) in symmap)...,) - else # if not all entries map a symbol to value pass through - return symmap - end -end - -function symmap_to_varmap(sys, symmap::AbstractArray{Pair{Symbol, T}}) where {T} - [_symbol_to_var(sys, sym) => val for (sym, val) in symmap] -end - -function symmap_to_varmap(sys, symmap::Dict{Symbol, T}) where {T} - Dict(_symbol_to_var(sys, sym) => val for (sym, val) in symmap) -end - -# don't permute any other types and let varmap_to_vars handle erroring -symmap_to_varmap(sys, symmap) = symmap -#error("symmap_to_varmap requires a Dict, AbstractArray or Tuple to map Symbols to values.") - -######################## reaction complexes and reaction rates ############################### - -""" - reset_networkproperties!(rn::ReactionSystem) - -Clears the cache of various properties (like the netstoichiometry matrix). Use if such -properties need to be recalculated for some reason. -""" -function reset_networkproperties!(rn::ReactionSystem) - reset!(get_networkproperties(rn)) - nothing -end - -# get the species indices and stoichiometry while filtering out constant species. -function filter_constspecs(specs, stoich::AbstractVector{V}, smap) where {V <: Integer} - isempty(specs) && (return Vector{Int}(), Vector{V}()) - - if any(isconstant, specs) - ids = Vector{Int}() - filtered_stoich = Vector{V}() - for (i, s) in enumerate(specs) - if !isconstant(s) - push!(ids, smap[s]) - push!(filtered_stoich, stoich[i]) - end - end - else - ids = map(Base.Fix1(getindex, smap), specs) - filtered_stoich = copy(stoich) - end - ids, filtered_stoich -end - -""" - reactioncomplexmap(rn::ReactionSystem) - -Find each [`ReactionComplex`](@ref) within the specified system, constructing a mapping from -the complex to vectors that indicate which reactions it appears in as substrates and -products. - -Notes: -- Each [`ReactionComplex`](@ref) is mapped to a vector of pairs, with each pair having the - form `reactionidx => ± 1`, where `-1` indicates the complex appears as a substrate and - `+1` as a product in the reaction with integer label `reactionidx`. -- Constant species are ignored as part of a complex. i.e. if species `A` is constant then - the reaction `A + B --> C + D` is considered to consist of the complexes `B` and `C + D`. - Likewise `A --> B` would be treated as the same as `0 --> B`. -""" -function reactioncomplexmap(rn::ReactionSystem) - isempty(get_systems(rn)) || - error("reactioncomplexmap does not currently support subsystems.") - - # check if previously calculated and hence cached - nps = get_networkproperties(rn) - !isempty(nps.complextorxsmap) && return nps.complextorxsmap - complextorxsmap = nps.complextorxsmap - - rxs = reactions(rn) - smap = speciesmap(rn) - numreactions(rn) > 0 || - error("There must be at least one reaction to find reaction complexes.") - for (i, rx) in enumerate(rxs) - subids, substoich = filter_constspecs(rx.substrates, rx.substoich, smap) - subrc = sort!(ReactionComplex(subids, substoich)) - if haskey(complextorxsmap, subrc) - push!(complextorxsmap[subrc], i => -1) - else - complextorxsmap[subrc] = [i => -1] - end - - prodids, prodstoich = filter_constspecs(rx.products, rx.prodstoich, smap) - prodrc = sort!(ReactionComplex(prodids, prodstoich)) - if haskey(complextorxsmap, prodrc) - push!(complextorxsmap[prodrc], i => 1) - else - complextorxsmap[prodrc] = [i => 1] - end - end - complextorxsmap -end - -function reactioncomplexes(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, - complextorxsmap) - complexes = collect(keys(complextorxsmap)) - Is = Int[] - Js = Int[] - Vs = Int[] - for (i, c) in enumerate(complexes) - for (j, σ) in complextorxsmap[c] - push!(Is, i) - push!(Js, j) - push!(Vs, σ) - end - end - B = sparse(Is, Js, Vs, length(complexes), numreactions(rn)) - complexes, B -end -function reactioncomplexes(::Type{Matrix{Int}}, rn::ReactionSystem, complextorxsmap) - complexes = collect(keys(complextorxsmap)) - B = zeros(Int, length(complexes), numreactions(rn)) - for (i, c) in enumerate(complexes) - for (j, σ) in complextorxsmap[c] - B[i, j] = σ - end - end - complexes, B -end - -@doc raw""" - reactioncomplexes(network::ReactionSystem; sparse=false) - -Calculate the reaction complexes and complex incidence matrix for the given -[`ReactionSystem`](@ref). - -Notes: -- returns a pair of a vector of [`ReactionComplex`](@ref)s and the complex incidence matrix. -- An empty [`ReactionComplex`](@ref) denotes the null (∅) state (from reactions like ∅ -> A - or A -> ∅). -- Constant species are ignored in generating a reaction complex. i.e. if A is constant then - A --> B consists of the complexes ∅ and B. -- The complex incidence matrix, ``B``, is number of complexes by number of reactions with -```math -B_{i j} = \begin{cases} --1, &\text{if the i'th complex is the substrate of the j'th reaction},\\ -1, &\text{if the i'th complex is the product of the j'th reaction},\\ -0, &\text{otherwise.} -\end{cases} -``` -- Set sparse=true for a sparse matrix representation of the incidence matrix -""" -function reactioncomplexes(rn::ReactionSystem; sparse = false) - isempty(get_systems(rn)) || - error("reactioncomplexes does not currently support subsystems.") - nps = get_networkproperties(rn) - if isempty(nps.complexes) || (sparse != issparse(nps.complexes)) - complextorxsmap = reactioncomplexmap(rn) - nps.complexes, nps.incidencemat = if sparse - reactioncomplexes(SparseMatrixCSC{Int, Int}, rn, complextorxsmap) - else - reactioncomplexes(Matrix{Int}, rn, complextorxsmap) - end - end - nps.complexes, nps.incidencemat -end - -""" - incidencemat(rn::ReactionSystem; sparse=false) - -Calculate the incidence matrix of `rn`, see [`reactioncomplexes`](@ref). - -Notes: -- Is cached in `rn` so that future calls, assuming the same sparsity, will also be fast. -""" -incidencemat(rn::ReactionSystem; sparse = false) = reactioncomplexes(rn; sparse)[2] - -function complexstoichmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, rcs) - Is = Int[] - Js = Int[] - Vs = Int[] - for (i, rc) in enumerate(rcs) - for rcel in rc - push!(Is, rcel.speciesid) - push!(Js, i) - push!(Vs, rcel.speciesstoich) - end - end - Z = sparse(Is, Js, Vs, numspecies(rn), length(rcs)) -end -function complexstoichmat(::Type{Matrix{Int}}, rn::ReactionSystem, rcs) - Z = zeros(Int, numspecies(rn), length(rcs)) - for (i, rc) in enumerate(rcs) - for rcel in rc - Z[rcel.speciesid, i] = rcel.speciesstoich - end - end - Z -end - -""" - complexstoichmat(network::ReactionSystem; sparse=false) - -Given a [`ReactionSystem`](@ref) and vector of reaction complexes, return a -matrix with positive entries of size number of species by number of complexes, -where the non-zero positive entries in the kth column denote stoichiometric -coefficients of the species participating in the kth reaction complex. - -Notes: -- Set sparse=true for a sparse matrix representation -""" -function complexstoichmat(rn::ReactionSystem; sparse = false) - isempty(get_systems(rn)) || - error("complexstoichmat does not currently support subsystems.") - nps = get_networkproperties(rn) - if isempty(nps.complexstoichmat) || (sparse != issparse(nps.complexstoichmat)) - nps.complexstoichmat = if sparse - complexstoichmat(SparseMatrixCSC{Int, Int}, rn, keys(reactioncomplexmap(rn))) - else - complexstoichmat(Matrix{Int}, rn, keys(reactioncomplexmap(rn))) - end - end - nps.complexstoichmat -end - -function complexoutgoingmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, B) - n = size(B, 2) - rows = rowvals(B) - vals = nonzeros(B) - Is = Int[] - Js = Int[] - Vs = Int[] - sizehint!(Is, div(length(vals), 2)) - sizehint!(Js, div(length(vals), 2)) - sizehint!(Vs, div(length(vals), 2)) - for j in 1:n - for i in nzrange(B, j) - if vals[i] != one(eltype(vals)) - push!(Is, rows[i]) - push!(Js, j) - push!(Vs, vals[i]) - end - end - end - sparse(Is, Js, Vs, size(B, 1), size(B, 2)) -end -function complexoutgoingmat(::Type{Matrix{Int}}, rn::ReactionSystem, B) - Δ = copy(B) - for (I, b) in pairs(Δ) - (b == 1) && (Δ[I] = 0) - end - Δ -end - -@doc raw""" - complexoutgoingmat(network::ReactionSystem; sparse=false) - -Given a [`ReactionSystem`](@ref) and complex incidence matrix, ``B``, return a -matrix of size num of complexes by num of reactions that identifies substrate -complexes. - -Notes: -- The complex outgoing matrix, ``\Delta``, is defined by -```math -\Delta_{i j} = \begin{cases} - = 0, &\text{if } B_{i j} = 1, \\ - = B_{i j}, &\text{otherwise.} -\end{cases} -``` -- Set sparse=true for a sparse matrix representation -""" -function complexoutgoingmat(rn::ReactionSystem; sparse = false) - isempty(get_systems(rn)) || - error("complexoutgoingmat does not currently support subsystems.") - nps = get_networkproperties(rn) - if isempty(nps.complexoutgoingmat) || (sparse != issparse(nps.complexoutgoingmat)) - B = reactioncomplexes(rn, sparse = sparse)[2] - nps.complexoutgoingmat = if sparse - complexoutgoingmat(SparseMatrixCSC{Int, Int}, rn, B) - else - complexoutgoingmat(Matrix{Int}, rn, B) - end - end - nps.complexoutgoingmat -end - -function incidencematgraph(incidencemat::Matrix{Int}) - @assert all(∈([-1, 0, 1]), incidencemat) - n = size(incidencemat, 1) # no. of nodes/complexes - graph = Graphs.DiGraph(n) - for col in eachcol(incidencemat) - src = 0 - dst = 0 - for i in eachindex(col) - (col[i] == -1) && (src = i) - (col[i] == 1) && (dst = i) - (src != 0) && (dst != 0) && break - end - Graphs.add_edge!(graph, src, dst) - end - return graph -end -function incidencematgraph(incidencemat::SparseMatrixCSC{Int, Int}) - @assert all(∈([-1, 0, 1]), incidencemat) - m, n = size(incidencemat) - graph = Graphs.DiGraph(m) - rows = rowvals(incidencemat) - vals = nonzeros(incidencemat) - for j in 1:n - inds = nzrange(incidencemat, j) - row = rows[inds] - val = vals[inds] - if val[1] == -1 - Graphs.add_edge!(graph, row[1], row[2]) - else - Graphs.add_edge!(graph, row[2], row[1]) - end - end - return graph -end - -""" - incidencematgraph(rn::ReactionSystem) - -Construct a directed simple graph where nodes correspond to reaction complexes and directed -edges to reactions converting between two complexes. - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -complexes,incidencemat = reactioncomplexes(sir) -incidencematgraph(sir) -``` -""" -function incidencematgraph(rn::ReactionSystem) - nps = get_networkproperties(rn) - if Graphs.nv(nps.incidencegraph) == 0 - isempty(nps.incidencemat) && - error("Please call reactioncomplexes(rn) first to construct the incidence matrix.") - nps.incidencegraph = incidencematgraph(nps.incidencemat) - end - nps.incidencegraph -end - -linkageclasses(incidencegraph) = Graphs.connected_components(incidencegraph) - -""" - linkageclasses(rn::ReactionSystem) - -Given the incidence graph of a reaction network, return a vector of the -connected components of the graph (i.e. sub-groups of reaction complexes that -are connected in the incidence graph). - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -complexes,incidencemat = reactioncomplexes(sir) -linkageclasses(sir) -``` -gives -```julia -2-element Vector{Vector{Int64}}: - [1, 2] - [3, 4] -``` -""" -function linkageclasses(rn::ReactionSystem) - nps = get_networkproperties(rn) - if isempty(nps.linkageclasses) - nps.linkageclasses = linkageclasses(incidencematgraph(rn)) - end - nps.linkageclasses -end - -@doc raw""" - deficiency(rn::ReactionSystem) - -Calculate the deficiency of a reaction network. - -Here the deficiency, ``\delta``, of a network with ``n`` reaction complexes, -``\ell`` linkage classes and a rank ``s`` stoichiometric matrix is - -```math -\delta = n - \ell - s -``` - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -rcs,incidencemat = reactioncomplexes(sir) -δ = deficiency(sir) -``` -""" -function deficiency(rn::ReactionSystem) - nps = get_networkproperties(rn) - conservationlaws(rn) - r = nps.rank - ig = incidencematgraph(rn) - lc = linkageclasses(rn) - nps.deficiency = Graphs.nv(ig) - length(lc) - r - nps.deficiency -end - -function subnetworkmapping(linkageclass, allrxs, complextorxsmap, p) - rxinds = sort!(collect(Set(rxidx for rcidx in linkageclass - for rxidx in complextorxsmap[rcidx]))) - rxs = allrxs[rxinds] - specset = Set(s for rx in rxs for s in rx.substrates if !isconstant(s)) - for rx in rxs - for product in rx.products - !isconstant(product) && push!(specset, product) - end - end - specs = collect(specset) - newps = Vector{eltype(p)}() - for rx in rxs - Symbolics.get_variables!(newps, rx.rate, p) - end - rxs, specs, newps # reactions and species involved in reactions of subnetwork -end - -""" - subnetworks(rn::ReactionSystem) - -Find subnetworks corresponding to each linkage class of the reaction network. - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -complexes,incidencemat = reactioncomplexes(sir) -subnetworks(sir) -``` -""" -function subnetworks(rs::ReactionSystem) - isempty(get_systems(rs)) || error("subnetworks does not currently support subsystems.") - lcs = linkageclasses(rs) - rxs = reactions(rs) - p = parameters(rs) - t = get_iv(rs) - spatial_ivs = get_sivs(rs) - complextorxsmap = [map(first, rcmap) for rcmap in values(reactioncomplexmap(rs))] - subnetworks = Vector{ReactionSystem}() - for i in 1:length(lcs) - reacs, specs, newps = subnetworkmapping(lcs[i], rxs, complextorxsmap, p) - newname = Symbol(nameof(rs), "_", i) - push!(subnetworks, - ReactionSystem(reacs, t, specs, newps; name = newname, spatial_ivs)) - end - subnetworks -end - -""" - linkagedeficiencies(network::ReactionSystem) - -Calculates the deficiency of each sub-reaction network within `network`. - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -rcs,incidencemat = reactioncomplexes(sir) -linkage_deficiencies = linkagedeficiencies(sir) -``` -""" -function linkagedeficiencies(rs::ReactionSystem) - lcs = linkageclasses(rs) - subnets = subnetworks(rs) - δ = zeros(Int, length(lcs)) - for (i, subnet) in enumerate(subnets) - conservationlaws(subnet) - nps = get_networkproperties(subnet) - δ[i] = length(lcs[i]) - 1 - nps.rank - end - δ -end - -""" - isreversible(rn::ReactionSystem) - -Given a reaction network, returns if the network is reversible or not. - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -rcs,incidencemat = reactioncomplexes(sir) -isreversible(sir) -``` -""" -function isreversible(rn::ReactionSystem) - ig = incidencematgraph(rn) - Graphs.reverse(ig) == ig -end - -""" - isweaklyreversible(rn::ReactionSystem, subnetworks) - -Determine if the reaction network with the given subnetworks is weakly reversible or not. - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -rcs,incidencemat = reactioncomplexes(sir) -subnets = subnetworks(rn) -isweaklyreversible(rn, subnets) -``` -""" -function isweaklyreversible(rn::ReactionSystem, subnets) - im = get_networkproperties(rn).incidencemat - isempty(im) && - error("Error, please call reactioncomplexes(rn::ReactionSystem) to ensure the incidence matrix has been cached.") - sparseig = issparse(im) - for subnet in subnets - nps = get_networkproperties(subnet) - isempty(nps.incidencemat) && reactioncomplexes(subnet; sparse = sparseig) - end - all(Graphs.is_strongly_connected ∘ incidencematgraph, subnets) -end - -############################################################################################ -######################## conservation laws ############################### - -""" - conservedequations(rn::ReactionSystem) - -Calculate symbolic equations from conservation laws, writing dependent variables as -functions of independent variables and the conservation law constants. - -Notes: -- Caches the resulting equations in `rn`, so will be fast on subsequent calls. - -Examples: -```@repl -rn = @reaction_network begin - k, A + B --> C - k2, C --> A + B - end -conservedequations(rn) -``` -gives -``` -2-element Vector{Equation}: - B(t) ~ A(t) + Γ[1] - C(t) ~ Γ[2] - A(t) -``` -""" -function conservedequations(rn::ReactionSystem) - conservationlaws(rn) - nps = get_networkproperties(rn) - nps.conservedeqs -end - -""" - conservationlaw_constants(rn::ReactionSystem) - -Calculate symbolic equations from conservation laws, writing the conservation law constants -in terms of the dependent and independent variables. - -Notes: -- Caches the resulting equations in `rn`, so will be fast on subsequent calls. - -Examples: -```@julia -rn = @reaction_network begin - k, A + B --> C - k2, C --> A + B - end -conservationlaw_constants(rn) -``` -gives -``` -2-element Vector{Equation}: - Γ[1] ~ B(t) - A(t) - Γ[2] ~ A(t) + C(t) -``` -""" -function conservationlaw_constants(rn::ReactionSystem) - conservationlaws(rn) - nps = get_networkproperties(rn) - nps.constantdefs -end - -""" - conservationlaws(netstoichmat::AbstractMatrix)::Matrix - -Given the net stoichiometry matrix of a reaction system, computes a matrix of -conservation laws, each represented as a row in the output. -""" -function conservationlaws(nsm::T; col_order = nothing) where {T <: AbstractMatrix} - - # compute the left nullspace over the integers - N = MT.nullspace(nsm'; col_order) - - # if all coefficients for a conservation law are negative, make positive - for Nrow in eachcol(N) - all(r -> r <= 0, Nrow) && (Nrow .*= -1) - end - - # check we haven't overflowed - iszero(N' * nsm) || error("Calculation of the conservation law matrix was inaccurate, " - * "likely due to numerical overflow. Please use a larger integer " - * "type like Int128 or BigInt for the net stoichiometry matrix.") - - T(N') -end - -function cache_conservationlaw_eqs!(rn::ReactionSystem, N::AbstractMatrix, col_order) - nullity = size(N, 1) - r = numspecies(rn) - nullity # rank of the netstoichmat - sts = species(rn) - indepidxs = col_order[begin:r] - indepspecs = sts[indepidxs] - depidxs = col_order[(r + 1):end] - depspecs = sts[depidxs] - constants = MT.unwrap.(MT.scalarize((@parameters Γ[1:nullity])[1])) - - conservedeqs = Equation[] - constantdefs = Equation[] - for (i, depidx) in enumerate(depidxs) - scaleby = (N[i, depidx] != 1) ? N[i, depidx] : one(eltype(N)) - (scaleby != 0) || error("Error, found a zero in the conservation law matrix where " - * - "one was not expected.") - coefs = @view N[i, indepidxs] - terms = sum(p -> p[1] / scaleby * p[2], zip(coefs, indepspecs)) - eq = depspecs[i] ~ constants[i] - terms - push!(conservedeqs, eq) - eq = constants[i] ~ depspecs[i] + terms - push!(constantdefs, eq) - end - - # cache in the system - nps = get_networkproperties(rn) - nps.rank = r - nps.nullity = nullity - nps.indepspecs = Set(indepspecs) - nps.depspecs = Set(depspecs) - nps.conservedeqs = conservedeqs - nps.constantdefs = constantdefs - - nothing -end - -""" - conservationlaws(rs::ReactionSystem) - -Return the conservation law matrix of the given `ReactionSystem`, calculating it if it is -not already stored within the system, or returning an alias to it. - -Notes: -- The first time being called it is calculated and cached in `rn`, subsequent calls should - be fast. -""" -function conservationlaws(rs::ReactionSystem) - nps = get_networkproperties(rs) - !isempty(nps.conservationmat) && (return nps.conservationmat) - nsm = netstoichmat(rs) - nps.conservationmat = conservationlaws(nsm; col_order = nps.col_order) - cache_conservationlaw_eqs!(rs, nps.conservationmat, nps.col_order) - nps.conservationmat -end - -""" - conservedquantities(state, cons_laws) - -Compute conserved quantities for a system with the given conservation laws. -""" -conservedquantities(state, cons_laws) = cons_laws * state - - -# If u0s are not given while conservation laws are present, throws an error. -# Used in HomotopyContinuation and BifurcationKit extensions. -# Currently only checks if any u0s are given -# (not whether these are enough for computing conserved quantitites, this will yield a less informative error). -function conservationlaw_errorcheck(rs, pre_varmap) - vars_with_vals = Set(p[1] for p in pre_varmap) - any(s -> s in vars_with_vals, species(rs)) && return - isempty(conservedequations(Catalyst.flatten(rs))) || - error("The system has conservation laws but initial conditions were not provided for some species.") -end - -######################## reaction network operators ####################### - -""" - ==(rx1::Reaction, rx2::Reaction) - -Tests whether two [`Reaction`](@ref)s are identical. - -Notes: -- Ignores the order in which stoichiometry components are listed. -- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be - considered different than `(A+1)^2`. -""" -function (==)(rx1::Reaction, rx2::Reaction) - isequal(rx1.rate, rx2.rate) || return false - issetequal(zip(rx1.substrates, rx1.substoich), zip(rx2.substrates, rx2.substoich)) || - return false - issetequal(zip(rx1.products, rx1.prodstoich), zip(rx2.products, rx2.prodstoich)) || - return false - issetequal(rx1.netstoich, rx2.netstoich) || return false - rx1.only_use_rate == rx2.only_use_rate -end - -function hash(rx::Reaction, h::UInt) - h = Base.hash(rx.rate, h) - for s in Iterators.flatten((rx.substrates, rx.products)) - h ⊻= hash(s) - end - for s in Iterators.flatten((rx.substoich, rx.prodstoich)) - h ⊻= hash(s) - end - for s in rx.netstoich - h ⊻= hash(s) - end - Base.hash(rx.only_use_rate, h) -end - -""" - isequivalent(rn1::ReactionSystem, rn2::ReactionSystem; ignorenames = true) - -Tests whether the underlying species, parameters and reactions are the same in -the two [`ReactionSystem`](@ref)s. Ignores the names of the systems in testing -equality. - -Notes: -- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be - considered different than `(A+1)^2`. -- Does not include `defaults` in determining equality. -""" -function isequivalent(rn1::ReactionSystem, rn2::ReactionSystem; ignorenames = true) - if !ignorenames - (nameof(rn1) == nameof(rn2)) || return false - end - - (get_combinatoric_ratelaws(rn1) == get_combinatoric_ratelaws(rn2)) || return false - isequal(get_iv(rn1), get_iv(rn2)) || return false - issetequal(get_sivs(rn1), get_sivs(rn2)) || return false - issetequal(get_states(rn1), get_states(rn2)) || return false - issetequal(get_ps(rn1), get_ps(rn2)) || return false - issetequal(MT.get_observed(rn1), MT.get_observed(rn2)) || return false - issetequal(get_eqs(rn1), get_eqs(rn2)) || return false - - # subsystems - (length(get_systems(rn1)) == length(get_systems(rn2))) || return false - issetequal(get_systems(rn1), get_systems(rn2)) || return false - - true -end - -function isequal_ignore_names(rn1, rn2) - Base.depwarn("Catalyst.isequal_ignore_names has been deprecated. Use isequivalent(rn1, rn2) instead.", - :isequal_ignore_names; force = true) - isequivalent(rn1, rn2) -end - -""" - ==(rn1::ReactionSystem, rn2::ReactionSystem) - -Tests whether the underlying species, parameters and reactions are the same in -the two [`ReactionSystem`](@ref)s. Requires the systems to have the same names -too. - -Notes: -- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be - considered different than `(A+1)^2`. -- Does not include `defaults` in determining equality. -""" -function (==)(rn1::ReactionSystem, rn2::ReactionSystem) - isequivalent(rn1, rn2; ignorenames = false) -end - -######################## functions to extend a network #################### - -""" - make_empty_network(; iv=DEFAULT_IV, name=gensym(:ReactionSystem)) - -Construct an empty [`ReactionSystem`](@ref). `iv` is the independent variable, -usually time, and `name` is the name to give the `ReactionSystem`. -""" -function make_empty_network(; iv = DEFAULT_IV, name = gensym(:ReactionSystem)) - ReactionSystem(Reaction[], iv, [], []; name = name) -end - -""" - addspecies!(network::ReactionSystem, s::Symbolic; disablechecks=false) - -Given a [`ReactionSystem`](@ref), add the species corresponding to the variable -`s` to the network (if it is not already defined). Returns the integer id of the -species within the system. - -Notes: -- `disablechecks` will disable checking for whether the passed in variable is - already defined, which is useful when adding many new variables to the system. - *Do not disable checks* unless you are sure the passed in variable is a new - variable, as this will potentially leave the system in an undefined state. -""" -function addspecies!(network::ReactionSystem, s::Symbolic; disablechecks = false) - reset_networkproperties!(network) - - isconstant(s) && error("Constant species should be added via addparams!.") - isspecies(s) || - error("$s is not a valid symbolic species. Please use @species to declare it.") - - # we don't check subsystems since we will add it to the top-level system... - curidx = disablechecks ? nothing : findfirst(S -> isequal(S, s), get_states(network)) - if curidx === nothing - push!(get_states(network), s) - sort!(get_states(network); by = !isspecies) - push!(get_species(network), s) - MT.process_variables!(get_var_to_name(network), get_defaults(network), [s]) - return length(get_species(network)) - else - return curidx - end -end - -""" - addspecies!(network::ReactionSystem, s::Num; disablechecks=false) - -Given a [`ReactionSystem`](@ref), add the species corresponding to the -variable `s` to the network (if it is not already defined). Returns the -integer id of the species within the system. - -- `disablechecks` will disable checking for whether the passed in variable is - already defined, which is useful when adding many new variables to the system. - *Do not disable checks* unless you are sure the passed in variable is a new - variable, as this will potentially leave the system in an undefined state. -""" -function addspecies!(network::ReactionSystem, s::Num; disablechecks = false) - addspecies!(network, value(s), disablechecks = disablechecks) -end - -""" - reorder_states!(rn, neworder) - -Given a [`ReactionSystem`](@ref) and a vector `neworder`, reorders the states of `rn`, i.e. -`get_states(rn)`, according to `neworder`. - -Notes: -- Currently only supports `ReactionSystem`s without subsystems. -""" -function reorder_states!(rn, neworder) - reset_networkproperties!(rn) - - permute!(get_states(rn), neworder) - if !issorted(get_states(rn); by = !isspecies) - @warn "New ordering has resulted in a non-species state preceding a species state. This is not allowed so states have been resorted to ensure species precede non-species." - sort!(get_states(rn); by = !isspecies) - end - get_species(rn) .= Iterators.filter(isspecies, get_states(rn)) - nothing -end - -""" - addparam!(network::ReactionSystem, p::Symbolic; disablechecks=false) - -Given a [`ReactionSystem`](@ref), add the parameter corresponding to the -variable `p` to the network (if it is not already defined). Returns the integer -id of the parameter within the system. - -- `disablechecks` will disable checking for whether the passed in variable is - already defined, which is useful when adding many new variables to the system. - *Do not disable checks* unless you are sure the passed in variable is a new - variable, as this will potentially leave the system in an undefined state. -""" -function addparam!(network::ReactionSystem, p::Symbolic; disablechecks = false) - reset_networkproperties!(network) - - # we don't check subsystems since we will add it to the top-level system... - if istree(p) && !(operation(p) isa Symbolic && !istree(operation(p))) - error("If the passed in parameter is an expression, it must correspond to an underlying Variable.") - end - curidx = disablechecks ? nothing : findfirst(S -> isequal(S, p), get_ps(network)) - if curidx === nothing - push!(get_ps(network), p) - MT.process_variables!(get_var_to_name(network), get_defaults(network), [p]) - return length(get_ps(network)) - else - return curidx - end -end - -""" - addparam!(network::ReactionSystem, p::Num; disablechecks=false) - -Given a [`ReactionSystem`](@ref), add the parameter corresponding to the -variable `p` to the network (if it is not already defined). Returns the -integer id of the parameter within the system. - -- `disablechecks` will disable checking for whether the passed in variable is - already defined, which is useful when adding many new variables to the system. - *Do not disable checks* unless you are sure the passed in variable is a new - variable, as this will potentially leave the system in an undefined state. -""" -function addparam!(network::ReactionSystem, p::Num; disablechecks = false) - addparam!(network, value(p); disablechecks = disablechecks) -end - -""" - addreaction!(network::ReactionSystem, rx::Reaction) - -Add the passed in reaction to the [`ReactionSystem`](@ref). Returns the -integer id of `rx` in the list of `Reaction`s within `network`. - -Notes: -- Any new species or parameters used in `rx` should be separately added to - `network` using [`addspecies!`](@ref) and [`addparam!`](@ref). -""" -function addreaction!(network::ReactionSystem, rx::Reaction) - reset_networkproperties!(network) - push!(get_eqs(network), rx) - sort(get_eqs(network); by = eqsortby) - push!(get_rxs(network), rx) - length(get_rxs(network)) -end - -""" - merge!(network1::ReactionSystem, network2::ReactionSystem) - -Merge `network2` into `network1`. - -Notes: -- Duplicate reactions between the two networks are not filtered out. -- [`Reaction`](@ref)s are not deepcopied to minimize allocations, so both - networks will share underlying data arrays. -- Subsystems are not deepcopied between the two networks and will hence be - shared. -- Returns `network1`. -- `combinatoric_ratelaws` is the value of `network1`. -""" -function Base.merge!(network1::ReactionSystem, network2::ReactionSystem) - isequal(get_iv(network1), get_iv(network2)) || - error("Reaction networks must have the same independent variable to be mergable.") - union!(get_sivs(network1), get_sivs(network2)) - - union!(get_eqs(network1), get_eqs(network2)) - sort!(get_eqs(network1), by = eqsortby) - union!(get_rxs(network1), get_rxs(network2)) - (count(eq -> eq isa Reaction, get_eqs(network1)) == length(get_rxs(network1))) || - error("Unequal number of reactions from get_rxs(sys) and get_eqs(sys) after merging.") - - union!(get_states(network1), get_states(network2)) - sort!(get_states(network1), by = !isspecies) - union!(get_species(network1), get_species(network2)) - (count(isspecies, get_states(network1)) == length(get_species(network1))) || - error("Unequal number of species from get_species(sys) and get_states(sys) after merging.") - - union!(get_ps(network1), get_ps(network2)) - union!(get_observed(network1), get_observed(network2)) - union!(get_systems(network1), get_systems(network2)) - merge!(get_defaults(network1), get_defaults(network2)) - union!(MT.get_continuous_events(network1), MT.get_continuous_events(network2)) - union!(MT.get_discrete_events(network1), MT.get_discrete_events(network2)) - - reset_networkproperties!(network1) - network1 -end - -############################### units ##################################### - -""" - validate(rx::Reaction; info::String = "") - -Check that all substrates and products within the given [`Reaction`](@ref) have -the same units, and that the units of the reaction's rate expression are -internally consistent (i.e. if the rate involves sums, each term in the sum has -the same units). - -""" -function validate(rx::Reaction; info::String = "") - validated = MT._validate([rx.rate], [string(rx, ": rate")], info = info) - - subunits = isempty(rx.substrates) ? nothing : get_unit(rx.substrates[1]) - for i in 2:length(rx.substrates) - if get_unit(rx.substrates[i]) != subunits - validated = false - @warn(string("In ", rx, " the substrates have differing units.")) - end - end - - produnits = isempty(rx.products) ? nothing : get_unit(rx.products[1]) - for i in 2:length(rx.products) - if get_unit(rx.products[i]) != produnits - validated = false - @warn(string("In ", rx, " the products have differing units.")) - end - end - - if (subunits !== nothing) && (produnits !== nothing) && (subunits != produnits) - validated = false - @warn(string("in ", rx, - " the substrate units are not consistent with the product units.")) - end - - validated -end - -""" - validate(rs::ReactionSystem, info::String="") - -Check that all species in the [`ReactionSystem`](@ref) have the same units, and -that the rate laws of all reactions reduce to units of (species units) / (time -units). - -Notes: -- Does not check subsystems, constraint equations, or non-species variables. -""" -function validate(rs::ReactionSystem, info::String = "") - specs = get_species(rs) - - # if there are no species we don't check units on the system - isempty(specs) && return true - - specunits = get_unit(specs[1]) - validated = true - for spec in specs - if get_unit(spec) != specunits - validated = false - @warn(string("Species are expected to have units of ", specunits, - " however, species ", spec, " has units ", get_unit(spec), ".")) - end - end - timeunits = get_unit(get_iv(rs)) - - # no units for species, time or parameters then assume validated - (specunits in (MT.unitless, nothing)) && (timeunits in (MT.unitless, nothing)) && - MT.all_dimensionless(get_ps(rs)) && return true - - rateunits = specunits / timeunits - for rx in get_rxs(rs) - rxunits = get_unit(rx.rate) - for (i, sub) in enumerate(rx.substrates) - rxunits *= get_unit(sub)^rx.substoich[i] - end - - if rxunits != rateunits - validated = false - @warn(string("Reaction rate laws are expected to have units of ", rateunits, - " however, ", rx, " has units of ", rxunits, ".")) - end - end - - validated -end - -# Checks if a unit consist of exponents with base 1 (and is this unitless). -unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) - From 128572084cfb87fac0e759b9c58da5c8e32e7c07 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 10:36:46 -0400 Subject: [PATCH 060/446] removed metagraph dep --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index ac53aaee49..df531adca8 100644 --- a/Project.toml +++ b/Project.toml @@ -15,7 +15,6 @@ LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" -MetaGraphs = "626554b9-1ddb-594c-aa3c-2596fe9399a5" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" From c5398de85d2999198388dde3c8fae6d56aa02de4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 31 May 2024 10:47:15 -0400 Subject: [PATCH 061/446] add DiffEq doc dependency again --- docs/Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Project.toml b/docs/Project.toml index 246727a23e..ebb086fda6 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,6 +5,7 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DiffEqParamEstim = "1130ab10-4a5a-5621-a13d-e4788d82bd4c" +DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DynamicalSystems = "61744808-ddfa-5f27-97ff-6e42cc95d634" From 556ca711af481cad3c384dc8099f225159f99bc9 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 11:29:56 -0400 Subject: [PATCH 062/446] cycle wip --- src/network_analysis.jl | 40 ++++++++++++++++++++++++++++++++++++++++ src/reactionsystem.jl | 3 ++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index cc33f9b63e..dcac8e0855 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -718,3 +718,43 @@ function conservationlaw_errorcheck(rs, pre_varmap) isempty(conservedequations(Catalyst.flatten(rs))) || error("The system has conservation laws but initial conditions were not provided for some species.") end + +""" + cycles(rs::ReactionSystem) + + Returns the matrix of cycles, or right eigenvectors of the stoichiometric matrix. +""" + +function cycles(rs::ReactionSystem) + # nps = get_networkproperties(rs) + nsm = netstoichmat(rs) + cycles(nsm) +end + +function cycles(nsm::T; col_order = nothing) where {T <: AbstractMatrix} + + # compute the left nullspace over the integers + N = MT.nullspace(nsm; col_order) + + # if all coefficients for a conservation law are negative, make positive + for Nrow in eachcol(N) + all(r -> r <= 0, Nrow) && (Nrow .*= -1) + end + + # check we haven't overflowed + iszero(nsm * N) || error("Calculation of the cycle matrix was inaccurate, " + * "likely due to numerical overflow. Please use a larger integer " + * "type like Int128 or BigInt for the net stoichiometry matrix.") + + T(N) +end + +""" + fluxmodes(rs::ReactionSystem) + + See documentation for [`cycles`](@ref). +""" + +function fluxmodes(rs::ReactionSystem) + cycles(rs) +end diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 5c04b2e642..2082cca9d1 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -80,6 +80,7 @@ Base.@kwdef mutable struct NetworkProperties{I <: Integer, V <: BasicSymbolic{Re isempty::Bool = true netstoichmat::Union{Matrix{Int}, SparseMatrixCSC{Int, Int}} = Matrix{Int}(undef, 0, 0) conservationmat::Matrix{I} = Matrix{I}(undef, 0, 0) + cyclemat::Matrix{I} = Matrix{I}(undef, 0, 0) col_order::Vector{Int} = Int[] rank::Int = 0 nullity::Int = 0 @@ -1456,4 +1457,4 @@ function validate(rs::ReactionSystem, info::String = "") end # Checks if a unit consist of exponents with base 1 (and is this unitless). -unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) \ No newline at end of file +unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) From cf83b4a4b20022ba77d1d2d5cd2b6794b9eded31 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Dec 2023 12:52:26 -0500 Subject: [PATCH 063/446] init. --- src/Catalyst.jl | 9 +- .../lattice_jump_systems.jl | 81 +++++++++ .../lattice_reaction_systems_jumps.jl | 157 ++++++++++++++++++ 3 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 src/spatial_reaction_systems/lattice_jump_systems.jl create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl diff --git a/src/Catalyst.jl b/src/Catalyst.jl index d80c115119..ad889bef22 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -166,20 +166,21 @@ export make_si_ode ### Spatial Reaction Networks ### -# spatial reactions +# Spatial reactions. include("spatial_reaction_systems/spatial_reactions.jl") export TransportReaction, TransportReactions, @transport_reaction export isedgeparameter -# lattice reaction systems +# Lattice reaction systems include("spatial_reaction_systems/lattice_reaction_systems.jl") export LatticeReactionSystem export spatial_species, vertex_parameters, edge_parameters -# variosu utility functions +# Various utility functions include("spatial_reaction_systems/utility.jl") -# spatial lattice ode systems. +# Specific spatial problem types. include("spatial_reaction_systems/spatial_ODE_systems.jl") +include("spatial_reaction_systems/lattice_jump_systems.jl") end # module diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl new file mode 100644 index 0000000000..2ecfe3fa56 --- /dev/null +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -0,0 +1,81 @@ +### JumpProblem ### + +# Builds a spatial DiscreteProblem from a Lattice Reaction System. +function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) + is_transport_system(lrs) || error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") + + # Converts potential symmaps to varmaps + # Vertex and edge parameters may be given in a tuple, or in a common vector, making parameter case complicated. + u0_in = symmap_to_varmap(lrs, u0_in) + p_in = (p_in isa Tuple{<:Any,<:Any}) ? + (symmap_to_varmap(lrs, p_in[1]),symmap_to_varmap(lrs, p_in[2])) : + symmap_to_varmap(lrs, p_in) + + # Converts u0 and p to their internal forms. + # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. + u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) + # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. + # These elements are length 1 vectors (if the parameter is uniform), + # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + + # Returns a DiscreteProblem. + # Previously, a Tuple was used for (vert_ps, edge_ps), but this was converted to a Vector internally. + return DiscreteProblem(lrs.rs, u0, tspan, [vert_ps, edge_ps], args...; kwargs...) +end + +# Builds a spatial JumpProblem from a DiscreteProblem containg a Lattice Reaction System. +function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(lrs.rs), + combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), kwargs...) + # Error checks. + (dprob.p isa Vector{Vector{Vector{Float64}}}) || dprob.p isa Vector{Vector} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") # The second check (Vector{Vector} is needed becaus on the CI server somehow the Tuple{..., ...} is covnerted into a Vector[..., ...]). It does not happen when I run tests locally, so no ideal how to fix. + any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") + + # Computes hopping constants and mass action jumps (requires some internal juggling). + # The non-spatial DiscreteProblem have a u0 matrix with entries for all combinations of species and vertexes. + # Currently, JumpProcesses requires uniform vertex parameters (hence `p=first.(dprob.p[1])`). + hopping_constants = make_hopping_constants(dprob, lrs) + non_spat_dprob = DiscreteProblem(reshape(dprob.u0, lrs.num_species, lrs.num_verts), dprob.tspan, first.(dprob.p[1])) + majumps = make_majumps(non_spat_dprob, lrs.rs) + + return JumpProblem(non_spat_dprob, aggregator, majumps; + hopping_constants, spatial_system = lrs.lattice, name, kwargs...) +end + +# Creates the hopping constants from a discrete problem and a lattice reaction system. +function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) + # Creates the all_diff_rates vector, containing for each species, its transport rate across all edges. + # If transport rate is uniform for one species, the vector have a single element, else one for each edge. + spatial_rates_dict = Dict(compute_all_transport_rates(dprob.p[1], dprob.p[2], lrs)) + all_diff_rates = [haskey(spatial_rates_dict, s) ? spatial_rates_dict[s] : [0.0] for s in species(lrs)] + + # Creates the hopping constant Matrix. It contains one element for each combination of species and vertex. + # Each element is a Vector, containing the outgoing hopping rates for that species, from that vertex, on that edge. + hopping_constants = [Vector{Float64}(undef, length(lrs.lattice.fadjlist[j])) + for i in 1:(lrs.num_species), j in 1:(lrs.num_verts)] + + # For each edge, finds each position in `hopping_constants`. + for (e_idx, e) in enumerate(edges(lrs.lattice)) + dst_idx = findfirst(isequal(e.dst), lrs.lattice.fadjlist[e.src]) + # For each species, sets that hopping rate. + for s_idx in 1:(lrs.num_species) + hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx) + end + end + + return hopping_constants +end + +# Creates the (non-spatial) mass action jumps from a (non-spatial) DiscreteProblem (and its Reaction System of origin). +function make_majumps(non_spat_dprob, rs::ReactionSystem) + # Computes various required inputs for assembling the mass action jumps. + js = convert(JumpSystem, rs) + statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(states(rs))) + eqs = equations(js) + invttype = non_spat_dprob.tspan[1] === nothing ? Float64 : typeof(1 / non_spat_dprob.tspan[2]) + + # Assembles the mass action jumps. + p = (non_spat_dprob.p isa DiffEqBase.NullParameters || non_spat_dprob.p === nothing) ? Num[] : non_spat_dprob.p + majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) + return ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) +end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl new file mode 100644 index 0000000000..f41f1d3090 --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -0,0 +1,157 @@ +### Preparations ### + +# Fetch packages. +using JumpProcesses +using Random, Statistics, SparseArrays, Test + +# Fetch test networks. +include("../spatial_test_networks.jl") + +### Correctness Tests ### + +# Tests that there are no errors during runs for a variety of input forms. +let + for grid in [small_2d_grid, short_path, small_directed_cycle] + for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] + lrs = LatticeReactionSystem(SIR_system, srs, grid) + u0_1 = [:S => 999, :I => 1, :R => 0] + u0_2 = [:S => round.(Int64, 500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => 1, :R => 0, ] + u0_3 = [:S => 950, :I => round.(Int64, 50 * rand_v_vals(lrs.lattice)), :R => round.(Int64, 50 * rand_v_vals(lrs.lattice))] + u0_4 = [:S => round.(500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => round.(50 * rand_v_vals(lrs.lattice)), :R => round.(50 * rand_v_vals(lrs.lattice))] + u0_5 = make_u0_matrix(u0_3, vertices(lrs.lattice), map(s -> Symbol(s.f), species(lrs.rs))) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + p1 = [:α => 0.1 / 1000, :β => 0.01] + p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] + p3 = [ + :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), + :β => 0.02 * rand_v_vals(lrs.lattice), + ] + p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) + for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. + pE_1 = map(sp -> sp => 0.01, ModelingToolkit.getname.(edge_parameters(lrs))) + pE_2 = map(sp -> sp => 0.01, ModelingToolkit.getname.(edge_parameters(lrs))) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), ModelingToolkit.getname.(edge_parameters(lrs))) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) + for pE in [pE_1, pE_2, pE_3, pE_4] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) + end + end + end + end + end +end + +### Input Handling Tests ### + +# Tests that the correct hopping rates and initial conditions are generated. +# In this base case, hopping rates should be on the form D_{s,i,j}. +let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + + # Prepares various u0 input types. + u0_1 = [:I => 2.0, :S => 1.0, :R => 3.0] + u0_2 = [:I => fill(2., nv(small_2d_grid)), :S => 1.0, :R => 3.0] + u0_3 = [1.0, 2.0, 3.0] + u0_4 = [1.0, fill(2., nv(small_2d_grid)), 3.0] + u0_5 = permutedims(hcat(fill(1., nv(small_2d_grid)), fill(2., nv(small_2d_grid)), fill(3., nv(small_2d_grid)))) + + # Prepare various (compartment) parameter input types. + pV_1 = [:β => 0.2, :α => 0.1] + pV_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] + pV_3 = [0.1, 0.2] + pV_4 = [0.1, fill(0.2, nv(small_2d_grid))] + pV_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pE_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pE_2 = [:dI => 0.02, :dS => fill(0.01, ne(small_2d_grid)), :dR => 0.03] + pE_3 = [0.01, 0.02, 0.03] + pE_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] + pE_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) + + # Checks hopping rates and u0 are correct. + true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] + true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) + true_maj_scaled_rates = [0.1, 0.2] + true_maj_reactant_stoch = [[1 => 1, 2 => 1], [2 => 1]] + true_maj_net_stoch = [[1 => -1, 2 => 1], [2 => -1, 3 => 1]] + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + # Provides parameters as a tupple. + for pV in [pV_1, pV_3], pE in [pE_1, pE_2, pE_3, pE_4, pE_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV,pE)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.prob.u0 == true_u0 + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + @test jprob.massaction_jump.scaled_rates == true_maj_scaled_rates + @test jprob.massaction_jump.reactant_stoch == true_maj_reactant_stoch + @test jprob.massaction_jump.net_stoch == true_maj_net_stoch + end + # Provides parameters as a combined vector. + for pV in [pV_1], pE in [pE_1, pE_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pE; pV]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.prob.u0 == true_u0 + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + @test jprob.massaction_jump.scaled_rates == true_maj_scaled_rates + @test jprob.massaction_jump.reactant_stoch == true_maj_reactant_stoch + @test jprob.massaction_jump.net_stoch == true_maj_net_stoch + end + end +end + +### ABC Model Test (from JumpProcesses) ### +let + # Preparations (stuff used in JumpProcesses examples ported over here, could be written directly into code). + Nsims = 100 + reltol = 0.05 + non_spatial_mean = [65.7395, 65.7395, 434.2605] #mean of 10,000 simulations + dim = 1 + linear_size = 5 + num_nodes = linear_size^dim + dims = Tuple(repeat([linear_size], dim)) + domain_size = 1.0 #μ-meter + mesh_size = domain_size / linear_size + rates = [0.1 / mesh_size, 1.0] + diffusivity = 1.0 + num_species = 3 + + # Make model. + rn = @reaction_network begin + (kB,kD), A + B <--> C + end + tr_1 = @transport_reaction D A + tr_2 = @transport_reaction D B + tr_3 = @transport_reaction D C + lattice = Graphs.grid(dims) + lrs = LatticeReactionSystem(rn, [tr_1, tr_2, tr_3], lattice) + + # Set simulation parameters and create problems + u0 = [:A => [0,0,500,0,0], :B => [0,0,500,0,0], :C => 0] + tspan = (0.0, 10.0) + pV = [:kB => rates[1], :kD => rates[2]] + pE = [:D => diffusivity] + dprob = DiscreteProblem(lrs, u0, tspan, (pV, pE)) + jump_problems = [JumpProblem(lrs, dprob, alg(); save_positions = (false, false)) for alg in [NSM, DirectCRDirect]] # NRM doesn't work. Might need Cartesian grid. + + # Tests. + function get_mean_end_state(jump_prob, Nsims) + end_state = zeros(size(jump_prob.prob.u0)) + for i in 1:Nsims + sol = solve(jump_prob, SSAStepper()) + end_state .+= sol.u[end] + end + end_state / Nsims + end + for jprob in jump_problems + solution = solve(jprob, SSAStepper()) + mean_end_state = get_mean_end_state(jprob, Nsims) + mean_end_state = reshape(mean_end_state, num_species, num_nodes) + diff = sum(mean_end_state, dims = 2) - non_spatial_mean + for (i, d) in enumerate(diff) + @test abs(d) < reltol * non_spatial_mean[i] + end + end +end \ No newline at end of file From f19582be1d5c8aa5394c4e2a6b1b20b23ee12b81 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Dec 2023 13:00:22 -0500 Subject: [PATCH 064/446] test up --- .../lattice_reaction_systems_jumps.jl | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index f41f1d3090..8b7db9c105 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -102,17 +102,19 @@ let end end -### ABC Model Test (from JumpProcesses) ### +### Tests taken from JumpProcesses ### + +# ABC Model Test let # Preparations (stuff used in JumpProcesses examples ported over here, could be written directly into code). Nsims = 100 reltol = 0.05 - non_spatial_mean = [65.7395, 65.7395, 434.2605] #mean of 10,000 simulations + non_spatial_mean = [65.7395, 65.7395, 434.2605] # Mean of 10,000 simulations. dim = 1 linear_size = 5 num_nodes = linear_size^dim dims = Tuple(repeat([linear_size], dim)) - domain_size = 1.0 #μ-meter + domain_size = 1.0 # μ-meter. mesh_size = domain_size / linear_size rates = [0.1 / mesh_size, 1.0] diffusivity = 1.0 @@ -128,15 +130,16 @@ let lattice = Graphs.grid(dims) lrs = LatticeReactionSystem(rn, [tr_1, tr_2, tr_3], lattice) - # Set simulation parameters and create problems + # Set simulation parameters and create problems. u0 = [:A => [0,0,500,0,0], :B => [0,0,500,0,0], :C => 0] tspan = (0.0, 10.0) pV = [:kB => rates[1], :kD => rates[2]] pE = [:D => diffusivity] dprob = DiscreteProblem(lrs, u0, tspan, (pV, pE)) - jump_problems = [JumpProblem(lrs, dprob, alg(); save_positions = (false, false)) for alg in [NSM, DirectCRDirect]] # NRM doesn't work. Might need Cartesian grid. + # NRM could be added, but doesn't work. Might need Cartesian grid. + jump_problems = [JumpProblem(lrs, dprob, alg(); save_positions = (false, false)) for alg in [NSM, DirectCRDirect]] - # Tests. + # Run tests. function get_mean_end_state(jump_prob, Nsims) end_state = zeros(size(jump_prob.prob.u0)) for i in 1:Nsims From fcff2e8e6c3c498995ef3b3647fb1162f77bab34 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 30 Dec 2023 11:55:20 +0100 Subject: [PATCH 065/446] up --- .../lattice_reaction_systems_jumps.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 8b7db9c105..1eb1566c30 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -7,7 +7,8 @@ using Random, Statistics, SparseArrays, Test # Fetch test networks. include("../spatial_test_networks.jl") -### Correctness Tests ### + +### General Tests ### # Tests that there are no errors during runs for a variety of input forms. let @@ -43,6 +44,7 @@ let end end + ### Input Handling Tests ### # Tests that the correct hopping rates and initial conditions are generated. @@ -102,6 +104,7 @@ let end end + ### Tests taken from JumpProcesses ### # ABC Model Test From 50c20beb5ea32ed93c2823e38b8bd60f67053515 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 31 Dec 2023 13:44:32 +0100 Subject: [PATCH 066/446] init --- .../lattice_jump_systems.jl | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 2ecfe3fa56..462c926294 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -36,9 +36,9 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator # Currently, JumpProcesses requires uniform vertex parameters (hence `p=first.(dprob.p[1])`). hopping_constants = make_hopping_constants(dprob, lrs) non_spat_dprob = DiscreteProblem(reshape(dprob.u0, lrs.num_species, lrs.num_verts), dprob.tspan, first.(dprob.p[1])) - majumps = make_majumps(non_spat_dprob, lrs.rs) + sma_jumps = make_spatial_majumps(non_spat_dprob, dprob, lrs) - return JumpProblem(non_spat_dprob, aggregator, majumps; + return JumpProblem(non_spat_dprob, aggregator, sma_jumps; hopping_constants, spatial_system = lrs.lattice, name, kwargs...) end @@ -66,6 +66,12 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst return hopping_constants end +# Creates the (spatial) mass action jumps from a (spatial) DiscreteProblem its non-spatial version, and a LatticeReactionSystem. +function make_spatial_majumps(non_spat_dprob, dprob, rs::LatticeReactionSystem) + ma_jumps = make_majumps(non_spat_dprob, lrs.rs) + +end + # Creates the (non-spatial) mass action jumps from a (non-spatial) DiscreteProblem (and its Reaction System of origin). function make_majumps(non_spat_dprob, rs::ReactionSystem) # Computes various required inputs for assembling the mass action jumps. @@ -74,8 +80,8 @@ function make_majumps(non_spat_dprob, rs::ReactionSystem) eqs = equations(js) invttype = non_spat_dprob.tspan[1] === nothing ? Float64 : typeof(1 / non_spat_dprob.tspan[2]) - # Assembles the mass action jumps. + # Assembles the non-spatial mass action jumps. p = (non_spat_dprob.p isa DiffEqBase.NullParameters || non_spat_dprob.p === nothing) ? Num[] : non_spat_dprob.p majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) return ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) -end \ No newline at end of file +end From 38c28691f7e1d2d13b62be11ba1355e9e7b8e4e7 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 3 Jan 2024 14:36:39 +0100 Subject: [PATCH 067/446] Use SpatialMassActionJump --- src/Catalyst.jl | 6 +- .../lattice_jump_systems.jl | 87 +++++++++++++---- src/spatial_reaction_systems/utility.jl | 96 +++++++++++++++++++ .../lattice_reaction_systems_jumps.jl | 56 +++++++++++ 4 files changed, 222 insertions(+), 23 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index ad889bef22..2aab60fb54 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -6,9 +6,9 @@ module Catalyst using DocStringExtensions using SparseArrays, DiffEqBase, Reexport, Setfield using LaTeXStrings, Latexify, Requires -using JumpProcesses: JumpProcesses, - JumpProblem, MassActionJump, ConstantRateJump, - VariableRateJump +using JumpProcesses: JumpProcesses, JumpProblem, + MassActionJump, ConstantRateJump, VariableRateJump, + SpatialMassActionJump # ModelingToolkit imports and convenience functions we use using ModelingToolkit diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 462c926294..6287936889 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -28,15 +28,19 @@ end function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(lrs.rs), combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), kwargs...) # Error checks. - (dprob.p isa Vector{Vector{Vector{Float64}}}) || dprob.p isa Vector{Vector} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") # The second check (Vector{Vector} is needed becaus on the CI server somehow the Tuple{..., ...} is covnerted into a Vector[..., ...]). It does not happen when I run tests locally, so no ideal how to fix. - any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") + # The second check (Vector{Vector} is needed because on the CI server somehow the Tuple{..., ...} is converted into a Vector[..., ...]). + # It does not happen when I run tests locally, so no ideal how to fix. + (dprob.p isa Vector{Vector{Vector{Float64}}}) || dprob.p isa Vector{Vector} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") # Computes hopping constants and mass action jumps (requires some internal juggling). - # The non-spatial DiscreteProblem have a u0 matrix with entries for all combinations of species and vertexes. # Currently, JumpProcesses requires uniform vertex parameters (hence `p=first.(dprob.p[1])`). + # Currently, the resulting JumpProblem does not depend on parameters (no way to incorporate these). + # Hence the parameters of this one does nto actually matter. If at some point JumpProcess can + # handle parameters this can be updated and improved. + # The non-spatial DiscreteProblem have a u0 matrix with entries for all combinations of species and vertexes. hopping_constants = make_hopping_constants(dprob, lrs) + sma_jumps = make_spatial_majumps(dprob, lrs) non_spat_dprob = DiscreteProblem(reshape(dprob.u0, lrs.num_species, lrs.num_verts), dprob.tspan, first.(dprob.p[1])) - sma_jumps = make_spatial_majumps(non_spat_dprob, dprob, lrs) return JumpProblem(non_spat_dprob, aggregator, sma_jumps; hopping_constants, spatial_system = lrs.lattice, name, kwargs...) @@ -66,22 +70,65 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst return hopping_constants end -# Creates the (spatial) mass action jumps from a (spatial) DiscreteProblem its non-spatial version, and a LatticeReactionSystem. -function make_spatial_majumps(non_spat_dprob, dprob, rs::LatticeReactionSystem) - ma_jumps = make_majumps(non_spat_dprob, lrs.rs) - +# Creates a SpatialMassActionJump struct from a (spatial) DiscreteProblem and a LatticeReactionSystem. +# Could implementation a version which, if all reaction's rates are uniform, returns a MassActionJump. +# Not sure if there is any form of performance improvement from that though. Possibly is not the case. +function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) + # Creates a vector, storing which reactions have spatial components. + is_spatials = [Catalyst.has_spatial_vertex_component(rx.rate, lrs; vert_ps = dprob.p[1]) for rx in reactions(lrs.rs)] + + # Creates templates for the rates (uniform and spatial) and the stoichiometries. + # We cannot fetch reactant_stoich and net_stoich from a (non-spatial) MassActionJump. + # The reason is that we need to re-order the reactions so that uniform appears first, and spatial next. + u_rates = Vector{Float64}(undef, length(reactions(lrs.rs)) - count(is_spatials)) + s_rates = Matrix{Float64}(undef, count(is_spatials), lrs.num_verts) + reactant_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(lrs.rs))) + net_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(lrs.rs))) + + # Loops through reactions with non-spatial rates, computes their rates and stoichiometries. + cur_rx = 1; + for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs)) + is_spat && continue + u_rates[cur_rx] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1])[1] + substoich_map = Pair.(rx.substrates, rx.substoich) + reactant_stoich[cur_rx] = int_map(substoich_map, lrs.rs) + net_stoich[cur_rx] = int_map(rx.netstoich, lrs.rs) + cur_rx += 1 + end + # Loops through reactions with spatial rates, computes their rates and stoichiometries. + for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs)) + is_spat || continue + s_rates[cur_rx-length(u_rates),:] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1]) + substoich_map = Pair.(rx.substrates, rx.substoich) + reactant_stoich[cur_rx] = int_map(substoich_map, lrs.rs) + net_stoich[cur_rx] = int_map(rx.netstoich, lrs.rs) + cur_rx += 1 + end + # SpatialMassActionJump expects empty rate containers to be nothing. + isempty(u_rates) && (u_rates = nothing) + (count(is_spatials)==0) && (s_rates = nothing) + + return SpatialMassActionJump(u_rates, s_rates, reactant_stoich, net_stoich) end -# Creates the (non-spatial) mass action jumps from a (non-spatial) DiscreteProblem (and its Reaction System of origin). -function make_majumps(non_spat_dprob, rs::ReactionSystem) - # Computes various required inputs for assembling the mass action jumps. - js = convert(JumpSystem, rs) - statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(states(rs))) - eqs = equations(js) - invttype = non_spat_dprob.tspan[1] === nothing ? Float64 : typeof(1 / non_spat_dprob.tspan[2]) - - # Assembles the non-spatial mass action jumps. - p = (non_spat_dprob.p isa DiffEqBase.NullParameters || non_spat_dprob.p === nothing) ? Num[] : non_spat_dprob.p - majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) - return ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) +### Extra ### + +# Temporary. Awaiting implementation in SII, or proper implementation withinCatalyst (with more general functionality). +function int_map(map_in, sys) where {T,S} + return [ModelingToolkit.variable_index(sys, pair[1]) => pair[2] for pair in map_in] end + +# Currently unused. If we want to create certain types of MassActionJumps (instead of SpatialMassActionJumps) we can take this one back. +# Creates the (non-spatial) mass action jumps from a (non-spatial) DiscreteProblem (and its Reaction System of origin). +# function make_majumps(non_spat_dprob, rs::ReactionSystem) +# # Computes various required inputs for assembling the mass action jumps. +# js = convert(JumpSystem, rs) +# statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(states(rs))) +# eqs = equations(js) +# invttype = non_spat_dprob.tspan[1] === nothing ? Float64 : typeof(1 / non_spat_dprob.tspan[2]) +# +# # Assembles the non-spatial mass action jumps. +# p = (non_spat_dprob.p isa DiffEqBase.NullParameters || non_spat_dprob.p === nothing) ? Num[] : non_spat_dprob.p +# majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) +# return ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) +# end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 03271562e8..691ba79a3c 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -295,3 +295,99 @@ end function matrix_expand_component_values(values::Vector{<:Vector}, n) reshape(expand_component_values(values, n), length(values), n) end + +# For an expression, computes its values using the provided state and parameter vectors. +# The expression is assumed to be valid in edges (and can have edges parameter components). +# If some component is non-uniform, output is a vector of length equal to the number of vertexes. +# If all components are uniform, the output is a length one vector. +function compute_edge_value(exp, lrs::LatticeReactionSystem, edge_ps) + # Finds the symbols in the expression. Checks that all correspond to edge parameters. + relevant_syms = Symbolics.get_variables(exp) + if !all(any(isequal(sym, p) for p in edge_parameters(lrs)) for sym in relevant_syms) + error("An non-edge parameter was encountered in expressions: $exp. Here, only edge parameters are expected.") + end + + # Creates a Function tha computes the expressions value for a parameter set. + exp_func = drop_expr(@RuntimeGeneratedFunction(build_function(exp, relevant_syms...))) + # Creates a dictionary with the value(s) for all edge parameters. + sym_val_dict = vals_to_dict(edge_parameters(lrs), edge_ps) + + # If all values are uniform, compute value once. Else, do it at all edges. + if !has_spatial_edge_component(exp, lrs, edge_ps) + return [exp_func([sym_val_dict[sym][1] for sym in relevant_syms]...)] + end + return [exp_func([get_component_value(sym_val_dict[sym], idxE) for sym in relevant_syms]...) + for idxE in 1:lrs.num_edges] +end + +# For an expression, computes its values using the provided state and parameter vectors. +# The expression is assumed to be valid in vertexes (and can have vertex parameter and state components). +# If at least one component is non-uniform, output is a vector of length equal to the number of vertexes. +# If all components are uniform, the output is a length one vector. +function compute_vertex_value(exp, lrs::LatticeReactionSystem; u=nothing, vert_ps=nothing) + # Finds the symbols in the expression. Checks that all correspond to states or vertex parameters. + relevant_syms = Symbolics.get_variables(exp) + if any(any(isequal(sym) in edge_parameters(lrs)) for sym in relevant_syms) + error("An edge parameter was encountered in expressions: $exp. Here, on vertex-based components are expected.") + end + # Creates a Function tha computes the expressions value for a parameter set. + exp_func = drop_expr(@RuntimeGeneratedFunction(build_function(exp, relevant_syms...))) + # Creates a dictionary with the value(s) for all edge parameters. + if !isnothing(u) && !isnothing(vert_ps) + all_syms = [species(lrs); vertex_parameters(lrs)] + all_vals = [u; vert_ps] + elseif !isnothing(u) && isnothing(vert_ps) + all_syms = species(lrs) + all_vals = u + + elseif isnothing(u) && !isnothing(vert_ps) + all_syms = vertex_parameters(lrs) + all_vals = vert_ps + else + error("Either u or vertex_ps have to be provided to has_spatial_vertex_component.") + end + sym_val_dict = vals_to_dict(all_syms, all_vals) + + # If all values are uniform, compute value once. Else, do it at all edges. + if !has_spatial_vertex_component(exp, lrs; u, vert_ps) + return [exp_func([sym_val_dict[sym][1] for sym in relevant_syms]...)] + end + return [exp_func([get_component_value(sym_val_dict[sym], idxV) for sym in relevant_syms]...) + for idxV in 1:lrs.num_verts] +end + +### System Property Checks ### + +# For a Symbolic expression, a LatticeReactionSystem, and a parameter list of the internal format: +# Checks if any edge parameter in the expression have a spatial component (that is, is not uniform). +function has_spatial_edge_component(exp, lrs::LatticeReactionSystem, edge_ps) + # Finds the edge parameters in the expression. Computes their indexes. + exp_syms = Symbolics.get_variables(exp) + exp_edge_ps = filter(sym -> any(isequal(sym), edge_parameters(lrs)), exp_syms) + p_idxs = [findfirst(isequal(sym, edge_p) for edge_p in edge_parameters(lrs)) for sym in exp_syms] + # Checks if any of the corresponding value vectors have length != 1 (that is, is not uniform). + return any(length(edge_ps[p_idx]) != 1 for p_idx in p_idxs) +end + +# For a Symbolic expression, a LatticeReactionSystem, and a parameter list of the internal format (vector of vectors): +# Checks if any vertex parameter in the expression have a spatial component (that is, is not uniform). +function has_spatial_vertex_component(exp, lrs::LatticeReactionSystem; u=nothing, vert_ps=nothing) + # Finds all the symbols in the expression. + exp_syms = Symbolics.get_variables(exp) + + # If vertex parameter values where given, checks if any of these have non-uniform values. + if !isnothing(vert_ps) + exp_vert_ps = filter(sym -> any(isequal(sym), vertex_parameters(lrs)), exp_syms) + p_idxs = [ModelingToolkit.parameter_index(lrs.rs, sym) for sym in exp_vert_ps] + any(length(vert_ps[p_idx]) != 1 for p_idx in p_idxs) && return true + end + + # If states values where given, checks if any of these have non-uniform values. + if !isnothing(u) + exp_u = filter(sym -> any(isequal(sym), species(lrs)), exp_syms) + u_idxs = [ModelingToolkit.variable_index(lrs.rs, sym) for sym in exp_u] + any(length(u[u_idx]) != 1 for u_idx in u_idxs) && return true + end + + return false +end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 1eb1566c30..6721e707cd 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -105,6 +105,62 @@ let end +### SpatialMassActionJump Testing ### + +# Checks that the correct structure is produced. +let + # Network for reference: + # A, ∅ → X + # 1, 2X + Y → 3X + # B, X → Y + # 1, X → ∅ + # srs = [@transport_reaction dX X] + # Create LatticeReactionSystem + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_3d_grid) + + # Create JumpProblem + u0 = [:X => 1, :Y => rand(1:10, lrs.num_verts)] + tspan = (0.0, 100.0) + ps = [:A => 1.0, :B => 5.0 .+ rand(lrs.num_verts), :dX => rand(lrs.num_edges)] + dprob = DiscreteProblem(lrs, u0, tspan, ps) + jprob = JumpProblem(lrs, dprob, NSM()) + + # Checks internal structures. + jprob.massaction_jump.uniform_rates == [1.0, 0.5 ,10.] # 0.5 is due to combinatoric /2! in (2X + Y). + jprob.massaction_jump.spatial_rates[1,:] == ps[2][2] + # Test when new SII functions are ready, or we implement them in Catalyst. + # @test isequal(to_int(getfield.(reactions(lrs.rs), :netstoich)), jprob.massaction_jump.net_stoch) + # @test isequal(to_int(Pair.(getfield.(reactions(lrs.rs), :substrates),getfield.(reactions(lrs.rs), :substoich))), jprob.massaction_jump.net_stoch) + + # Checks that problem can be simulated. + @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) +end + +# Checks that simulations gives a correctly heterogeneous solution. +let + # Create model. + birth_death_network = @reaction_network begin + (p,d), 0 <--> X + end + srs = [(@transport_reaction D X)] + lrs = LatticeReactionSystem(birth_death_network, srs, very_small_2d_grid) + + # Create JumpProblem. + u0 = [:X => 1] + tspan = (0.0, 100.0) + ps = [:p => [0.1, 1.0, 10.0, 100.0], :d => 1.0, :D => 0.0] + dprob = DiscreteProblem(lrs, u0, tspan, ps) + jprob = JumpProblem(lrs, dprob, NSM()) + + # Simulate model (a few repeats to ensure things don't succeed by change for uniform rates). + # Check that higher p gives higher mean. + for i = 1:5 + sol = solve(jprob, SSAStepper(); saveat = 1., seed = i*1234) + @test mean(getindex.(sol.u, 1)) < mean(getindex.(sol.u, 2)) < mean(getindex.(sol.u, 3)) < mean(getindex.(sol.u, 4)) + end +end + + ### Tests taken from JumpProcesses ### # ABC Model Test From 89fcdf30d9c2c3916378c69a9adf1805655153b1 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 25 Jan 2024 17:42:34 -0500 Subject: [PATCH 068/446] test up --- .../lattice_reaction_systems_jumps.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 6721e707cd..8fc019d8bf 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -87,9 +87,8 @@ let jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - @test jprob.massaction_jump.scaled_rates == true_maj_scaled_rates @test jprob.massaction_jump.reactant_stoch == true_maj_reactant_stoch - @test jprob.massaction_jump.net_stoch == true_maj_net_stoch + @test all(issetequal(ns1, ns2) for (ns1, ns2) in zip(jprob.massaction_jump.net_stoch, true_maj_net_stoch)) end # Provides parameters as a combined vector. for pV in [pV_1], pE in [pE_1, pE_2] @@ -97,9 +96,8 @@ let jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - @test jprob.massaction_jump.scaled_rates == true_maj_scaled_rates @test jprob.massaction_jump.reactant_stoch == true_maj_reactant_stoch - @test jprob.massaction_jump.net_stoch == true_maj_net_stoch + @test all(issetequal(ns1, ns2) for (ns1, ns2) in zip(jprob.massaction_jump.net_stoch, true_maj_net_stoch)) end end end From 16938691527001b74a5608ea2119586631e01d58 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 16 May 2024 08:41:50 -0400 Subject: [PATCH 069/446] update for new MTK Parameter structure --- .../lattice_jump_systems.jl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 6287936889..818da05d1a 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -2,7 +2,9 @@ # Builds a spatial DiscreteProblem from a Lattice Reaction System. function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) - is_transport_system(lrs) || error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") + if !is_transport_system(lrs) + error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") + end # Converts potential symmaps to varmaps # Vertex and edge parameters may be given in a tuple, or in a common vector, making parameter case complicated. @@ -18,20 +20,20 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_ # These elements are length 1 vectors (if the parameter is uniform), # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) - + # Returns a DiscreteProblem. # Previously, a Tuple was used for (vert_ps, edge_ps), but this was converted to a Vector internally. - return DiscreteProblem(lrs.rs, u0, tspan, [vert_ps, edge_ps], args...; kwargs...) + return DiscreteProblem(u0, tspan, [vert_ps, edge_ps], args...; kwargs...) end # Builds a spatial JumpProblem from a DiscreteProblem containg a Lattice Reaction System. function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(lrs.rs), combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), kwargs...) # Error checks. - # The second check (Vector{Vector} is needed because on the CI server somehow the Tuple{..., ...} is converted into a Vector[..., ...]). - # It does not happen when I run tests locally, so no ideal how to fix. - (dprob.p isa Vector{Vector{Vector{Float64}}}) || dprob.p isa Vector{Vector} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") - + if !isnothing(dprob.f.sys) + error("Unexpected `DiscreteProblem` passed into `JumpProblem`. Was a `LatticeReactionSystem` used as input to the initial `DiscreteProblem`?") + end + # Computes hopping constants and mass action jumps (requires some internal juggling). # Currently, JumpProcesses requires uniform vertex parameters (hence `p=first.(dprob.p[1])`). # Currently, the resulting JumpProblem does not depend on parameters (no way to incorporate these). From 09bd10a0d365df5c67c2786d9f95fef1b0b2371c Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 31 May 2024 14:28:27 -0400 Subject: [PATCH 070/446] rebase fix --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index 8f255921d7..9196ca91c8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -61,6 +61,7 @@ using SafeTestsets, Test @time @safetestset "PDE Systems Simulations" begin include("spatial_modelling/simulate_PDEs.jl") end @time @safetestset "Lattice Reaction Systems" begin include("spatial_modelling/lattice_reaction_systems.jl") end @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_ODEs.jl") end + @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end #end #if GROUP == "All" || GROUP == "Visualisation-Extensions" From 67ab88808507ec639466c2f50a0a53eaf3948324 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 31 May 2024 16:02:51 -0400 Subject: [PATCH 071/446] up --- docs/src/assets/Project.toml | 1 + docs/src/model_simulation/simulation_introduction.md | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index 246727a23e..ebb086fda6 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -5,6 +5,7 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DiffEqParamEstim = "1130ab10-4a5a-5621-a13d-e4788d82bd4c" +DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DynamicalSystems = "61744808-ddfa-5f27-97ff-6e42cc95d634" diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index 6234dc4a0a..81894ac74e 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -210,6 +210,9 @@ Next, let us consider a simulation for another parameter set: ```@example simulation_intro_sde sprob = remake(sprob; u0 = [:X1 => 100.0, :X2 => 200.0], p = [:k1 => 200.0, :k2 => 500.0]) sol = solve(sprob, STrapezoid()) +nothing # hide +``` +```@example simulation_intro_sde sol = solve(sprob, STrapezoid(); seed = 12345) # hide plot(sol) ``` @@ -317,9 +320,9 @@ plot(sol) ### [Designating aggregators and simulation methods for jump simulations](@id simulation_intro_jumps_solver_designation) Jump simulations (just like ODEs and SDEs) are performed using solver methods. Unlike ODEs and SDEs, jump simulations are carried out by two different types of methods acting in tandem. First, an *aggregator* method is used to (after each reaction) determine the time to, and type of, the next reaction. Next, a simulation method is used to actually carry out the simulation. -Several different aggregators are available (a full list is provided [here](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-for-Exact-Simulation)). To designate a specific one, provide it as the third argument to the `JumpProblem`. E.g. to designate that Gillespie's direct method (`Direct`) should be used, use: +Several different aggregators are available (a full list is provided [here](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-for-Exact-Simulation)). To designate a specific one, provide it as the third argument to the `JumpProblem`. E.g. to designate that the sorting direct method (`SortingDirect`) should be used, use: ```@example simulation_intro_jumps -jprob = JumpProblem(two_state_model, dprob, Direct()) +jprob = JumpProblem(two_state_model, dprob, SortingDirect()) nothing # hide ``` Especially for large systems, the choice of aggregator is relevant to simulation performance. A guide for aggregator selection is provided [here](@ref ref). From abd6dcafddb1ddabdd7598f4229c24401e77d0f7 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 31 May 2024 16:03:41 -0400 Subject: [PATCH 072/446] up --- docs/pages.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index 96e6587bf3..941e906c3a 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -18,7 +18,7 @@ pages = Any[ "model_creation/network_analysis.md", "model_creation/chemistry_related_functionality.md", "Model creation examples" => Any[ - "model_creation/examples/basic_CRN_library.md", + #"model_creation/examples/basic_CRN_library.md", "model_creation/examples/programmatic_generative_linear_pathway.md", "model_creation/examples/hodgkin_huxley_equation.md", "model_creation/examples/smoluchowski_coagulation_equation.md" @@ -29,9 +29,9 @@ pages = Any[ # Simulation introduction. "model_simulation/simulation_plotting.md", "model_simulation/simulation_structure_interfacing.md", - "model_simulation/ensemble_simulations.md", + #"model_simulation/ensemble_simulations.md", # Stochastic simulation statistical analysis. - "model_simulation/ode_simulation_performance.md", + #"model_simulation/ode_simulation_performance.md", # ODE Performance considerations/advice. # SDE Performance considerations/advice. # Jump Performance considerations/advice. @@ -39,7 +39,7 @@ pages = Any[ ], "Steady state analysis" => Any[ "steady_state_functionality/homotopy_continuation.md", - "steady_state_functionality/nonlinear_solve.md", + #"steady_state_functionality/nonlinear_solve.md", "steady_state_functionality/steady_state_stability_computation.md", "steady_state_functionality/bifurcation_diagrams.md", "steady_state_functionality/dynamical_systems.md" From fdb9cc0fe331d711679dc7d8e9a00763260492cf Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 16:32:25 -0400 Subject: [PATCH 073/446] changed reactionparams to parameters --- test/network_analysis/network_properties.jl | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/network_analysis/network_properties.jl b/test/network_analysis/network_properties.jl index a716d04c36..5507a9f655 100644 --- a/test/network_analysis/network_properties.jl +++ b/test/network_analysis/network_properties.jl @@ -44,7 +44,7 @@ let @test Catalyst.get_networkproperties(MAPK).rank == 15 k = rand(rng, numparams(MAPK)) - rates = Dict(zip(reactionparams(MAPK), k)) + rates = Dict(zip(parameters(MAPK), k)) @test Catalyst.iscomplexbalanced(MAPK, rates) == false # i=0; # for lcs in linkageclasses(MAPK) @@ -85,7 +85,7 @@ let @test Catalyst.get_networkproperties(rn2).rank == 6 k = rand(rng, numparams(rn2)) - rates = Dict(zip(reactionparams(rn2), k)) + rates = Dict(zip(parameters(rn2), k)) @test Catalyst.iscomplexbalanced(rn2, rates) == false # i=0; # for lcs in linkageclasses(rn2) @@ -129,7 +129,7 @@ let @test Catalyst.get_networkproperties(rn3).rank == 10 k = rand(rng, numparams(rn3)) - rates = Dict(zip(reactionparams(rn3), k)) + rates = Dict(zip(parameters(rn3), k)) @test Catalyst.iscomplexbalanced(rn3, rates) == false # i=0; # for lcs in linkageclasses(rn3) @@ -154,7 +154,7 @@ let end k = rand(rng, numparams(rn4)) - rates = Dict(zip(reactionparams(rn4), k)) + rates = Dict(zip(parameters(rn4), k)) @test Catalyst.iscomplexbalanced(rn4, rates) == true end @@ -182,7 +182,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end @@ -200,7 +200,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end let @@ -212,7 +212,7 @@ let weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end let @@ -226,7 +226,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end let @@ -241,7 +241,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == true end let @@ -254,7 +254,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end let @@ -267,7 +267,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == true end let @@ -277,7 +277,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == true end let @@ -292,7 +292,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == true end let @@ -307,7 +307,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end @@ -322,7 +322,7 @@ let end k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == true end From 8d2d6be784ed9e1b94e3ab2f699191df3a9b24e9 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 27 Dec 2023 17:21:35 +0100 Subject: [PATCH 074/446] init --- .../catalyst_for_new_julia_users.md | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md index 82c297b33d..f56fc260fc 100644 --- a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md +++ b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md @@ -54,15 +54,24 @@ To import a Julia package into a session, you can use the `using PackageName` co ```julia using Pkg Pkg.add("Catalyst") +using Catalyst ``` Here, the Julia package manager package (`Pkg`) is by default installed on your computer when Julia is installed, and can be activated directly. Next, we also wish to install the `DifferentialEquations` and `Plots` packages (for numeric simulation of models, and plotting, respectively). ```julia -Pkg.add("DifferentialEquations") -Pkg.add("Plots") +using Pkg +Pkg.activate(".") ``` Once a package has been installed through the `Pkg.add` command, this command does not have to be repeated if we restart our Julia session. We can now import all three packages into our current session with: ```@example ex2 using Catalyst +``` +This will only make Catalyst available for the current Julia session. If you exit Julia, you will have to run `using Catalyst` again to use its features (however, `Pkg.add("Catalyst")` does not need to be rerun). Next, we wish to install the `DifferentialEquations` and `Plots` packages (for numeric simulation of models, and plotting, respectively): +```julia +Pkg.add("DifferentialEquations") +Pkg.add("Plots") +``` +and also to import them into our current session: +```@example ex2 using DifferentialEquations using Plots ``` @@ -70,6 +79,30 @@ Here, if we restart Julia, these `using` commands *must be rerun*. A more comprehensive (but still short) introduction to package management in Julia is provided at [the end of this documentation page](@ref catalyst_for_new_julia_users_packages). It contains some useful information and is hence highly recommended reading. For a more detailed introduction to Julia package management, please read [the Pkg documentation](https://docs.julialang.org/en/v1/stdlib/Pkg/). +Here, while Catalyst has previously been installed on your computer, it has not been added to the new environment you created. To do so, simply run +```julia +using Pkg +Pkg.add("Catalyst") +``` +after which Catalyst can be imported through `using Catalyst`. You can get a list of all packages available in your current environment using: +```julia +Pkg.status() +``` + +So, why is this required, and why cannot we simply import any package installed on our computer? The reason is that most packages depend on other packages, and these dependencies may be restricted to only specific versions of these packages. This creates complicated dependency graphs that restrict what versions of what packages are compatible with each other. When you use `Pkg.add("PackageName")`, only a specific version of that package is actually added (the latest possible version as permitted by the dependency graph). Here, Julia environments both define what packages are available *and* their respective versions (these versions are also displayed by the `Pkg.status()` command). By doing this, Julia can guarantee that the packages (and their versions) specified in an environment are compatible with each other. + +The reason why all this is important is that it is *highly recommended* to, for each project, define a separate environment. To these, only add the required packages. General-purpose environments with a large number of packages often produce package-incompatibility issues. While these might not prevent you from installing all desired package, they often mean that you are unable to use the latest version of some packages. + +!!! note + A not-infrequent cause for reported errors with Catalyst (typically the inability to replicate code in tutorials) is package incompatibilities in large environments preventing the latest version of Catalyst from being installed. Hence, whenever an issue is encountered, it is useful to run `Pkg.status()` to check whenever the latest version of Catalyst is being used. + +Some additional useful Pkg commands are: +- `Pk.rm("PackageName")` removes a package from the current environment. +- `Pkg.update(PackageName")`: updates the designated package. + +!!! note + A useful feature of Julia's environment system is that enables the exact definition of what packages and versions were used to execute a script. This supports e.g. reproducibility in academic research. Here, by providing the corresponding Project.toml and Manifest.toml files, you can enable someone to reproduce the exact program just to perform some set of analyses. + ## Simulating a basic Catalyst model Now that we have some basic familiarity with Julia, and have installed and imported the required packages, we will create and simulate a basic chemical reaction network model using Catalyst. From b239bcf38d35102361a74c8b3471f2acd803c4c0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 29 Dec 2023 12:06:53 +0100 Subject: [PATCH 075/446] up --- .../catalyst_for_new_julia_users.md | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md index f56fc260fc..3c3a529dfd 100644 --- a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md +++ b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md @@ -70,8 +70,9 @@ This will only make Catalyst available for the current Julia session. If you exi Pkg.add("DifferentialEquations") Pkg.add("Plots") ``` -and also to import them into our current session: +Once a package has been installed through the `Pkg.add` command, this command does not have to be repeated if we restart our Julia session. We can now import all three packages into our current session with: ```@example ex2 +using Catalyst using DifferentialEquations using Plots ``` @@ -79,30 +80,6 @@ Here, if we restart Julia, these `using` commands *must be rerun*. A more comprehensive (but still short) introduction to package management in Julia is provided at [the end of this documentation page](@ref catalyst_for_new_julia_users_packages). It contains some useful information and is hence highly recommended reading. For a more detailed introduction to Julia package management, please read [the Pkg documentation](https://docs.julialang.org/en/v1/stdlib/Pkg/). -Here, while Catalyst has previously been installed on your computer, it has not been added to the new environment you created. To do so, simply run -```julia -using Pkg -Pkg.add("Catalyst") -``` -after which Catalyst can be imported through `using Catalyst`. You can get a list of all packages available in your current environment using: -```julia -Pkg.status() -``` - -So, why is this required, and why cannot we simply import any package installed on our computer? The reason is that most packages depend on other packages, and these dependencies may be restricted to only specific versions of these packages. This creates complicated dependency graphs that restrict what versions of what packages are compatible with each other. When you use `Pkg.add("PackageName")`, only a specific version of that package is actually added (the latest possible version as permitted by the dependency graph). Here, Julia environments both define what packages are available *and* their respective versions (these versions are also displayed by the `Pkg.status()` command). By doing this, Julia can guarantee that the packages (and their versions) specified in an environment are compatible with each other. - -The reason why all this is important is that it is *highly recommended* to, for each project, define a separate environment. To these, only add the required packages. General-purpose environments with a large number of packages often produce package-incompatibility issues. While these might not prevent you from installing all desired package, they often mean that you are unable to use the latest version of some packages. - -!!! note - A not-infrequent cause for reported errors with Catalyst (typically the inability to replicate code in tutorials) is package incompatibilities in large environments preventing the latest version of Catalyst from being installed. Hence, whenever an issue is encountered, it is useful to run `Pkg.status()` to check whenever the latest version of Catalyst is being used. - -Some additional useful Pkg commands are: -- `Pk.rm("PackageName")` removes a package from the current environment. -- `Pkg.update(PackageName")`: updates the designated package. - -!!! note - A useful feature of Julia's environment system is that enables the exact definition of what packages and versions were used to execute a script. This supports e.g. reproducibility in academic research. Here, by providing the corresponding Project.toml and Manifest.toml files, you can enable someone to reproduce the exact program just to perform some set of analyses. - ## Simulating a basic Catalyst model Now that we have some basic familiarity with Julia, and have installed and imported the required packages, we will create and simulate a basic chemical reaction network model using Catalyst. From 61ff1f644c3a001a12710f937cc8bb44c26d7dd3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 29 Dec 2023 15:17:15 +0100 Subject: [PATCH 076/446] init --- .../lattice_reaction_systems.jl | 76 +++++++++++++++---- .../lattice_reaction_systems_lattice_types.jl | 8 ++ 2 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index a153a0b2a6..faac2d70f5 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,14 +1,14 @@ ### Lattice Reaction Network Structure ### # Describes a spatial reaction network over a graph. # Adding the "<: MT.AbstractTimeDependentSystem" part messes up show, disabling me from creating LRSs. -struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem +struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem # Input values. """The reaction system within each compartment.""" - rs::ReactionSystem{S} + rs::ReactionSystem{Q} """The spatial reactions defined between individual nodes.""" - spatial_reactions::Vector{T} + spatial_reactions::Vector{R} """The graph on which the lattice is defined.""" - lattice::SimpleDiGraph{Int64} + lattice::S # Derived values. """The number of compartments.""" @@ -17,8 +17,7 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem num_edges::Int64 """The number of species.""" num_species::Int64 - """Whenever the initial input was a digraph.""" - init_digraph::Bool + """Species that may move spatially.""" spat_species::Vector{BasicSymbolic{Real}} """ @@ -37,19 +36,29 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem """ edge_parameters::Vector{BasicSymbolic{Real}} - function LatticeReactionSystem(rs::ReactionSystem{S}, spatial_reactions::Vector{T}, - lattice::DiGraph; init_digraph = true) where {S, T} - # There probably some better way to ascertain that T has that type. Not sure how. - if !(T <: AbstractSpatialReaction) + """Whenever the initial input was a digraph.""" + init_digraph::Bool + """ + A list of all the edges on the lattice. + The format depends on the type of lattice (Cartesian grid, grid, or graph). + """ + edge_list::T + + function LatticeReactionSystem(rs::ReactionSystem{Q}, spatial_reactions::Vector{R}, + lattice::S, num_verts, edge_list::T; init_digraph = true) where {Q, R, S, T} + # Error checks. + if !(R <: AbstractSpatialReaction) | error("The second argument must be a vector of AbstractSpatialReaction subtypes.") end + # Computes derived values spatial species. Counts the total number of species. if isempty(spatial_reactions) spat_species = Vector{BasicSymbolic{Real}}[] else spat_species = unique(reduce(vcat, [spatial_species(sr) for sr in spatial_reactions])) end - num_species = length(unique([species(rs); spat_species])) + + # Computes the sets of vertex, edge, and all, parameters. rs_edge_parameters = filter(isedgeparameter, parameters(rs)) if isempty(spatial_reactions) srs_edge_parameters = Vector{BasicSymbolic{Real}}[] @@ -58,16 +67,46 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem end edge_parameters = unique([rs_edge_parameters; srs_edge_parameters]) vertex_parameters = filter(!isedgeparameter, parameters(rs)) + + # Counts the number of edges and species types (number of vertexes already counted). + num_edges = false + num_species = length(unique([species(rs); spat_species])) + # Ensures the parameter order begins similarly to in the non-spatial ReactionSystem. ps = [parameters(rs); setdiff([edge_parameters; vertex_parameters], parameters(rs))] + # Checks that all spatial reactions are valid for this reactions system. foreach(sr -> check_spatial_reaction_validity(rs, sr; edge_parameters=edge_parameters), spatial_reactions) - return new{S,T}(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), num_species, - init_digraph, spat_species, ps, vertex_parameters, edge_parameters) + + return new{Q,R,S,T}(rs, spatial_reactions, lattice, num_verts, ne(lattice), num_species, + spat_species, ps, vertex_parameters, edge_parameters, init_digraph, edge_list) end end -function LatticeReactionSystem(rs, srs, lat::SimpleGraph) - return LatticeReactionSystem(rs, srs, DiGraph(lat); init_digraph = false) + +# Creates a LatticeReactionSystem from a CartesianGrid lattice. +function LatticeReactionSystem(rs, srs, lattice::CartesianGridRej{S,T}; diagonal_connections=false) where {S,T} + num_verts = prod(lattice.dims) + edge_list = false + return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; init_digraph = false, edge_list) +end + +# Creates a LatticeReactionSystem from a Boolean Array lattice. +function LatticeReactionSystem(rs, srs, lattice::Array{Bool, T}; diagonal_connections=false) where {T} + num_verts = count(lattice) + edge_list = false + return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; init_digraph = false, edge_list) +end + +# Creates a LatticeReactionSystem from a DiGraph lattice. +function LatticeReactionSystem(rs, srs, lattice::SimpleGraph) + num_verts = nv(lattice) + edge_list = false + return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; init_digraph = false, edge_list) +end + +# Creates a LatticeReactionSystem from a Graph lattice. +function LatticeReactionSystem(rs, srs, lattice::SimpleGraph) + return LatticeReactionSystem(rs, srs, DiGraph(lattice); init_digraph = false) end ### Lattice ReactionSystem Getters ### @@ -89,3 +128,10 @@ ModelingToolkit.nameof(lrs::LatticeReactionSystem) = nameof(lrs.rs) # Checks if a lattice reaction system is a pure (linear) transport reaction system. is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReaction, lrs.spatial_reactions) + +# Checks if a LatticeReactionSystem have a Cartesian grid lattice. +has_cartesian_lattice(lrs::LatticeReactionsSystem) = lrs.lattice isa CartesianGridRej{S,T} where {S,T} +# Checks if a LatticeReactionSystem have a regular grid lattice. +has_cartesian_lattice(lrs::LatticeReactionsSystem) = lrs.lattice isa Array{Bool, T} where T +# Checks if a LatticeReactionSystem have a graph lattice. +has_cartesian_lattice(lrs::LatticeReactionsSystem) = lrs.lattice isa SimpleDiGraph \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl new file mode 100644 index 0000000000..4c13fa6779 --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl @@ -0,0 +1,8 @@ +### Preparations ### + +# Fetch packages. +using Catalyst, Graphs, OrdinaryDiffEq, Test + +### Run Tests ### + +# Checks that some grids, created using different approaches, generates the same spatial structures. \ No newline at end of file From 341424343f24cbafdbf004c68044582956ddc862 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 29 Dec 2023 15:17:28 +0100 Subject: [PATCH 077/446] add test run --- test/runtests.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 9196ca91c8..079c8a33e7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -56,13 +56,11 @@ using SafeTestsets, Test @time @safetestset "MTK Problem Inputs" begin include("upstream/mtk_problem_inputs.jl") end #end - #if GROUP == "All" || GROUP == "Spatial" - # Tests spatial modelling and simulations. @time @safetestset "PDE Systems Simulations" begin include("spatial_modelling/simulate_PDEs.jl") end @time @safetestset "Lattice Reaction Systems" begin include("spatial_modelling/lattice_reaction_systems.jl") end - @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_ODEs.jl") end + @time @safetestset "Lattice Reaction Systems Lattice Types" begin include("spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl") end + @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_ODEs.jl") end @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end - #end #if GROUP == "All" || GROUP == "Visualisation-Extensions" # Tests network visualisation. From 64cc18683cdba747e2b5cc6916593ff46dd46995 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 31 Dec 2023 14:23:46 +0100 Subject: [PATCH 078/446] init --- .../lattice_reaction_systems.jl | 72 ++++++++++++++----- 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index faac2d70f5..cb6b4afbd4 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,14 +1,14 @@ ### Lattice Reaction Network Structure ### # Describes a spatial reaction network over a graph. # Adding the "<: MT.AbstractTimeDependentSystem" part messes up show, disabling me from creating LRSs. -struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem +struct LatticeReactionSystem{R,S,T} # <: MT.AbstractTimeDependentSystem # Input values. """The reaction system within each compartment.""" - rs::ReactionSystem{Q} + rs::ReactionSystem{R} """The spatial reactions defined between individual nodes.""" - spatial_reactions::Vector{R} + spatial_reactions::Vector{S} """The graph on which the lattice is defined.""" - lattice::S + lattice::T # Derived values. """The number of compartments.""" @@ -36,18 +36,23 @@ struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem """ edge_parameters::Vector{BasicSymbolic{Real}} - """Whenever the initial input was a digraph.""" - init_digraph::Bool + """ + Whenever the initial input was directed. True for Digraph input, false for other graphs and all grids. + Used later to know whenever duplication of edge parameter should be duplicated + (i.e. allow parameter for edge i => j to be used for j => i). + Even if false, giving separate parameters for both directions is still permitted. + """ + directed_edges::Bool """ A list of all the edges on the lattice. The format depends on the type of lattice (Cartesian grid, grid, or graph). """ - edge_list::T + edge_list::Vector{Vector{Pair{Int64,Int64}}} - function LatticeReactionSystem(rs::ReactionSystem{Q}, spatial_reactions::Vector{R}, - lattice::S, num_verts, edge_list::T; init_digraph = true) where {Q, R, S, T} + function LatticeReactionSystem(rs::ReactionSystem{R}, spatial_reactions::Vector{S}, + lattice::S, num_verts, edge_list::T; directed_edges = false) where {R, S, T} # Error checks. - if !(R <: AbstractSpatialReaction) | + if !(S <: AbstractSpatialReaction) | error("The second argument must be a vector of AbstractSpatialReaction subtypes.") end @@ -85,28 +90,61 @@ end # Creates a LatticeReactionSystem from a CartesianGrid lattice. function LatticeReactionSystem(rs, srs, lattice::CartesianGridRej{S,T}; diagonal_connections=false) where {S,T} + (length(lattice.dims) > 3) && error("Grids of higher dimension than 3 is currently not supported.") + diagonal_connections && error("Diagonal connections is currently unsupported for Cartesian grid lattices.") num_verts = prod(lattice.dims) + + # Prepares the list of edges. + edge_list = [Vector{Pair{Int64,Int64}}() for i in 1:num_verts] + + # Ensures that the matrix is on a 3d form + lattice = reshape(vals,dims..., fill(1,3- length(dims))...) + + edge_list = false - return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; init_digraph = false, edge_list) + return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; edge_list) end # Creates a LatticeReactionSystem from a Boolean Array lattice. -function LatticeReactionSystem(rs, srs, lattice::Array{Bool, T}; diagonal_connections=false) where {T} +function LatticeReactionSystem(rs, srs, lattice::Array{Bool, T}; diagonal_connections=false) where {T} + dims = size(vals) + (length(dims) > 3) && error("Grids of higher dimension than 3 is currently not supported.") + diagonal_connections && error("Diagonal connections is currently unsupported for grid lattices.") num_verts = count(lattice) - edge_list = false - return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; init_digraph = false, edge_list) + + # Prepares the list of edges. + edge_list = [Vector{Pair{Int64,Int64}}() for i in 1:num_verts] + + # Ensures that the matrix is on a 3d form + lattice = CartesianGrid((lattice.dims..., fill(1,3- length(lattice.dims))...)) + + # Loops through, simultaneously, the coordinates of each position in the grid, as well as that + # coordinate's (scalar) flat index. + indices = [(i, j, k) for i in 1:lattice.dims[1], j in 1:lattice.dims[2], k in 1:lattice.dims[3]] + flat_indices = 1:prod(lattice.dims) + for ((i,j,k), idx) in zip(indices, flat_indices) + for ii in i-1:i+1, jj in j-1:j+1, kk in k-1:k+1 + # Finds the (scalar) flat index of this neighbour. If it is a valid neighbour, add it to edge_list. + n_idx = (k - 1) * (l * m) + (j - 1) * l + i + (1 <= n_idx <= flat_indices[end]) || continue + (n_idx == idx) && continue + push!(edge_list[cur_vert], n_idx) + end + end + + return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; edge_list) end # Creates a LatticeReactionSystem from a DiGraph lattice. -function LatticeReactionSystem(rs, srs, lattice::SimpleGraph) +function LatticeReactionSystem(rs, srs, lattice::SimpleGraph; directed_edges = true) num_verts = nv(lattice) edge_list = false - return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; init_digraph = false, edge_list) + return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; directed_edges, edge_list) end # Creates a LatticeReactionSystem from a Graph lattice. function LatticeReactionSystem(rs, srs, lattice::SimpleGraph) - return LatticeReactionSystem(rs, srs, DiGraph(lattice); init_digraph = false) + return LatticeReactionSystem(rs, srs, DiGraph(lattice); directed_edges) end ### Lattice ReactionSystem Getters ### From da0b3f10a2f920942fa794b4f1ab279027ddd840 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 31 Dec 2023 15:22:25 +0100 Subject: [PATCH 079/446] add tests for lattice types --- .../lattice_reaction_systems.jl | 6 +- .../lattice_reaction_systems_ODEs.jl | 5 +- .../lattice_reaction_systems_lattice_types.jl | 69 ++++++++++++++++++- test/spatial_test_networks.jl | 32 +++++++-- 4 files changed, 102 insertions(+), 10 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index cb6b4afbd4..e8e4d2cae0 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -47,7 +47,7 @@ struct LatticeReactionSystem{R,S,T} # <: MT.AbstractTimeDependentSystem A list of all the edges on the lattice. The format depends on the type of lattice (Cartesian grid, grid, or graph). """ - edge_list::Vector{Vector{Pair{Int64,Int64}}} + edge_list::Vector{Vector{Int64}} function LatticeReactionSystem(rs::ReactionSystem{R}, spatial_reactions::Vector{S}, lattice::S, num_verts, edge_list::T; directed_edges = false) where {R, S, T} @@ -95,7 +95,7 @@ function LatticeReactionSystem(rs, srs, lattice::CartesianGridRej{S,T}; diagonal num_verts = prod(lattice.dims) # Prepares the list of edges. - edge_list = [Vector{Pair{Int64,Int64}}() for i in 1:num_verts] + edge_list = [Vector{Int64}() for i in 1:num_verts] # Ensures that the matrix is on a 3d form lattice = reshape(vals,dims..., fill(1,3- length(dims))...) @@ -113,7 +113,7 @@ function LatticeReactionSystem(rs, srs, lattice::Array{Bool, T}; diagonal_connec num_verts = count(lattice) # Prepares the list of edges. - edge_list = [Vector{Pair{Int64,Int64}}() for i in 1:num_verts] + edge_list = [Vector{Int64}() for i in 1:num_verts] # Ensures that the matrix is on a 3d form lattice = CartesianGrid((lattice.dims..., fill(1,3- length(lattice.dims))...)) diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index cdefc6ebc9..ec9f7d79d2 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -15,7 +15,10 @@ rng = StableRNG(12345) t = default_t() ### Tests Simulations Don't Error ### -for grid in [small_2d_grid, short_path, small_directed_cycle] +for grid in [small_2d_grid, short_path, small_directed_cycle, + small_1d_cartesian_grid, small_2d_cartesian_grid, small_3d_cartesian_grid, + small_1d_regular_grid, small_2d_regular_grid, small_3d_regular_grid, + random_2d_regular_grid] # Non-stiff case for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl index 4c13fa6779..d4687a16c7 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl @@ -5,4 +5,71 @@ using Catalyst, Graphs, OrdinaryDiffEq, Test ### Run Tests ### -# Checks that some grids, created using different approaches, generates the same spatial structures. \ No newline at end of file +# Checks that some grids, created using different approaches, generates the same spatial structures. +# Checks that some grids, created using different approaches, generates the same simulation output. +let + # Create LatticeReactionsSystems + cartesian_grid = Graphs.grid([5, 5]) + regular_grid = fill(true, 5, 5) + graph_grid = Graphs.grid([5, 5]) + + cartesian_lrs = LatticeReactionSystem(brusselator_system, srs, cartesian_grid) + regular_lrs = LatticeReactionSystem(brusselator_system, srs, regular_grid) + graph_lrs = LatticeReactionSystem(brusselator_system, srs, graph_grid) + + # Check internal structures. + @test cartesian_lrs.rs == regular_lrs.rs == graph_lrs.rs + @test cartesian_lrs.spatial_reactions == regular_lrs.spatial_reactions == graph_lrs.spatial_reactions + @test cartesian_lrs.num_verts == regular_lrs.num_verts == graph_lrs.num_verts + @test cartesian_lrs.num_edges == regular_lrs.num_edges == graph_lrs.num_edges + @test cartesian_lrs.num_species == regular_lrs.num_species == graph_lrs.num_species + @test cartesian_lrs.spat_species == regular_lrs.spat_species == graph_lrs.spat_species + @test cartesian_lrs.parameters == regular_lrs.parameters == graph_lrs.parameters + @test cartesian_lrs.vertex_parameters == regular_lrs.vertex_parameters == graph_lrs.vertex_parameters + @test cartesian_lrs.edge_parameters == regular_lrs.edge_parameters == graph_lrs.edge_parameters + @test cartesian_lrs.directed_edges == regular_lrs.directed_edges == graph_lrs.directed_edges + @test cartesian_lrs.edge_list == regular_lrs.edge_list == graph_lrs.edge_list + + # Checks that simulations yields the same output. + u0 = [:X => rand_v_vals(graph_lrs.lattice, 10.0), :Y => 2.0] + pV = [:A => 0.5 .+ rand_v_vals(graph_lrs.lattice, 0.5), :B => 4.0] + pE= map(sp -> sp => 0.2, spatial_param_syms(lrs)) + + cartesian_oprob = ODEProblem(cartesian_lrs, u0, (0.0, 100.0), (pV, pE)) + regular_oprob = ODEProblem(regular_lrs, u0, (0.0, 100.0), (pV, pE)) + graph_oprob = ODEProblem(graph_lrs, u0, (0.0, 100.0), (pV, pE)) + + cartesian_sol = solve(cartesian_oprob, QNDF(); saveat=0.1) + regular_sol = solve(regular_oprob, QNDF(); saveat=0.1) + graph_sol = solvegraph(_oprob, QNDF(); saveat=0.1) + + @test cartesian_sol.u ≈ regular_sol.u ≈ graph_sol +end + +# Checks that a regular grid with absent vertices generate the same output as corresponding graph. +let + # Create LatticeReactionsSystems + regular_grid = [true true true; true false true; true true true] + graph_grid = cycle_graph(8) + + regular_lrs = LatticeReactionSystem(brusselator_system, srs, regular_grid) + graph_lrs = LatticeReactionSystem(brusselator_system, srs, graph_grid) + + # Check internal structures. + @test regular_lrs.num_verts == graph_lrs.num_verts + @test regular_lrs.num_edges == graph_lrs.num_edges + @test regular_lrs.edge_list == graph_lrs.edge_list + + # Checks that simulations yields the same output. + u0 = [:X => rand_v_vals(graph_lrs.lattice, 10.0), :Y => 2.0] + pV = [:A => 0.5 .+ rand_v_vals(graph_lrs.lattice, 0.5), :B => 4.0] + pE= map(sp -> sp => 0.2, spatial_param_syms(lrs)) + + regular_oprob = ODEProblem(regular_lrs, u0, (0.0, 100.0), (pV, pE)) + graph_oprob = ODEProblem(graph_lrs, u0, (0.0, 100.0), (pV, pE)) + + regular_sol = solve(regular_oprob, QNDF(); saveat=0.1) + graph_sol = solvegraph(_oprob, QNDF(); saveat=0.1) + + @test regular_sol.u ≈ graph_sol +end \ No newline at end of file diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index f2f9472e87..813466754c 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -168,7 +168,29 @@ sigmaB_srs_2 = [sigmaB_tr_σB, sigmaB_tr_w, sigmaB_tr_v] ### Declares Lattices ### -# Grids. +# Cartesian grids. +small_1d_cartesian_grid = CartesianGrid(5) +small_2d_cartesian_grid = CartesianGrid((5,5)) +small_3d_cartesian_grid = CartesianGrid((5,5,5)) + +large_1d_cartesian_grid = CartesianGrid(100) +large_2d_cartesian_grid = CartesianGrid((100,100)) +large_3d_cartesian_grid = CartesianGrid((100,100,100)) + +# Regular grids. +small_1d_regular_grid = fill(true, 5) +small_2d_regular_grid = fill(true, 5, 5) +small_3d_regular_grid = fill(true, 5, 5, 5) + +large_1d_regular_grid = fill(true, 5) +large_2d_regular_grid = fill(true, 5, 5) +large_3d_regular_grid = fill(true, 5, 5, 5) + +random_1d_regular_grid = rand([true, true, true, false], 10) +random_2d_regular_grid = rand([true, true, true, false], 10, 10) +random_3d_regular_grid = rand([true, true, true, false], 10, 10, 10) + +# Graph - grids. very_small_2d_grid = Graphs.grid([2, 2]) small_2d_grid = Graphs.grid([5, 5]) medium_2d_grid = Graphs.grid([20, 20]) @@ -178,16 +200,16 @@ small_3d_grid = Graphs.grid([5, 5, 5]) medium_3d_grid = Graphs.grid([20, 20, 20]) large_3d_grid = Graphs.grid([100, 100, 100]) -# Paths. +# Graph - paths. short_path = path_graph(100) long_path = path_graph(1000) -# Unconnected graphs. +# Graph - unconnected graphs. unconnected_graph = SimpleGraph(10) -# Undirected cycle. +# Graph - undirected cycle. undirected_cycle = cycle_graph(49) -# Directed cycle. +# Graph - directed cycle. small_directed_cycle = cycle_graph(100) large_directed_cycle = cycle_graph(1000) \ No newline at end of file From 170b9f63a9ff2e0c41537fb5281c260cedea5684 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Jan 2024 17:04:06 +0100 Subject: [PATCH 080/446] up --- src/Catalyst.jl | 4 +++- src/spatial_reaction_systems/lattice_reaction_systems.jl | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 2aab60fb54..a69883aaa1 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -8,7 +8,8 @@ using SparseArrays, DiffEqBase, Reexport, Setfield using LaTeXStrings, Latexify, Requires using JumpProcesses: JumpProcesses, JumpProblem, MassActionJump, ConstantRateJump, VariableRateJump, - SpatialMassActionJump + SpatialMassActionJump, + CartesianGrid, CartesianGridRej # ModelingToolkit imports and convenience functions we use using ModelingToolkit @@ -175,6 +176,7 @@ export isedgeparameter include("spatial_reaction_systems/lattice_reaction_systems.jl") export LatticeReactionSystem export spatial_species, vertex_parameters, edge_parameters +export CartesianGrid, CartesianGridReJ # (Implemented in JumpProcesses) # Various utility functions include("spatial_reaction_systems/utility.jl") diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index e8e4d2cae0..dded97c22e 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -83,7 +83,7 @@ struct LatticeReactionSystem{R,S,T} # <: MT.AbstractTimeDependentSystem # Checks that all spatial reactions are valid for this reactions system. foreach(sr -> check_spatial_reaction_validity(rs, sr; edge_parameters=edge_parameters), spatial_reactions) - return new{Q,R,S,T}(rs, spatial_reactions, lattice, num_verts, ne(lattice), num_species, + return new{R,S,T}(rs, spatial_reactions, lattice, num_verts, ne(lattice), num_species, spat_species, ps, vertex_parameters, edge_parameters, init_digraph, edge_list) end end @@ -168,8 +168,8 @@ ModelingToolkit.nameof(lrs::LatticeReactionSystem) = nameof(lrs.rs) is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReaction, lrs.spatial_reactions) # Checks if a LatticeReactionSystem have a Cartesian grid lattice. -has_cartesian_lattice(lrs::LatticeReactionsSystem) = lrs.lattice isa CartesianGridRej{S,T} where {S,T} +has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa CartesianGridRej{S,T} where {S,T} # Checks if a LatticeReactionSystem have a regular grid lattice. -has_cartesian_lattice(lrs::LatticeReactionsSystem) = lrs.lattice isa Array{Bool, T} where T +has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa Array{Bool, T} where T # Checks if a LatticeReactionSystem have a graph lattice. -has_cartesian_lattice(lrs::LatticeReactionsSystem) = lrs.lattice isa SimpleDiGraph \ No newline at end of file +has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa SimpleDiGraph \ No newline at end of file From 4c4762ff99040a19bd64ebdde8e9c96fd10d83fe Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 3 Jan 2024 10:52:57 +0100 Subject: [PATCH 081/446] finish alternative grids --- .../lattice_reaction_systems.jl | 13 ++++- .../lattice_reaction_systems_lattice_types.jl | 48 ++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index dded97c22e..9cb81eda4c 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -170,6 +170,15 @@ is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReac # Checks if a LatticeReactionSystem have a Cartesian grid lattice. has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa CartesianGridRej{S,T} where {S,T} # Checks if a LatticeReactionSystem have a regular grid lattice. -has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa Array{Bool, T} where T +has_regular_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa Array{Bool, T} where T +# Checks if a LatticeReactionSystem have a grid lattice (cartesian or regular). +has_grid_lattice(lrs::LatticeReactionSystem) = (has_cartesian_lattice(lrs) || has_regular_lattice(lrs)) # Checks if a LatticeReactionSystem have a graph lattice. -has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa SimpleDiGraph \ No newline at end of file +has_graph_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa SimpleDiGraph + +# Returns the dimensions of a LatticeReactionNetwork with a grid lattice. +function grid_dims(lrs::LatticeReactionSystem) + has_cartesian_lattice(lrs) && (return length(lrs.lattice.dims)) + has_regular_lattice(lrs) && (return length(size(lrs.lattice))) + error("Dimensions are only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") +end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl index d4687a16c7..6869ac9f1f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl @@ -5,10 +5,54 @@ using Catalyst, Graphs, OrdinaryDiffEq, Test ### Run Tests ### +# Test errors when attempting to create networks with dimension > 3. +let + @test_throws Exception LatticeReactionSystem(brusselator_system, srs, CartesianGrid((5, 5, 5, 5))) + @test_throws Exception LatticeReactionSystem(brusselator_system, srs, fill(true, 5, 5, 5, 5)) +end + +# Checks that getter functions give the correct output. +let + # Create LatticeReactionsSystems. + cartesian_1d_lrs = LatticeReactionSystem(brusselator_system, srs, small_1d_cartesian_grid) + cartesian_2d_lrs = LatticeReactionSystem(brusselator_system, srs, small_2d_cartesian_grid) + cartesian_3d_lrs = LatticeReactionSystem(brusselator_system, srs, small_3d_cartesian_grid) + regular_1d_lrs = LatticeReactionSystem(brusselator_system, srs, small_1d_regular_grid) + regular_2d_lrs = LatticeReactionSystem(brusselator_system, srs, small_2d_regular_grid) + regular_3d_lrs = LatticeReactionSystem(brusselator_system, srs, small_3d_regular_grid) + graph_lrs = LatticeReactionSystem(brusselator_system, srs, small_2d_grid) + + # Test lattice type getters. + @test has_cartesian_grid_lattice(cartesian_2d_lrs) + @test !has_cartesian_grid_lattice(regular_2d_lrs) + @test !has_cartesian_grid_lattice(graph_lrs) + + @test !has_regular_grid_lattice(cartesian_2d_lrs) + @test has_regular_grid_lattice(regular_2d_lrs) + @test !has_regular_grid_lattice(graph_lrs) + + @test has_grid_lattice(cartesian_2d_lrs) + @test has_grid_lattice(regular_2d_lrs) + @test !has_grid_lattice(graph_lrs) + + @test !has_graph_lattice(cartesian_2d_lrs) + @test !has_graph_lattice(regular_2d_lrs) + @test has_graph_lattice(graph_lrs) + + # Checks grid dimensions. + @test grid_dims(cartesian_1d_lrs) == 1 + @test grid_dims(cartesian_2d_lrs) == 2 + @test grid_dims(cartesian_3d_lrs) == 3 + @test grid_dims(regular_1d_lrs) == 1 + @test grid_dims(regular_2d_lrs) == 2 + @test grid_dims(regular_3d_lrs) == 3 + @test_throws Exception grid_dims(graph_lrs) +end + # Checks that some grids, created using different approaches, generates the same spatial structures. # Checks that some grids, created using different approaches, generates the same simulation output. let - # Create LatticeReactionsSystems + # Create LatticeReactionsSystems. cartesian_grid = Graphs.grid([5, 5]) regular_grid = fill(true, 5, 5) graph_grid = Graphs.grid([5, 5]) @@ -48,7 +92,7 @@ end # Checks that a regular grid with absent vertices generate the same output as corresponding graph. let - # Create LatticeReactionsSystems + # Create LatticeReactionsSystems. regular_grid = [true true true; true false true; true true true] graph_grid = cycle_graph(8) From c6bd6af6b778fc3f21009bbfaeab6772a5ac9aeb Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 3 Jan 2024 10:53:59 +0100 Subject: [PATCH 082/446] add tests --- .../lattice_reaction_systems_lattice_types.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl index 6869ac9f1f..168711c9ff 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl @@ -23,13 +23,13 @@ let graph_lrs = LatticeReactionSystem(brusselator_system, srs, small_2d_grid) # Test lattice type getters. - @test has_cartesian_grid_lattice(cartesian_2d_lrs) - @test !has_cartesian_grid_lattice(regular_2d_lrs) - @test !has_cartesian_grid_lattice(graph_lrs) + @test has_cartesian_lattice(cartesian_2d_lrs) + @test has_cartesian_lattice(regular_2d_lrs) + @test has_cartesian_lattice(graph_lrs) - @test !has_regular_grid_lattice(cartesian_2d_lrs) - @test has_regular_grid_lattice(regular_2d_lrs) - @test !has_regular_grid_lattice(graph_lrs) + @test !has_regular_lattice(cartesian_2d_lrs) + @test !has_regular_lattice(regular_2d_lrs) + @test !has_regular_lattice(graph_lrs) @test has_grid_lattice(cartesian_2d_lrs) @test has_grid_lattice(regular_2d_lrs) From ed88b141caaed87a7d4c5a14d7373fe7e7c5e281 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 26 Jan 2024 12:40:13 -0500 Subject: [PATCH 083/446] finalise grid alternatives. --- .../lattice_reaction_systems.jl | 151 +++++---- .../lattice_reaction_systems.jl | 304 ++++++++++++++++++ .../lattice_reaction_systems_lattice_types.jl | 72 ++--- test/spatial_test_networks.jl | 24 +- 4 files changed, 448 insertions(+), 103 deletions(-) create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems.jl diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 9cb81eda4c..6b48175feb 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,14 +1,15 @@ ### Lattice Reaction Network Structure ### -# Describes a spatial reaction network over a graph. -# Adding the "<: MT.AbstractTimeDependentSystem" part messes up show, disabling me from creating LRSs. -struct LatticeReactionSystem{R,S,T} # <: MT.AbstractTimeDependentSystem + +# Describes a spatial reaction network over a lattice. +# Adding the "<: MT.AbstractTimeDependentSystem" part messes up show, disabling me from creating LRSs. Should be fixed some time. +struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem # Input values. """The reaction system within each compartment.""" - rs::ReactionSystem{R} + rs::ReactionSystem{Q} """The spatial reactions defined between individual nodes.""" - spatial_reactions::Vector{S} + spatial_reactions::Vector{R} """The graph on which the lattice is defined.""" - lattice::T + lattice::S # Derived values. """The number of compartments.""" @@ -44,15 +45,15 @@ struct LatticeReactionSystem{R,S,T} # <: MT.AbstractTimeDependentSystem """ directed_edges::Bool """ - A list of all the edges on the lattice. + An iterator over all the edges on the lattice. The format depends on the type of lattice (Cartesian grid, grid, or graph). """ - edge_list::Vector{Vector{Int64}} + edge_iterator::T - function LatticeReactionSystem(rs::ReactionSystem{R}, spatial_reactions::Vector{S}, - lattice::S, num_verts, edge_list::T; directed_edges = false) where {R, S, T} + function LatticeReactionSystem(rs::ReactionSystem{Q}, spatial_reactions::Vector{R}, lattice::S, + num_verts, num_edges, edge_iterator::T; directed_edges = false) where {Q,R, S, T} # Error checks. - if !(S <: AbstractSpatialReaction) | + if !(R <: AbstractSpatialReaction) error("The second argument must be a vector of AbstractSpatialReaction subtypes.") end @@ -73,8 +74,8 @@ struct LatticeReactionSystem{R,S,T} # <: MT.AbstractTimeDependentSystem edge_parameters = unique([rs_edge_parameters; srs_edge_parameters]) vertex_parameters = filter(!isedgeparameter, parameters(rs)) - # Counts the number of edges and species types (number of vertexes already counted). - num_edges = false + # Counts the number of species types. The number of vertexes and compartments is given in input. + # `num_edges` cannot be computed here, because how it is computed depends on the lattice and `typeof(edge_iterator)`. num_species = length(unique([species(rs); spat_species])) # Ensures the parameter order begins similarly to in the non-spatial ReactionSystem. @@ -83,68 +84,108 @@ struct LatticeReactionSystem{R,S,T} # <: MT.AbstractTimeDependentSystem # Checks that all spatial reactions are valid for this reactions system. foreach(sr -> check_spatial_reaction_validity(rs, sr; edge_parameters=edge_parameters), spatial_reactions) - return new{R,S,T}(rs, spatial_reactions, lattice, num_verts, ne(lattice), num_species, - spat_species, ps, vertex_parameters, edge_parameters, init_digraph, edge_list) + return new{Q,R,S,T}(rs, spatial_reactions, lattice, num_verts, num_edges, num_species, + spat_species, ps, vertex_parameters, edge_parameters, directed_edges, edge_iterator) end end -# Creates a LatticeReactionSystem from a CartesianGrid lattice. +# Creates a LatticeReactionSystem from a CartesianGrid lattice (cartesian grid). function LatticeReactionSystem(rs, srs, lattice::CartesianGridRej{S,T}; diagonal_connections=false) where {S,T} + # Error checks. (length(lattice.dims) > 3) && error("Grids of higher dimension than 3 is currently not supported.") - diagonal_connections && error("Diagonal connections is currently unsupported for Cartesian grid lattices.") - num_verts = prod(lattice.dims) - - # Prepares the list of edges. - edge_list = [Vector{Int64}() for i in 1:num_verts] # Ensures that the matrix is on a 3d form - lattice = reshape(vals,dims..., fill(1,3- length(dims))...) - + lattice = CartesianGrid((lattice.dims..., fill(1, 3-length(lattice.dims))...)) + + # Counts vertexes and edges. The `num_edges` count formula counts the number of internal, side, + # edge, and corner vertexes (on the grid). The number of edges from each depend on whether diagonal + # connections are allowed. The formula holds even if l, m, and/or n are 1. + l,m,n = 2,3,4 + num_verts = l * m * n + (ni, ns, ne, nc) = diagonal_connections ? (26,17,11,7) : (6,5,4,3) + num_edges = ni*(l-2)*(m-2)*(n-2) + # Internal vertexes. + ns*(2(l-2)*(m-2) + 2(l-2)*(n-2) + 2(m-2)*(n-2)) + # Side vertexes. + ne*(4(l-2) + 4(m-2) + 4(n-2)) + # Edge vertexes. + nc*8 # Corner vertexes. + + # Creates an iterator over all edges. + # Currently creates a full vector. Future version might be for efficient. + edge_iterator = Vector{Pair{Int64,Int64}}(undef, num_edges) + # Loops through, simultaneously, the coordinates of each position in the grid, as well as that + # coordinate's (scalar) flat index. For each grid point, loops through all potential neighbours. + indices = [(L, M, N) for L in 1:l, M in 1:m, N in 1:n] + flat_indices = 1:num_verts + next_vert = 0 + for ((L, M, N), idx) in zip(indices, flat_indices) + for LL in max(L - 1, 1):min(L + 1, l), + MM in max(M - 1, 1):min(M + 1, m), + NN in max(N - 1, 1):min(N + 1, n) + + # Which (LL,MM,NN) indexes are valid neighbours depends on whether diagonal connects are permitted. + !diagonal_connections && (count([L==LL, M==MM, N==NN]) == 2) || continue + diagonal_connections && (L==LL) && (M==MM) && (N==NN) && continue + + # Computes the neighbours scalar index. Add that connection to `edge_iterator`. + neighbour_idx = LL + (MM - 1) * l + (NN - 1) * m * l + edge_iterator[next_vert += 1] = (idx => neighbour_idx) + end + end - edge_list = false - return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; edge_list) + return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator) end -# Creates a LatticeReactionSystem from a Boolean Array lattice. +# Creates a LatticeReactionSystem from a Boolean Array lattice (masked grid). function LatticeReactionSystem(rs, srs, lattice::Array{Bool, T}; diagonal_connections=false) where {T} - dims = size(vals) + # Error checks. + dims = size(lattice) (length(dims) > 3) && error("Grids of higher dimension than 3 is currently not supported.") - diagonal_connections && error("Diagonal connections is currently unsupported for grid lattices.") - num_verts = count(lattice) - - # Prepares the list of edges. - edge_list = [Vector{Int64}() for i in 1:num_verts] # Ensures that the matrix is on a 3d form - lattice = CartesianGrid((lattice.dims..., fill(1,3- length(lattice.dims))...)) + lattice = reshape(lattice, [dims...; fill(1, 3-length(dims))]...) + + # Counts vertexes (edges have to be counted after the iterator have been created). + num_verts = count(lattice) + # Creates an iterator over all edges.Currently a full vector of all edges (as pairs). + edge_iterator = Vector{Pair{Int64,Int64}}() # Loops through, simultaneously, the coordinates of each position in the grid, as well as that - # coordinate's (scalar) flat index. - indices = [(i, j, k) for i in 1:lattice.dims[1], j in 1:lattice.dims[2], k in 1:lattice.dims[3]] - flat_indices = 1:prod(lattice.dims) - for ((i,j,k), idx) in zip(indices, flat_indices) - for ii in i-1:i+1, jj in j-1:j+1, kk in k-1:k+1 - # Finds the (scalar) flat index of this neighbour. If it is a valid neighbour, add it to edge_list. - n_idx = (k - 1) * (l * m) + (j - 1) * l + i - (1 <= n_idx <= flat_indices[end]) || continue - (n_idx == idx) && continue - push!(edge_list[cur_vert], n_idx) + # coordinate's (scalar) flat index. For each grid point, loops through all potential neighbours. + l,m,n = size(lattice) + indices = [(L, M, N) for L in 1:l, M in 1:m, N in 1:n] + flat_indices = 1:num_verts + for ((L, M, N), idx) in zip(indices, flat_indices) + for LL in max(L - 1, 1):min(L + 1, l), + MM in max(M - 1, 1):min(M + 1, m), + NN in max(N - 1, 1):min(N + 1, n) + + # Ensures that the neighbour is a valid lattice point. + lattice[LL,MM,NN] || continue + + # Which (LL,MM,NN) indexes are valid neighbours depends on whether diagonal connects are permitted. + !diagonal_connections && (count([L==LL, M==MM, N==NN]) == 2) || continue + diagonal_connections && (L==LL) && (M==MM) && (N==NN) && continue + + # Computes the neighbours scalar index. Add that connection to `edge_iterator`. + neighbour_idx = LL + (MM - 1) * l + (NN - 1) * m * l + push!(edge_iterator, idx => neighbour_idx) end end + num_edges = length(edge_iterator) - return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; edge_list) + return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator) end -# Creates a LatticeReactionSystem from a DiGraph lattice. -function LatticeReactionSystem(rs, srs, lattice::SimpleGraph; directed_edges = true) +# Creates a LatticeReactionSystem from a DiGraph lattice (graph grid). +function LatticeReactionSystem(rs, srs, lattice::DiGraph; directed_edges = true) num_verts = nv(lattice) - edge_list = false - return LatticeReactionSystem(rs, srs, lattice, num_verts, edge_list; directed_edges, edge_list) + num_edges = ne(lattice) + edge_iterator = edges(lattice) + return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator; directed_edges) end -# Creates a LatticeReactionSystem from a Graph lattice. +# Creates a LatticeReactionSystem from a Graph lattice (graph grid). function LatticeReactionSystem(rs, srs, lattice::SimpleGraph) - return LatticeReactionSystem(rs, srs, DiGraph(lattice); directed_edges) + return LatticeReactionSystem(rs, srs, DiGraph(lattice); directed_edges = false) end ### Lattice ReactionSystem Getters ### @@ -169,16 +210,16 @@ is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReac # Checks if a LatticeReactionSystem have a Cartesian grid lattice. has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa CartesianGridRej{S,T} where {S,T} -# Checks if a LatticeReactionSystem have a regular grid lattice. -has_regular_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa Array{Bool, T} where T -# Checks if a LatticeReactionSystem have a grid lattice (cartesian or regular). -has_grid_lattice(lrs::LatticeReactionSystem) = (has_cartesian_lattice(lrs) || has_regular_lattice(lrs)) +# Checks if a LatticeReactionSystem have a masked grid lattice. +has_masked_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa Array{Bool, T} where T +# Checks if a LatticeReactionSystem have a grid lattice (cartesian or masked). +has_grid_lattice(lrs::LatticeReactionSystem) = (has_cartesian_lattice(lrs) || has_masked_lattice(lrs)) # Checks if a LatticeReactionSystem have a graph lattice. has_graph_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa SimpleDiGraph # Returns the dimensions of a LatticeReactionNetwork with a grid lattice. function grid_dims(lrs::LatticeReactionSystem) has_cartesian_lattice(lrs) && (return length(lrs.lattice.dims)) - has_regular_lattice(lrs) && (return length(size(lrs.lattice))) + has_masked_lattice(lrs) && (return length(size(lrs.lattice))) error("Dimensions are only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl new file mode 100644 index 0000000000..17ff9b3940 --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -0,0 +1,304 @@ +### Preparations ### + +# Fetch packages. +using Catalyst, Graphs, Test + +# Pre declares a grid. +grid = Graphs.grid([2, 2]) + +### Tests LatticeReactionSystem Getters Correctness ### +# Test case 1. +let + rs = @reaction_network begin + (p, 1), 0 <--> X + end + tr = @transport_reaction d X + lrs = LatticeReactionSystem(rs, [tr], grid) + + @unpack X, p = rs + d = edge_parameters(lrs)[1] + @test issetequal(species(lrs), [X]) + @test issetequal(spatial_species(lrs), [X]) + @test issetequal(parameters(lrs), [p, d]) + @test issetequal(vertex_parameters(lrs), [p]) + @test issetequal(edge_parameters(lrs), [d]) +end + +# Test case 2. +let + rs = @reaction_network begin + @parameters p1 p2 [edgeparameter=true] + end + @unpack p1, p2 = rs + + @test !isedgeparameter(p1) + @test isedgeparameter(p2) +end + +# Test case 3. +let + rs = @reaction_network begin + @parameters pX pY dX [edgeparameter=true] dY + (pX, 1), 0 <--> X + (pY, 1), 0 <--> Y + end + tr_1 = @transport_reaction dX X + tr_2 = @transport_reaction dY Y + lrs = LatticeReactionSystem(rs, [tr_1, tr_2], grid) + + @unpack X, Y, pX, pY, dX, dY = rs + @test issetequal(species(lrs), [X, Y]) + @test issetequal(spatial_species(lrs), [X, Y]) + @test issetequal(parameters(lrs), [pX, pY, dX, dY]) + @test issetequal(vertex_parameters(lrs), [pX, pY, dY]) + @test issetequal(edge_parameters(lrs), [dX]) +end + +# Test case 4. +let + rs = @reaction_network begin + @parameters dX p + (pX, 1), 0 <--> X + (pY, 1), 0 <--> Y + end + tr_1 = @transport_reaction dX X + lrs = LatticeReactionSystem(rs, [tr_1], grid) + + @unpack dX, p, X, Y, pX, pY = rs + @test issetequal(species(lrs), [X, Y]) + @test issetequal(spatial_species(lrs), [X]) + @test issetequal(parameters(lrs), [dX, p, pX, pY]) + @test issetequal(vertex_parameters(lrs), [dX, p, pX, pY]) + @test issetequal(edge_parameters(lrs), []) +end + +# Test case 5. +let + rs = @reaction_network begin + @species W(t) + @parameters pX pY dX [edgeparameter=true] dY + (pX, 1), 0 <--> X + (pY, 1), 0 <--> Y + (pZ, 1), 0 <--> Z + (pV, 1), 0 <--> V + end + @unpack dX, X, V = rs + @parameters dV dW + @variables t + @species W(t) + tr_1 = TransportReaction(dX, X) + tr_2 = @transport_reaction dY Y + tr_3 = @transport_reaction dZ Z + tr_4 = TransportReaction(dV, V) + tr_5 = TransportReaction(dW, W) + lrs = LatticeReactionSystem(rs, [tr_1, tr_2, tr_3, tr_4, tr_5], grid) + + @unpack pX, pY, pZ, pV, dX, dY, X, Y, Z, V = rs + dZ, dV, dW = edge_parameters(lrs)[2:end] + @test issetequal(species(lrs), [W, X, Y, Z, V]) + @test issetequal(spatial_species(lrs), [X, Y, Z, V, W]) + @test issetequal(parameters(lrs), [pX, pY, dX, dY, pZ, pV, dZ, dV, dW]) + @test issetequal(vertex_parameters(lrs), [pX, pY, dY, pZ, pV]) + @test issetequal(edge_parameters(lrs), [dX, dZ, dV, dW]) +end + +# Test case 6. +let + rs = @reaction_network customname begin + (p, 1), 0 <--> X + end + tr = @transport_reaction d X + lrs = LatticeReactionSystem(rs, [tr], grid) + + @test nameof(lrs) == :customname +end + +### Tests Spatial Reactions Getters Correctness ### + +# Test case 1. +let + tr_1 = @transport_reaction dX X + tr_2 = @transport_reaction dY1*dY2 Y + + # @test ModelingToolkit.getname.(species(tr_1)) == ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] # species(::TransportReaction) currently not supported. + # @test ModelingToolkit.getname.(species(tr_2)) == ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] + @test ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] + @test ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] + @test ModelingToolkit.getname.(parameters(tr_1)) == [:dX] + @test ModelingToolkit.getname.(parameters(tr_2)) == [:dY1, :dY2] + + # @test issetequal(species(tr_1), [tr_1.species]) + # @test issetequal(species(tr_2), [tr_2.species]) + @test issetequal(spatial_species(tr_1), [tr_1.species]) + @test issetequal(spatial_species(tr_2), [tr_2.species]) +end + +# Test case 2. +let + rs = @reaction_network begin + @species X(t) Y(t) + @parameters dX dY1 dY2 + end + @unpack X, Y, dX, dY1, dY2 = rs + tr_1 = TransportReaction(dX, X) + tr_2 = TransportReaction(dY1*dY2, Y) + # @test isequal(species(tr_1), [X]) + # @test isequal(species(tr_1), [X]) + @test issetequal(spatial_species(tr_2), [Y]) + @test issetequal(spatial_species(tr_2), [Y]) + @test issetequal(parameters(tr_1), [dX]) + @test issetequal(parameters(tr_2), [dY1, dY2]) +end + +### Tests Spatial Reactions Generation ### + +# Tests TransportReaction with non-trivial rate. +let + rs = @reaction_network begin + @parameters dV dE [edgeparameter=true] + (p,1), 0 <--> X + end + @unpack dV, dE, X = rs + + tr = TransportReaction(dV*dE, X) + @test isequal(tr.rate, dV*dE) +end + +# Tests transport_reactions function for creating TransportReactions. +let + rs = @reaction_network begin + @parameters d + (p,1), 0 <--> X + end + @unpack d, X = rs + trs = TransportReactions([(d, X), (d, X)]) + @test isequal(trs[1], trs[2]) +end + +# Test reactions with constants in rate. +let + @variables t + @species X(t) Y(t) + + tr_1 = TransportReaction(1.5, X) + tr_1_macro = @transport_reaction 1.5 X + @test isequal(tr_1.rate, tr_1_macro.rate) + @test isequal(tr_1.species, tr_1_macro.species) + + tr_2 = TransportReaction(π, Y) + tr_2_macro = @transport_reaction π Y + @test isequal(tr_2.rate, tr_2_macro.rate) + @test isequal(tr_2.species, tr_2_macro.species) +end + +### Test Interpolation ### + +# Does not currently work. The 3 tr_macro_ lines generate errors. +# Test case 1. +let + rs = @reaction_network begin + @species X(t) Y(t) Z(t) + @parameters dX dY1 dY2 dZ + end + @unpack X, Y, Z, dX, dY1, dY2, dZ = rs + rate1 = dX + rate2 = dY1*dY2 + species3 = Z + tr_1 = TransportReaction(dX, X) + tr_2 = TransportReaction(dY1*dY2, Y) + tr_3 = TransportReaction(dZ, Z) + tr_macro_1 = @transport_reaction $dX X + tr_macro_2 = @transport_reaction $(rate2) Y + # tr_macro_3 = @transport_reaction dZ $species3 # Currently does not work, something with meta programming. + + @test isequal(tr_1, tr_macro_1) + @test isequal(tr_2, tr_macro_2) # Unsure why these fails, since for components equality hold: `isequal(tr_1.species, tr_macro_1.species)` and `isequal(tr_1.rate, tr_macro_1.rate)` are both true. + # @test isequal(tr_3, tr_macro_3) +end + +### Tests Error generation ### + +# Test creation of TransportReaction with non-parameters in rate. +# Tests that it works even when rate is highly nested. +let + @variables t + @species X(t) Y(t) + @parameters D1 D2 D3 + @test_throws ErrorException TransportReaction(D1 + D2*(D3 + Y), X) + @test_throws ErrorException TransportReaction(Y, X) +end + +# Network where diffusion species is not declared in non-spatial network. +let + rs = @reaction_network begin + (p, d), 0 <--> X + end + tr = @transport_reaction D Y + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) +end + +# Network where the rate depend on a species +let + rs = @reaction_network begin + @species Y(t) + (p, d), 0 <--> X + end + tr = @transport_reaction D*Y X + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) +end + +# Network with edge parameter in non-spatial reaction rate. +let + rs = @reaction_network begin + @parameters p [edgeparameter=true] + (p, d), 0 <--> X + end + tr = @transport_reaction D X + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) +end + +# Network where metadata has been added in rs (which is not seen in transport reaction). +let + rs = @reaction_network begin + @species X(t) [description="Species with added metadata"] + (p, d), 0 <--> X + end + tr = @transport_reaction D X + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + + rs = @reaction_network begin + @parameters D [description="Parameter with added metadata"] + (p, d), 0 <--> X + end + tr = @transport_reaction D X + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) +end + + +### Tests Grid Vertex and Edge Number Computation ### + +# Tests that the correct numbers are computed for num_edges. +let + # Function counting the values in an iterator by stepping through it. + function iterator_count(iterator) + count = 0 + foreach(e -> count+=1, iterator) + return count + end + + # Cartesian and masked grid (test diagonal edges as well). + for lattice in [small_1d_cartesian_grid, small_2d_cartesian_grid, small_3d_cartesian_grid, + random_1d_masked_grid, random_2d_masked_grid, random_3d_masked_grid] + lrs1 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) + lrs2 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice; diagonal_connections=true) + @test lrs1.num_edges == iterator_count(lrs1.edge_iterator) + @test lrs2.num_edges == iterator_count(lrs2.edge_iterator) + end + + # Graph grids (cannot test diagonal connections). + for lattice in [small_2d_grid, small_3d_grid, undirected_cycle, small_directed_cycle, unconnected_graph] + lrs1 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) + @test lrs1.num_edges == iterator_count(lrs1.edge_iterator) + end +end + diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl index 168711c9ff..7771898c2f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl @@ -17,35 +17,35 @@ let cartesian_1d_lrs = LatticeReactionSystem(brusselator_system, srs, small_1d_cartesian_grid) cartesian_2d_lrs = LatticeReactionSystem(brusselator_system, srs, small_2d_cartesian_grid) cartesian_3d_lrs = LatticeReactionSystem(brusselator_system, srs, small_3d_cartesian_grid) - regular_1d_lrs = LatticeReactionSystem(brusselator_system, srs, small_1d_regular_grid) - regular_2d_lrs = LatticeReactionSystem(brusselator_system, srs, small_2d_regular_grid) - regular_3d_lrs = LatticeReactionSystem(brusselator_system, srs, small_3d_regular_grid) + masked_1d_lrs = LatticeReactionSystem(brusselator_system, srs, small_1d_masked_grid) + masked_2d_lrs = LatticeReactionSystem(brusselator_system, srs, small_2d_masked_grid) + masked_3d_lrs = LatticeReactionSystem(brusselator_system, srs, small_3d_masked_grid) graph_lrs = LatticeReactionSystem(brusselator_system, srs, small_2d_grid) # Test lattice type getters. @test has_cartesian_lattice(cartesian_2d_lrs) - @test has_cartesian_lattice(regular_2d_lrs) + @test has_cartesian_lattice(masked_2d_lrs) @test has_cartesian_lattice(graph_lrs) - @test !has_regular_lattice(cartesian_2d_lrs) - @test !has_regular_lattice(regular_2d_lrs) - @test !has_regular_lattice(graph_lrs) + @test !has_masked_lattice(cartesian_2d_lrs) + @test !has_masked_lattice(masked_2d_lrs) + @test !has_masked_lattice(graph_lrs) @test has_grid_lattice(cartesian_2d_lrs) - @test has_grid_lattice(regular_2d_lrs) + @test has_grid_lattice(masked_2d_lrs) @test !has_grid_lattice(graph_lrs) @test !has_graph_lattice(cartesian_2d_lrs) - @test !has_graph_lattice(regular_2d_lrs) + @test !has_graph_lattice(masked_2d_lrs) @test has_graph_lattice(graph_lrs) # Checks grid dimensions. @test grid_dims(cartesian_1d_lrs) == 1 @test grid_dims(cartesian_2d_lrs) == 2 @test grid_dims(cartesian_3d_lrs) == 3 - @test grid_dims(regular_1d_lrs) == 1 - @test grid_dims(regular_2d_lrs) == 2 - @test grid_dims(regular_3d_lrs) == 3 + @test grid_dims(masked_1d_lrs) == 1 + @test grid_dims(masked_2d_lrs) == 2 + @test grid_dims(masked_3d_lrs) == 3 @test_throws Exception grid_dims(graph_lrs) end @@ -54,25 +54,25 @@ end let # Create LatticeReactionsSystems. cartesian_grid = Graphs.grid([5, 5]) - regular_grid = fill(true, 5, 5) + masked_grid = fill(true, 5, 5) graph_grid = Graphs.grid([5, 5]) cartesian_lrs = LatticeReactionSystem(brusselator_system, srs, cartesian_grid) - regular_lrs = LatticeReactionSystem(brusselator_system, srs, regular_grid) + masked_lrs = LatticeReactionSystem(brusselator_system, srs, masked_grid) graph_lrs = LatticeReactionSystem(brusselator_system, srs, graph_grid) # Check internal structures. - @test cartesian_lrs.rs == regular_lrs.rs == graph_lrs.rs - @test cartesian_lrs.spatial_reactions == regular_lrs.spatial_reactions == graph_lrs.spatial_reactions - @test cartesian_lrs.num_verts == regular_lrs.num_verts == graph_lrs.num_verts - @test cartesian_lrs.num_edges == regular_lrs.num_edges == graph_lrs.num_edges - @test cartesian_lrs.num_species == regular_lrs.num_species == graph_lrs.num_species - @test cartesian_lrs.spat_species == regular_lrs.spat_species == graph_lrs.spat_species - @test cartesian_lrs.parameters == regular_lrs.parameters == graph_lrs.parameters - @test cartesian_lrs.vertex_parameters == regular_lrs.vertex_parameters == graph_lrs.vertex_parameters - @test cartesian_lrs.edge_parameters == regular_lrs.edge_parameters == graph_lrs.edge_parameters - @test cartesian_lrs.directed_edges == regular_lrs.directed_edges == graph_lrs.directed_edges - @test cartesian_lrs.edge_list == regular_lrs.edge_list == graph_lrs.edge_list + @test cartesian_lrs.rs == masked_lrs.rs == graph_lrs.rs + @test cartesian_lrs.spatial_reactions == masked_lrs.spatial_reactions == graph_lrs.spatial_reactions + @test cartesian_lrs.num_verts == masked_lrs.num_verts == graph_lrs.num_verts + @test cartesian_lrs.num_edges == masked_lrs.num_edges == graph_lrs.num_edges + @test cartesian_lrs.num_species == masked_lrs.num_species == graph_lrs.num_species + @test cartesian_lrs.spat_species == masked_lrs.spat_species == graph_lrs.spat_species + @test cartesian_lrs.parameters == masked_lrs.parameters == graph_lrs.parameters + @test cartesian_lrs.vertex_parameters == masked_lrs.vertex_parameters == graph_lrs.vertex_parameters + @test cartesian_lrs.edge_parameters == masked_lrs.edge_parameters == graph_lrs.edge_parameters + @test cartesian_lrs.directed_edges == masked_lrs.directed_edges == graph_lrs.directed_edges + @test cartesian_lrs.edge_list == masked_lrs.edge_list == graph_lrs.edge_list # Checks that simulations yields the same output. u0 = [:X => rand_v_vals(graph_lrs.lattice, 10.0), :Y => 2.0] @@ -80,40 +80,40 @@ let pE= map(sp -> sp => 0.2, spatial_param_syms(lrs)) cartesian_oprob = ODEProblem(cartesian_lrs, u0, (0.0, 100.0), (pV, pE)) - regular_oprob = ODEProblem(regular_lrs, u0, (0.0, 100.0), (pV, pE)) + masked_oprob = ODEProblem(masked_lrs, u0, (0.0, 100.0), (pV, pE)) graph_oprob = ODEProblem(graph_lrs, u0, (0.0, 100.0), (pV, pE)) cartesian_sol = solve(cartesian_oprob, QNDF(); saveat=0.1) - regular_sol = solve(regular_oprob, QNDF(); saveat=0.1) + masked_sol = solve(masked_oprob, QNDF(); saveat=0.1) graph_sol = solvegraph(_oprob, QNDF(); saveat=0.1) - @test cartesian_sol.u ≈ regular_sol.u ≈ graph_sol + @test cartesian_sol.u ≈ masked_sol.u ≈ graph_sol end # Checks that a regular grid with absent vertices generate the same output as corresponding graph. let # Create LatticeReactionsSystems. - regular_grid = [true true true; true false true; true true true] + masked_grid = [true true true; true false true; true true true] graph_grid = cycle_graph(8) - regular_lrs = LatticeReactionSystem(brusselator_system, srs, regular_grid) + masked_lrs = LatticeReactionSystem(brusselator_system, srs, masked_grid) graph_lrs = LatticeReactionSystem(brusselator_system, srs, graph_grid) # Check internal structures. - @test regular_lrs.num_verts == graph_lrs.num_verts - @test regular_lrs.num_edges == graph_lrs.num_edges - @test regular_lrs.edge_list == graph_lrs.edge_list + @test masked_lrs.num_verts == graph_lrs.num_verts + @test masked_lrs.num_edges == graph_lrs.num_edges + @test masked_lrs.edge_list == graph_lrs.edge_list # Checks that simulations yields the same output. u0 = [:X => rand_v_vals(graph_lrs.lattice, 10.0), :Y => 2.0] pV = [:A => 0.5 .+ rand_v_vals(graph_lrs.lattice, 0.5), :B => 4.0] pE= map(sp -> sp => 0.2, spatial_param_syms(lrs)) - regular_oprob = ODEProblem(regular_lrs, u0, (0.0, 100.0), (pV, pE)) + masked_oprob = ODEProblem(masked_lrs, u0, (0.0, 100.0), (pV, pE)) graph_oprob = ODEProblem(graph_lrs, u0, (0.0, 100.0), (pV, pE)) - regular_sol = solve(regular_oprob, QNDF(); saveat=0.1) + masked_sol = solve(masked_oprob, QNDF(); saveat=0.1) graph_sol = solvegraph(_oprob, QNDF(); saveat=0.1) - @test regular_sol.u ≈ graph_sol + @test masked_sol.u ≈ graph_sol end \ No newline at end of file diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index 813466754c..b365b26fa2 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -177,18 +177,18 @@ large_1d_cartesian_grid = CartesianGrid(100) large_2d_cartesian_grid = CartesianGrid((100,100)) large_3d_cartesian_grid = CartesianGrid((100,100,100)) -# Regular grids. -small_1d_regular_grid = fill(true, 5) -small_2d_regular_grid = fill(true, 5, 5) -small_3d_regular_grid = fill(true, 5, 5, 5) - -large_1d_regular_grid = fill(true, 5) -large_2d_regular_grid = fill(true, 5, 5) -large_3d_regular_grid = fill(true, 5, 5, 5) - -random_1d_regular_grid = rand([true, true, true, false], 10) -random_2d_regular_grid = rand([true, true, true, false], 10, 10) -random_3d_regular_grid = rand([true, true, true, false], 10, 10, 10) +# Masked grids. +small_1d_masked_grid = fill(true, 5) +small_2d_masked_grid = fill(true, 5, 5) +small_3d_masked_grid = fill(true, 5, 5, 5) + +large_1d_masked_grid = fill(true, 5) +large_2d_masked_grid = fill(true, 5, 5) +large_3d_masked_grid = fill(true, 5, 5, 5) + +random_1d_masked_grid = rand([true, true, true, false], 10) +random_2d_masked_grid = rand([true, true, true, false], 10, 10) +random_3d_masked_grid = rand([true, true, true, false], 10, 10, 10) # Graph - grids. very_small_2d_grid = Graphs.grid([2, 2]) From 3405236932613abc937f7d8721ff2707e1617c4e Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 26 Jan 2024 17:00:49 -0500 Subject: [PATCH 084/446] updates --- .../spatial_ODE_systems.jl | 37 +++---- src/spatial_reaction_systems/utility.jl | 4 + ...attice_reaction_systems_ODE_performance.jl | 8 +- .../lattice_reaction_systems_ODEs.jl | 104 ++++++++++++++++-- test/spatial_test_networks.jl | 26 ++--- 5 files changed, 133 insertions(+), 46 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index b967dd29fb..ddfdf0b171 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -24,13 +24,13 @@ struct LatticeTransportODEf{Q,R,S,T} """ v_ps_idx_types::Vector{Bool} """ - A vector of pairs, with a value for each species with transportation. + A vector of sparse, with a value for each species with transportation. The first value is the species index (in the species(::ReactionSystem) vector), and the second is a vector with its transport rate values. If the transport rate is uniform (across all edges), that value is the only value in the vector. Else, there is one value for each edge in the lattice. """ - transport_rates::Vector{Pair{Int64, Vector{R}}} + transport_rates::Vector{SparseMatrixCSC{R, Int64}} """ A matrix, NxM, where N is the number of species with transportation and M the number of vertexes. Each value is the total rate at which that species leaves that vertex @@ -59,7 +59,7 @@ struct LatticeTransportODEf{Q,R,S,T} work_vert_ps = zeros(lrs.num_verts) # 1 if ps are constant across the graph, 0 else. v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) - eds = edges(lrs.lattice) + eds = lrs.edge_iterator new{Q,R,typeof(eds),T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, v_ps_idx_types, transport_rates, leaving_rates, eds, edge_ps) end @@ -125,16 +125,15 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, # Converts potential symmaps to varmaps # Vertex and edge parameters may be given in a tuple, or in a common vector, making parameter case complicated. u0_in = symmap_to_varmap(lrs, u0_in) - p_in = (p_in isa Tuple{<:Any,<:Any}) ? - (symmap_to_varmap(lrs, p_in[1]),symmap_to_varmap(lrs, p_in[2])) : - symmap_to_varmap(lrs, p_in) + p_in = symmap_to_varmap(lrs, p_in) # Converts u0 and p to their internal forms. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) - # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. - # These elements are length 1 vectors (if the parameter is uniform), - # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). + # Each parameter value in vert_ps is a vector (with one value for each parameter). + # edge_ps becomes sparse matrix. Here, index (i,j) is its value in the edge from vertex i to vertex j. + # Uniform vertex/edge parameters stores only a single value (in a length 1 vector, or size 1x1 sparse matrix). + # This is the parameters single value. vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Creates ODEProblem. @@ -261,25 +260,25 @@ end function (f_func::LatticeTransportODEf)(du, u, p, t) # Updates for non-spatial reactions. for vert_i in 1:(f_func.num_verts) - # gets the indices of species at vertex i + # Gets the indices of species at vertex i. idxs = get_indexes(vert_i, f_func.num_species) - # vector of vertex ps at vert_i - vert_i_ps = view_vert_ps_vector!(f_func.work_vert_ps, p, vert_i, enumerate(f_func.v_ps_idx_types)) + # Vector of vertex ps at vert_i. + vert_i_ps = view_vert_ps_vector!(f_func, p, vert_i) - # evaluate reaction contributions to du at vert_i + # Evaluate reaction contributions to du at vert_i. f_func.ofunc((@view du[idxs]), (@view u[idxs]), vert_i_ps, t) end - # s_idx is species index among transport species, s is index among all species - # rates are the species' transport rates + # s_idx is species index among transport species, s is index among all species. + # rates are the species' transport rates. for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) # Rate for leaving vert_i for vert_i in 1:(f_func.num_verts) idx = get_index(vert_i, s, f_func.num_species) du[idx] -= f_func.leaving_rates[s_idx, vert_i] * u[idx] end - # Add rates for entering a given vertex via an incoming edge + # Add rates for entering a given vertex via an incoming edge. for (e_idx, e) in enumerate(f_func.edges) idx_dst = get_index(e.dst, s, f_func.num_species) idx_src = get_index(e.src, s, f_func.num_species) @@ -292,13 +291,13 @@ end function (jac_func::LatticeTransportODEjac)(J, u, p, t) J .= 0.0 - # Update the Jacobian from reaction terms + # Update the Jacobian from reaction terms. for vert_i in 1:(jac_func.num_verts) idxs = get_indexes(vert_i, jac_func.num_species) - vert_ps = view_vert_ps_vector!(jac_func.work_vert_ps, p, vert_i, enumerate(jac_func.v_ps_idx_types)) + vert_ps = view_vert_ps_vector!(jac_func, p, vert_i) jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), vert_ps, t) end # Updates for the spatial reactions (adds the Jacobian values from the diffusion reactions). J .+= jac_func.jac_transport -end \ No newline at end of file +end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 691ba79a3c..1f498630d2 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -289,6 +289,10 @@ function view_vert_ps_vector!(work_vert_ps, vert_ps, comp, enumerated_vert_ps_id end return work_vert_ps end +# Input is always either a LatticeTransportODEf or LatticeTransportODEjac function (which fields we then pass on). +function view_vert_ps_vector!(lt_ode_func, vert_ps, comp) + return view_vert_ps_vector!(lt_ode_func.work_vert_ps, vert_ps, comp, enumerate(lt_ode_func.v_ps_idx_types)) +end # Expands a u0/p information stored in Vector{Vector{}} for to Matrix form # (currently only used in Spatial Jump systems). diff --git a/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl b/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl index c3c801c603..992d864b20 100644 --- a/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl +++ b/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl @@ -19,7 +19,7 @@ include("../spatial_test_networks.jl") # Small grid, small, non-stiff, system. let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_graph_grid) u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] pV = SIR_p pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] @@ -49,7 +49,7 @@ end # Small grid, small, stiff, system. let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_graph_grid) u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] pV = brusselator_p pE = [:dX => 0.2] @@ -80,7 +80,7 @@ end # Small grid, mid-sized, non-stiff, system. let lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, - small_2d_grid) + small_2d_graph_grid) u0 = [ :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), @@ -141,7 +141,7 @@ end # Small grid, mid-sized, stiff, system. let - lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_graph_grid) u0 = [ :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index ec9f7d79d2..20476eeee9 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -17,8 +17,8 @@ t = default_t() ### Tests Simulations Don't Error ### for grid in [small_2d_grid, short_path, small_directed_cycle, small_1d_cartesian_grid, small_2d_cartesian_grid, small_3d_cartesian_grid, - small_1d_regular_grid, small_2d_regular_grid, small_3d_regular_grid, - random_2d_regular_grid] + small_1d_masked_grid, small_2d_masked_grid, small_3d_masked_grid, + random_2d_masked_grid] # Non-stiff case for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) @@ -34,8 +34,6 @@ for grid in [small_2d_grid, short_path, small_directed_cycle, :I => 50 * rand_v_vals(lrs.lattice), :R => 50 * rand_v_vals(lrs.lattice), ] - u0_5 = make_u0_matrix(u0_3, vertices(lrs.lattice), - map(s -> Symbol(s.f), species(lrs.rs))) for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] p1 = [:α => 0.1 / 1000, :β => 0.01] p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] @@ -43,13 +41,11 @@ for grid in [small_2d_grid, short_path, small_directed_cycle, :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), :β => 0.02 * rand_v_vals(lrs.lattice), ] - p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1, p2, p3, p4] pE_1 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) pE_2 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), spatial_param_syms(lrs)) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), spatial_param_syms(lrs)) for pE in [pE_1, pE_2, pE_3, pE_4] oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) @@ -67,8 +63,6 @@ for grid in [small_2d_grid, short_path, small_directed_cycle, u0_1 = [:X => 1.0, :Y => 20.0] u0_2 = [:X => rand_v_vals(lrs.lattice, 10.0), :Y => 2.0] u0_3 = [:X => rand_v_vals(lrs.lattice, 20), :Y => rand_v_vals(lrs.lattice, 10)] - u0_4 = make_u0_matrix(u0_3, vertices(lrs.lattice), - map(s -> Symbol(s.f), species(lrs.rs))) for u0 in [u0_1, u0_2, u0_3, u0_4] p1 = [:A => 1.0, :B => 4.0] p2 = [:A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :B => 4.0] @@ -76,13 +70,11 @@ for grid in [small_2d_grid, short_path, small_directed_cycle, :A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :B => 4.0 .+ rand_v_vals(lrs.lattice, 1.0), ] - p4 = make_u0_matrix(p2, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1, p2, p3, p4] pE_1 = map(sp -> sp => 0.2, spatial_param_syms(lrs)) pE_2 = map(sp -> sp => rand(rng), spatial_param_syms(lrs)) pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), spatial_param_syms(lrs)) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), spatial_param_syms(lrs)) for pE in [pE_1, pE_2, pE_3, pE_4] oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) @@ -272,6 +264,98 @@ let @test all(isapprox.(ss_1, ss_2)) end +### Test Grid Types ### + +# Tests that identical lattices (using different types of lattices) give identical results. +let + # 1d lattices. + lrs1_cartesian = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_1d_cartesian_grid) + lrs1_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_1d_masked_grid) + lrs1_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_1d_graph_grid) + + oprob1_cartesian = ODEProblem(lrs1_cartesian, sigmaB_u0, (0.0,10.0), sigmaB_p) + oprob1_masked = ODEProblem(lrs1_masked, sigmaB_u0, (0.0,10.0), sigmaB_p) + oprob1_graph = ODEProblem(lrs1_graph, sigmaB_u0, (0.0,10.0), sigmaB_p) + @test solve(oprob1_cartesian, QNDF()) == solve(oprob1_masked, QNDF()) == solve(oprob1_graph, QNDF()) + + # 2d lattices. + lrs2_cartesian = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_cartesian_grid) + lrs2_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_masked_grid) + lrs2_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_graph_grid) + + oprob2_cartesian = ODEProblem(lrs2_cartesian, sigmaB_u0, (0.0,10.0), sigmaB_p) + oprob2_masked = ODEProblem(lrs2_masked, sigmaB_u0, (0.0,10.0), sigmaB_p) + oprob2_graph = ODEProblem(lrs2_graph, sigmaB_u0, (0.0,10.0), sigmaB_p) + @test solve(oprob2_cartesian, QNDF()) == solve(oprob2_masked, QNDF()) == solve(oprob2_graph, QNDF()) + + # 3d lattices. + lrs3_cartesian = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_3d_cartesian_grid) + lrs3_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_3d_masked_grid) + lrs3_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_3d_graph_grid) + + oprob3_cartesian = ODEProblem(lrs3_cartesian, sigmaB_u0, (0.0,10.0), sigmaB_p) + oprob3_masked = ODEProblem(lrs3_masked, sigmaB_u0, (0.0,10.0), sigmaB_p) + oprob3_graph = ODEProblem(lrs3_graph, sigmaB_u0, (0.0,10.0), sigmaB_p) + @test solve(oprob3_cartesian, QNDF()) == solve(oprob3_masked, QNDF()) == solve(oprob3_graph, QNDF()) +end + +# Tests that input parameter and u0 values can be given using different types of input for 2d lattices. +# Tries both for cartesian and masked (where all vertexes are `true`). +let + for lattice in [CartesianGrid(3,4), fill(true, 3, 4)] + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, lattice) + + # Initial condition values. + S_vals_vec = [100, 200, 300, 100, 100, 100, 200, 200, 200, 300, 300, 300] + S_vals_mat = [100, 200, 300; 100, 100, 100; 200, 200, 200; 300, 300, 300] + SIR_u0_vec = [:S => S_vals_vec, :I => 1.0, :R => 0.0] + SIR_u0_mat = [:S => S_vals_mat, :I => 1.0, :R => 0.0] + + # Parameter values. + β_vals_vec = [0.01, 0.02, 0.03, 0.01, 0.01, 0.02, 0.02, 0.02, 0.02, 0.03, 0.03, 0.03] + β_vals_mat = [0.01 0.02 0.03; 0.01 0.01 0.02; 0.02 0.02 0.02; 0.03 0.03 0.03] + SIR_p_vec = [:α => 0.1 / 1000, :β => β_vals_vec] + SIR_p_mat = [:α => 0.1 / 1000, :β => β_vals_mat] + + sol1 = solve(ODEProblem(lrs, SIR_u0_vec, (0.0, 10.0), SIR_p_vec)) + sol2 = solve(ODEProblem(lrs, SIR_u0_mat, (0.0, 10.0), SIR_p_vec)) + sol3 = solve(ODEProblem(lrs, SIR_u0_vec, (0.0, 10.0), SIR_p_mat)) + sol4 = solve(ODEProblem(lrs, SIR_u0_mat, (0.0, 10.0), SIR_p_mat)) + + @test sol1 == sol2 == sol3 = sol4 + end +end + +# Tests that input parameter and u0 values can be given using different types of input for 2d masked grid. +# Tries when several of the mask values are `false`. +let + lattice = [true true false; true false false; true true true; false true true] + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, lattice) + + # Initial condition values. 999 is used for empty points. + S_vals_vec = [100, 200, 100, 200, 200, 200, 300, 300] + S_vals_mat = [100, 200, 999; 100, 999, 999; 200, 200, 200; 999, 300, 300] + S_vals_sparse_mat = sparse(S_vals_mat .* lattice) + SIR_u0_vec = [:S => S_vals_vec, :I => 1.0, :R => 0.0] + SIR_u0_mat = [:S => S_vals_mat, :I => 1.0, :R => 0.0] + SIR_u0_sparse_mat = [:S => S_vals_sparse_mat, :I => 1.0, :R => 0.0] + + # Parameter values. 9.99 is used for empty points. + β_vals_vec = [0.01, 0.02, 0.03, 0.01, 0.01, 0.02, 0.02, 0.02, 0.02, 0.03, 0.03, 0.03] + β_vals_mat = [0.01 0.02 9.99; 0.01 9.99 9.99; 0.02 0.02 0.02; 9.99 0.03 0.03] + β_vals_sparse_mat = sparse(β_vals_mat .* lattice) + SIR_p_vec = [:α => 0.1 / 1000, :β => β_vals_vec] + SIR_p_mat = [:α => 0.1 / 1000, :β => β_vals_mat] + SIR_p_sparse_mat = [:α => 0.1 / 1000, :β => β_vals_sparse_mat] + + sol = solve(ODEProblem(lrs, SIR_u0_vec, (0.0, 10.0), SIR_p_vec)) + for u0 in [SIR_u0_vec, SIR_u0_mat, SIR_u0_sparse_mat] + for p in [SIR_p_vec, SIR_p_mat, SIR_p_sparse_mat] + @test sol == solve(ODEProblem(lrs, u0, (0.0, 10.0), p)) + end + end +end + ### Test Transport Reaction Types ### # Compares where spatial reactions are created with/without the macro. diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index b365b26fa2..f4fcb17f50 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -12,11 +12,6 @@ rand_v_vals(grid) = rand(rng, nv(grid)) rand_v_vals(grid, x::Number) = rand_v_vals(grid) * x rand_e_vals(grid) = rand(rng, ne(grid)) rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x -function make_u0_matrix(value_map, vals, symbols) - (length(symbols) == 0) && (return zeros(0, length(vals))) - d = Dict(value_map) - return [(d[s] isa Vector) ? d[s][v] : d[s] for s in symbols, v in 1:length(vals)] -end # Gets a symbol list of spatial parameters. function spatial_param_syms(lrs::LatticeReactionSystem) @@ -191,14 +186,19 @@ random_2d_masked_grid = rand([true, true, true, false], 10, 10) random_3d_masked_grid = rand([true, true, true, false], 10, 10, 10) # Graph - grids. -very_small_2d_grid = Graphs.grid([2, 2]) -small_2d_grid = Graphs.grid([5, 5]) -medium_2d_grid = Graphs.grid([20, 20]) -large_2d_grid = Graphs.grid([100, 100]) - -small_3d_grid = Graphs.grid([5, 5, 5]) -medium_3d_grid = Graphs.grid([20, 20, 20]) -large_3d_grid = Graphs.grid([100, 100, 100]) +very_small_2d_graph_grid = Graphs.grid([2, 2]) + +small_1d_graph_grid = path_graph(5) +small_2d_graph_grid = Graphs.grid([5,5]) +small_3d_graph_grid = Graphs.grid([5,5,5]) + +medium_1d_graph_grid = path_graph(20) +medium_2d_graph_grid = Graphs.grid([20,20]) +medium_3d_graph_grid = Graphs.grid([20,20,20]) + +large_1d_graph_grid = path_graph(100) +large_2d_graph_grid = Graphs.grid([100,100]) +large_3d_graph_grid = Graphs.grid([100,100,100]) # Graph - paths. short_path = path_graph(100) From 1ce1766960368de63e543b895b332dc3e2b639d5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 6 Feb 2024 15:02:51 -0500 Subject: [PATCH 085/446] update --- src/Catalyst.jl | 1 + .../lattice_reaction_systems.jl | 83 ++-- .../spatial_ODE_systems.jl | 173 ++++---- src/spatial_reaction_systems/utility.jl | 389 ++++++++---------- .../lattice_reaction_systems_ODEs.jl | 313 +++++--------- .../lattice_reaction_systems.jl | 122 +++--- .../lattice_reaction_systems_lattice_types.jl | 139 ++++--- test/spatial_test_networks.jl | 20 +- 8 files changed, 612 insertions(+), 628 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index a69883aaa1..a5f51efb89 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -177,6 +177,7 @@ include("spatial_reaction_systems/lattice_reaction_systems.jl") export LatticeReactionSystem export spatial_species, vertex_parameters, edge_parameters export CartesianGrid, CartesianGridReJ # (Implemented in JumpProcesses) +export has_cartesian_lattice, has_masked_lattice, has_grid_lattice, has_graph_lattice, grid_dims # Various utility functions include("spatial_reaction_systems/utility.jl") diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 6b48175feb..be75d75d73 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -36,14 +36,6 @@ struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem e.g. (possibly) have an unique value at each edge of the system. """ edge_parameters::Vector{BasicSymbolic{Real}} - - """ - Whenever the initial input was directed. True for Digraph input, false for other graphs and all grids. - Used later to know whenever duplication of edge parameter should be duplicated - (i.e. allow parameter for edge i => j to be used for j => i). - Even if false, giving separate parameters for both directions is still permitted. - """ - directed_edges::Bool """ An iterator over all the edges on the lattice. The format depends on the type of lattice (Cartesian grid, grid, or graph). @@ -51,7 +43,7 @@ struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem edge_iterator::T function LatticeReactionSystem(rs::ReactionSystem{Q}, spatial_reactions::Vector{R}, lattice::S, - num_verts, num_edges, edge_iterator::T; directed_edges = false) where {Q,R, S, T} + num_verts, num_edges, edge_iterator::T) where {Q,R, S, T} # Error checks. if !(R <: AbstractSpatialReaction) error("The second argument must be a vector of AbstractSpatialReaction subtypes.") @@ -85,22 +77,22 @@ struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem foreach(sr -> check_spatial_reaction_validity(rs, sr; edge_parameters=edge_parameters), spatial_reactions) return new{Q,R,S,T}(rs, spatial_reactions, lattice, num_verts, num_edges, num_species, - spat_species, ps, vertex_parameters, edge_parameters, directed_edges, edge_iterator) + spat_species, ps, vertex_parameters, edge_parameters, edge_iterator) end end # Creates a LatticeReactionSystem from a CartesianGrid lattice (cartesian grid). -function LatticeReactionSystem(rs, srs, lattice::CartesianGridRej{S,T}; diagonal_connections=false) where {S,T} +function LatticeReactionSystem(rs, srs, lattice_in::CartesianGridRej{S,T}; diagonal_connections=false) where {S,T} # Error checks. - (length(lattice.dims) > 3) && error("Grids of higher dimension than 3 is currently not supported.") + (length(lattice_in.dims) > 3) && error("Grids of higher dimension than 3 is currently not supported.") - # Ensures that the matrix is on a 3d form - lattice = CartesianGrid((lattice.dims..., fill(1, 3-length(lattice.dims))...)) + # Ensures that the matrix has a 3d form (for intermediary computations, original is passed to constructor). + lattice = CartesianGrid((lattice_in.dims..., fill(1, 3-length(lattice_in.dims))...)) # Counts vertexes and edges. The `num_edges` count formula counts the number of internal, side, # edge, and corner vertexes (on the grid). The number of edges from each depend on whether diagonal # connections are allowed. The formula holds even if l, m, and/or n are 1. - l,m,n = 2,3,4 + l,m,n = lattice.dims num_verts = l * m * n (ni, ns, ne, nc) = diagonal_connections ? (26,17,11,7) : (6,5,4,3) num_edges = ni*(l-2)*(m-2)*(n-2) + # Internal vertexes. @@ -131,29 +123,41 @@ function LatticeReactionSystem(rs, srs, lattice::CartesianGridRej{S,T}; diagonal end end - return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator) + return LatticeReactionSystem(rs, srs, lattice_in, num_verts, num_edges, edge_iterator) end # Creates a LatticeReactionSystem from a Boolean Array lattice (masked grid). -function LatticeReactionSystem(rs, srs, lattice::Array{Bool, T}; diagonal_connections=false) where {T} +function LatticeReactionSystem(rs, srs, lattice_in::Array{Bool, T}; diagonal_connections=false) where {T} # Error checks. - dims = size(lattice) + dims = size(lattice_in) (length(dims) > 3) && error("Grids of higher dimension than 3 is currently not supported.") - # Ensures that the matrix is on a 3d form - lattice = reshape(lattice, [dims...; fill(1, 3-length(dims))]...) + # Ensures that the matrix has a 3d form (for intermediary computations, original is passed to constructor). + # Also gets some basic lattice information. + lattice = reshape(lattice_in, [dims...; fill(1, 3-length(dims))]...) # Counts vertexes (edges have to be counted after the iterator have been created). num_verts = count(lattice) # Creates an iterator over all edges.Currently a full vector of all edges (as pairs). edge_iterator = Vector{Pair{Int64,Int64}}() + + # Makes a template matrix to store each vertex's index. The matrix is 0 where there are no vertex. + idx_matrix = fill(0, size(lattice_in)) + cur_vertex_idx = 0 + for flat_idx in 1:length(lattice) + if lattice[flat_idx] + idx_matrix[flat_idx] = (cur_vertex_idx += 1) + end + end + # Loops through, simultaneously, the coordinates of each position in the grid, as well as that # coordinate's (scalar) flat index. For each grid point, loops through all potential neighbours. - l,m,n = size(lattice) + l, m, n = size(lattice) indices = [(L, M, N) for L in 1:l, M in 1:m, N in 1:n] - flat_indices = 1:num_verts - for ((L, M, N), idx) in zip(indices, flat_indices) + for (L, M, N) in indices + # Ensures that we are in a valid lattice point. + lattice[L,M,N] || continue for LL in max(L - 1, 1):min(L + 1, l), MM in max(M - 1, 1):min(M + 1, m), NN in max(N - 1, 1):min(N + 1, n) @@ -166,27 +170,23 @@ function LatticeReactionSystem(rs, srs, lattice::Array{Bool, T}; diagonal_connec diagonal_connections && (L==LL) && (M==MM) && (N==NN) && continue # Computes the neighbours scalar index. Add that connection to `edge_iterator`. - neighbour_idx = LL + (MM - 1) * l + (NN - 1) * m * l - push!(edge_iterator, idx => neighbour_idx) + push!(edge_iterator, idx_matrix[L,M,N] => idx_matrix[LL,MM,NN]) end end num_edges = length(edge_iterator) - return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator) + return LatticeReactionSystem(rs, srs, lattice_in, num_verts, num_edges, edge_iterator) end -# Creates a LatticeReactionSystem from a DiGraph lattice (graph grid). -function LatticeReactionSystem(rs, srs, lattice::DiGraph; directed_edges = true) +# Creates a LatticeReactionSystem from a (directed) Graph lattice (graph grid). +function LatticeReactionSystem(rs, srs, lattice::DiGraph) num_verts = nv(lattice) num_edges = ne(lattice) - edge_iterator = edges(lattice) - return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator; directed_edges) -end - -# Creates a LatticeReactionSystem from a Graph lattice (graph grid). -function LatticeReactionSystem(rs, srs, lattice::SimpleGraph) - return LatticeReactionSystem(rs, srs, DiGraph(lattice); directed_edges = false) + edge_iterator = [e.src => e.dst for e in edges(lattice)] + return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator) end +# Creates a LatticeReactionSystem from a (undirected) Graph lattice (graph grid). +LatticeReactionSystem(rs, srs, lattice::SimpleGraph) = LatticeReactionSystem(rs, srs, DiGraph(lattice)) ### Lattice ReactionSystem Getters ### @@ -217,9 +217,12 @@ has_grid_lattice(lrs::LatticeReactionSystem) = (has_cartesian_lattice(lrs) || ha # Checks if a LatticeReactionSystem have a graph lattice. has_graph_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa SimpleDiGraph +# Returns the size of the lattice of a LatticeReactionNetwork with a grid lattice. +function grid_size(lrs::LatticeReactionSystem) + has_cartesian_lattice(lrs) && (return lrs.lattice.dims) + has_masked_lattice(lrs) && (return size(lrs.lattice)) + error("Grid size is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") +end + # Returns the dimensions of a LatticeReactionNetwork with a grid lattice. -function grid_dims(lrs::LatticeReactionSystem) - has_cartesian_lattice(lrs) && (return length(lrs.lattice.dims)) - has_masked_lattice(lrs) && (return length(size(lrs.lattice))) - error("Dimensions are only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") -end \ No newline at end of file +grid_dims(lrs::LatticeReactionSystem) = length(grid_size(lrs)) \ No newline at end of file diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index ddfdf0b171..ecf5b6dd86 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -1,26 +1,25 @@ ### Spatial ODE Functor Structures ### # Functor with information for the forcing function of a spatial ODE with spatial movement on a lattice. -struct LatticeTransportODEf{Q,R,S,T} +struct LatticeTransportODEf{S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" - ofunc::Q + ofunc::S """The number of vertices.""" num_verts::Int64 """The number of species.""" num_species::Int64 """The values of the parameters which values are tied to vertexes.""" - vert_ps::Vector{Vector{R}} + vert_ps::Vector{Vector{T}} """ Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length num_verts vector. To avoid re-allocation they are written to this vector. """ - work_vert_ps::Vector{R} + work_vert_ps::Vector{T} """ For each parameter in vert_ps, its value is a vector with length either num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. - This check is done once, and the value stored to this array. - This field (specifically) is an enumerate over that array. + This check is done once, and the value stored to this array. True means a uniform value. """ v_ps_idx_types::Vector{Bool} """ @@ -30,57 +29,67 @@ struct LatticeTransportODEf{Q,R,S,T} If the transport rate is uniform (across all edges), that value is the only value in the vector. Else, there is one value for each edge in the lattice. """ - transport_rates::Vector{SparseMatrixCSC{R, Int64}} + transport_rates::Vector{Pair{Int64,SparseMatrixCSC{T, Int64}}} + """ + For each transport rate in transport_rates, its value is a (sparse) matrix with size either + (num_verts,num_verts) or (1,1). In the second case, that transportation rate is uniform across all edges. + To know how to access transport rate's value (without checking sizes), we can use this vector directly. + True means a uniform value. + """ + t_rate_idx_types::Vector{Bool} """ A matrix, NxM, where N is the number of species with transportation and M the number of vertexes. Each value is the total rate at which that species leaves that vertex (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D). """ - leaving_rates::Matrix{R} - """An (enumerate'ed) iterator over all the edges of the lattice.""" - edges::S - """ - The edge parameters used to create the spatial ODEProblem. Currently unused, - but will be needed to support changing these (e.g. due to events). - Contain one vector for each edge parameter (length one if uniform, else one value for each edge). - """ - edge_ps::Vector{Vector{T}} + leaving_rates::Matrix{T} + """An iterator over all the edges of the lattice.""" + edge_iterator::Vector{Pair{Int64, Int64}} - function LatticeTransportODEf(ofunc::Q, vert_ps::Vector{Vector{R}}, transport_rates::Vector{Pair{Int64, Vector{R}}}, - edge_ps::Vector{Vector{T}}, lrs::LatticeReactionSystem) where {Q,R,T} + function LatticeTransportODEf(ofunc::S, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, transport_rates::Vector{Pair{Int64, SparseMatrixCSC{T, Int64}}}, + lrs::LatticeReactionSystem) where {S,T} + # Records. which parameters and rates are uniform and not. + v_ps_idx_types = map(vp -> length(vp[2]) == 1, vert_ps) + t_rate_idx_types = map(tr -> size(tr[2]) == (1,1), transport_rates) + + # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a vector). + # This vector is sorted according to the parameters order. Here, we simply extract its values only. + vert_ps = [vp[2] for vp in vert_ps] + + # Computes the leaving rates. leaving_rates = zeros(length(transport_rates), lrs.num_verts) for (s_idx, trpair) in enumerate(transport_rates) - rates = last(trpair) - for (e_idx, e) in enumerate(edges(lrs.lattice)) + t_rate = trpair[2] + for e in lrs.edge_iterator # Updates the exit rate for species s_idx from vertex e.src - leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) + leaving_rates[s_idx, e[1]] += get_transport_rate(t_rate, e, t_rate_idx_types[s_idx]) end end - work_vert_ps = zeros(lrs.num_verts) - # 1 if ps are constant across the graph, 0 else. - v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) - eds = lrs.edge_iterator - new{Q,R,typeof(eds),T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, - v_ps_idx_types, transport_rates, leaving_rates, eds, edge_ps) + + # Declares `work_vert_ps` (used as storage during computation) and an iterator over the edges. + work_vert_ps = zeros(length(vert_ps)) + edge_iterator = lrs.edge_iterator + new{S,T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, + v_ps_idx_types, transport_rates, t_rate_idx_types, leaving_rates, edge_iterator) end end # Functor with information for the Jacobian function of a spatial ODE with spatial movement on a lattice. -struct LatticeTransportODEjac{Q,R,S,T} +struct LatticeTransportODEjac{R,S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" - ofunc::Q + ofunc::R """The number of vertices.""" num_verts::Int64 """The number of species.""" num_species::Int64 """The values of the parameters which values are tied to vertexes.""" - vert_ps::Vector{Vector{R}} + vert_ps::Vector{Vector{S}} """ Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length(num_verts) vector. To avoid re-allocation they are written to this vector. """ - work_vert_ps::Vector{R} + work_vert_ps::Vector{S} """ For each parameter in vert_ps, it either have length num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, @@ -90,22 +99,20 @@ struct LatticeTransportODEjac{Q,R,S,T} v_ps_idx_types::Vector{Bool} """Whether the Jacobian is sparse or not.""" sparse::Bool - """The transport rates. Can be a dense matrix (for non-sparse) or as the "nzval" field if sparse.""" - jac_transport::S - """ - The edge parameters used to create the spatial ODEProblem. Currently unused, - but will be needed to support changing these (e.g. due to events). - Contain one vector for each edge parameter (length one if uniform, else one value for each edge). - """ - edge_ps::Vector{Vector{T}} + """The transport rates. This is a dense or sparse matrix (depending on what type of Jacobian is used).""" + jac_transport::T - function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, + function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{S}}}, lrs::LatticeReactionSystem, jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, - edge_ps::Vector{Vector{T}}, sparse::Bool) where {R,S,T} + sparse::Bool) where {R,S} + # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a vector). + # This vector is sorted according to the parameters order. Here, we simply extract its values only. + vert_ps = [vp[2] for vp in vert_ps] + work_vert_ps = zeros(lrs.num_verts) v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) - new{R,S,typeof(jac_transport),T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, - work_vert_ps, v_ps_idx_types, sparse, jac_transport, edge_ps) + new{R,S,typeof(jac_transport)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, + work_vert_ps, v_ps_idx_types, sparse, jac_transport) end end @@ -128,31 +135,37 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = symmap_to_varmap(lrs, p_in) # Converts u0 and p to their internal forms. + # u0 is simply a vector with all the species initial condition values across all vertexes. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. - u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) - # Each parameter value in vert_ps is a vector (with one value for each parameter). - # edge_ps becomes sparse matrix. Here, index (i,j) is its value in the edge from vertex i to vertex j. + u0 = lattice_process_u0(u0_in, species(lrs), lrs) + # vert_ps and `edge_ps` are vector maps, taking each parameter's Symbolics to its value(s). + # vert_ps values are vectors. Here, index (i) is a parameters value in vertex i. + # edge_ps becomes sparse matrix. Here, index (i,j) is a parameters value in the edge from vertex i to vertex j. # Uniform vertex/edge parameters stores only a single value (in a length 1 vector, or size 1x1 sparse matrix). # This is the parameters single value. - vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + # In the `ODEProblem` vert_ps and edge_ps are merged (but for building the ODEFunction, they are separate). + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Creates ODEProblem. ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse, name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) - return ODEProblem(ofun, u0, tspan, vert_ps, args...; kwargs...) + + # Combines `vert_ps` and `edge_ps` to a single vector with values only (not a map). Creates ODEProblem. + ps = [p[2] for p in [vert_ps; edge_ps]] + return ODEProblem(ofun, u0, tspan, ps, args...; kwargs...) end # Builds an ODEFunction for a spatial ODEProblem. -function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T}}, - edge_ps::Vector{Vector{T}}, jac::Bool, sparse::Bool, - name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) where {T} +function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{A,Vector{T}}}, + edge_ps::Vector{Pair{B,SparseMatrixCSC{T, Int64}}}, jac::Bool, sparse::Bool, + name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) where {A,B,T} if remove_conserved error("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s") end # Creates a map, taking (the index in species(lrs) each species (with transportation) # to its transportation rate (uniform or one value for each edge). - transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) + transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) # Prepares the Jacobian and forcing functions (depending on jacobian and sparsity selection). osys = complete(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks)) @@ -164,23 +177,23 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) if sparse - f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) + f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, lrs) jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) - J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, edge_ps, true) + J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, true) jac_prototype = jac_vals else - f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, edge_ps, lrs) - J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, edge_ps, false) + f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, lrs) + J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, false) jac_prototype = nothing end else if sparse ofunc_sparse = ODEFunction(osys; jac = false, sparse = true) - f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) + f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, lrs) jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = false) else ofunc_dense = ODEFunction(osys; jac = false, sparse = false) - f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, edge_ps, lrs) + f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, lrs) jac_prototype = nothing end J = nothing @@ -190,10 +203,10 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} end # Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. -function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, trans_rates, - lrs::LatticeReactionSystem; set_nonzero = false) +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, transport_rates::Vector{Pair{Int64,SparseMatrixCSC{T, Int64}}}, + lrs::LatticeReactionSystem; set_nonzero = false) where T # Finds the indexes of the transport species, and the species with transport only (and no non-spatial dynamics). - trans_species = first.(trans_rates) + trans_species = [tr[1] for tr in transport_rates] trans_only_species = filter(s_idx -> !Base.isstored(ns_jac_prototype, s_idx, s_idx), trans_species) # Finds the indexes of all terms in the non-spatial jacobian. @@ -218,19 +231,19 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, end # Indexes of elements due to spatial dynamics. - for e in edges(lrs.lattice) + for e in lrs.edge_iterator # Indexes due to terms for a species leaves its current vertex (but does not have # non-spatial dynamics). If the non-spatial Jacobian is fully dense, these would already # be accounted for. for s_idx in trans_only_species - i_idxs[idx] = get_index(e.src, s_idx, lrs.num_species) + i_idxs[idx] = get_index(e[1], s_idx, lrs.num_species) j_idxs[idx] = i_idxs[idx] idx += 1 end # Indexes due to terms for species arriving into a new vertex. for s_idx in trans_species - i_idxs[idx] = get_index(e.src, s_idx, lrs.num_species) - j_idxs[idx] = get_index(e.dst, s_idx, lrs.num_species) + i_idxs[idx] = get_index(e[1], s_idx, lrs.num_species) + j_idxs[idx] = get_index(e[2], s_idx, lrs.num_species) idx += 1 end end @@ -240,10 +253,10 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, # Set element values. if set_nonzero - for (s, rates) in trans_rates, (e_idx, e) in enumerate(edges(lrs.lattice)) - idx_src = get_index(e.src, s, lrs.num_species) - idx_dst = get_index(e.dst, s, lrs.num_species) - val = get_component_value(rates, e_idx) + for (s, rates) in transport_rates, e in lrs.edge_iterator + idx_src = get_index(e[1], s, lrs.num_species) + idx_dst = get_index(e[2], s, lrs.num_species) + val = get_transport_rate(rates, e, size(rates)==(1,1)) # Term due to species leaving source vertex. jac_prototype[idx_src, idx_src] -= val @@ -263,11 +276,11 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) # Gets the indices of species at vertex i. idxs = get_indexes(vert_i, f_func.num_species) - # Vector of vertex ps at vert_i. - vert_i_ps = view_vert_ps_vector!(f_func, p, vert_i) + # Updates the vector which contains the vertex parameter values for vertex vert_i. + update_work_vert_ps!(f_func, p, vert_i) # Evaluate reaction contributions to du at vert_i. - f_func.ofunc((@view du[idxs]), (@view u[idxs]), vert_i_ps, t) + f_func.ofunc((@view du[idxs]), (@view u[idxs]), f_func.work_vert_ps, t) end # s_idx is species index among transport species, s is index among all species. @@ -275,14 +288,14 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) # Rate for leaving vert_i for vert_i in 1:(f_func.num_verts) - idx = get_index(vert_i, s, f_func.num_species) - du[idx] -= f_func.leaving_rates[s_idx, vert_i] * u[idx] + idx_src = get_index(vert_i, s, f_func.num_species) + du[idx_src] -= f_func.leaving_rates[s_idx, vert_i] * u[idx_src] end # Add rates for entering a given vertex via an incoming edge. - for (e_idx, e) in enumerate(f_func.edges) - idx_dst = get_index(e.dst, s, f_func.num_species) - idx_src = get_index(e.src, s, f_func.num_species) - du[idx_dst] += get_component_value(rates, e_idx) * u[idx_src] + for e in f_func.edge_iterator + idx_src = get_index(e[1], s, f_func.num_species) + idx_dst = get_index(e[2], s, f_func.num_species) + du[idx_dst] += get_transport_rate(s_idx, f_func, e) * u[idx_src] end end end @@ -294,8 +307,8 @@ function (jac_func::LatticeTransportODEjac)(J, u, p, t) # Update the Jacobian from reaction terms. for vert_i in 1:(jac_func.num_verts) idxs = get_indexes(vert_i, jac_func.num_species) - vert_ps = view_vert_ps_vector!(jac_func, p, vert_i) - jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), vert_ps, t) + update_work_vert_ps!(jac_func, p, vert_i) + jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), jac_func.work_vert_ps, t) end # Updates for the spatial reactions (adds the Jacobian values from the diffusion reactions). diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 1f498630d2..3b18fd31cf 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -15,283 +15,260 @@ end # From u0 input, extracts their values and store them in the internal format. # Internal format: a vector on the form [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]). -function lattice_process_u0(u0_in, u0_syms, num_verts) - # u0 values can be given in various forms. This converts it to a Vector{Vector{}} form. - # Top-level vector: Contains one vector for each species. - # Second-level vector: contain one value if species uniform across lattice, else one value for each vertex). - u0 = lattice_process_input(u0_in, u0_syms, num_verts) - - # Perform various error checks on the (by the user provided) initial conditions. - check_vector_lengths(u0, length(u0_syms), num_verts) - - # Converts the Vector{Vector{}} format to a single Vector (with one values for each species and vertex). - expand_component_values(u0, num_verts) +function lattice_process_u0(u0_in, u0_syms, lrs::LatticeReactionSystem) + # u0 values can be given in various forms. This converts it to a Vector{Pair{Symbolics,...}} form. + # Top-level vector: Maps each species to its value(s). + u0 = lattice_process_input(u0_in, u0_syms) + + # Species' initial condition values can be given in different forms (also depending on the lattice). + # This converts each species's values to a Vector. For species with uniform initial conditions, + # The value vector holds that value only. For spatially heterogeneous initial conditions, + # the vector have teh same length as the number of vertexes (with one value for each). + u0 = vertex_value_map(u0, lrs) + + # Converts the initial condition to a single Vector (with one values for each species and vertex). + return expand_component_values([entry[2] for entry in u0], lrs.num_verts) end # From p input, splits it into diffusion parameters and compartment parameters. # Store these in the desired internal format. -function lattice_process_p(p_in, p_vertex_syms, p_edge_syms, lrs::LatticeReactionSystem) - # If the user provided parameters as a single map (mixing vertex and edge parameters): - # Split into two separate vectors. - vert_ps_in, edge_ps_in = split_parameters(p_in, p_vertex_syms, p_edge_syms) - - # Parameter values can be given in various forms. This converts it to the Vector{Vector{}} form. - vert_ps = lattice_process_input(vert_ps_in, p_vertex_syms, lrs.num_verts) - - # Parameter values can be given in various forms. This converts it to the Vector{Vector{}} form. - edge_ps = lattice_process_input(edge_ps_in, p_edge_syms, lrs.num_edges) - - # If the lattice defined as (N edge) undirected graph, and we provides N/2 values for some edge parameter: - # Presume they want to expand that parameters value so it has the same value in both directions. - lrs.init_digraph || duplicate_trans_params!(edge_ps, lrs) - - # Perform various error checks on the (by the user provided) vertex and edge parameters. - check_vector_lengths(vert_ps, length(p_vertex_syms), lrs.num_verts) - check_vector_lengths(edge_ps, length(p_edge_syms), lrs.num_edges) +function lattice_process_p(ps_in, ps_vertex_syms, ps_edge_syms, lrs::LatticeReactionSystem) + # p values can be given in various forms. This converts it to a Vector{Pair{Symbolics,...}} form. + # Top-level vector: Maps each parameter to its value(s). + # Second-level: Contains either a vector (vertex parameters) or a sparse matrix (edge parameters). + # For uniform parameters these have size 1/1x1. Else, they have size num_verts/num_vertsxnum_verts. + ps = lattice_process_input(ps_in, [ps_vertex_syms; ps_edge_syms]) + + # Split the parameter vector into one for vertex parameters and one for edge parameters. + # Next, converts the values to the correct form (vectors for vert_ps and sparse matrices for edge_ps). + vert_ps, edge_ps = split_parameters(ps, ps_vertex_syms, ps_edge_syms) + vert_ps = vertex_value_map(vert_ps, lrs) + edge_ps = edge_value_map(edge_ps, lrs) return vert_ps, edge_ps end -# Splits parameters into those for the vertexes and those for the edges. +# The input (parameters or initial conditions) may either be a dictionary (symbolics to value(s).) +# or a map (in vector or tuple form) from symbolics to value(s). This converts the input to a +# (Vector) map from symbolics to value(s), where the entries have the same order as `syms`. +function lattice_process_input(input::Dict{BasicSymbolic{Real}, <:Any}, syms::Vector{BasicSymbolic{Real}}) + # Error checks + if !isempty(setdiff(keys(input), syms)) + error("You have provided values for the following unrecognised parameters/initial conditions: $(setdiff(keys(input), syms)).") + end + if !isempty(setdiff(syms, keys(input))) + error("You have not provided values for the following parameters/initial conditions: $(setdiff(syms, keys(input))).") + end -# If they are already split, return that. -split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps -# Providing parameters to a spatial reaction system as a single vector of values (e.g. [1.0, 4.0, 0.1]) is not allowed. -# Either use tuple (e.g. ([1.0, 4.0], [0.1])) or map format (e.g. [A => 1.0, B => 4.0, D => 0.1]). -function split_parameters(ps::Vector{<:Number}, args...) - error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") + return [sym => input[sym] for sym in syms] end -# Splitting is only done for Vectors of Pairs (where the first value is a Symbols, and the second a value). -function split_parameters(ps::Vector{<: Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) - vert_ps_in = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] - edge_ps_in = [p for p in ps if any(isequal(p[1]), p_edge_syms)] - - # Error check, in case some input parameters where neither recognised as vertex or edge parameters. - if (sum(length.([vert_ps_in, edge_ps_in])) != length(ps)) - error("These input parameters are not recognised: $(setdiff(first.(ps), vcat(first.([vert_ps_in, edge_ps_in]))))") - end - - return vert_ps_in, edge_ps_in +function lattice_process_input(input, syms::Vector{BasicSymbolic{Real}}) + lattice_process_input(Dict(input), syms) end -# Input may have the following forms (after potential Symbol maps to Symbolic maps conversions): - # - A vector of values, where the i'th value corresponds to the value of the i'th - # initial condition value (for u0_in), vertex parameter value (for vert_ps_in), or edge parameter value (for edge_ps_in). - # - A vector of vectors of values. The same as previously, - # but here the species/parameter can have different values across the spatial structure. - # - A map of Symbols to values. These can either be a single value (if uniform across the spatial structure) - # or a vector (with different values for each vertex/edge). - # These can be mixed (e.g. [X => 1.0, Y => [1.0, 2.0, 3.0, 4.0]] is allowed). - # - A matrix. E.g. for initial conditions you can have a num_species * num_vertex matrix, - # indicating the value of each species at each vertex. - -# The lattice_process_input function takes input initial conditions/vertex parameters/edge parameters -# of whichever form the user have used, and converts them to the Vector{Vector{}} form used internally. -# E.g. for parameters the top-level vector contain one vector for each parameter (same order as in parameters(::ReactionSystem)). -# If a parameter is uniformly-values across the spatial structure, its vector has a single value. -# Else, it has a number of values corresponding to the number of vertexes/edges (for edge/vertex parameters). -# Initial conditions works similarly. - -# If the input is given in a map form, the vector needs sorting and the first value removed. -# The creates a Vector{Vector{Value}} or Vector{value} form, which is then again sent to lattice_process_input for reprocessing. -function lattice_process_input(input::Vector{<:Pair}, syms::Vector{BasicSymbolic{Real}}, args...) - if !isempty(setdiff(first.(input), syms)) - error("Some input symbols are not recognised: $(setdiff(first.(input), syms)).") - end - sorted_input = sort(input; by = p -> findfirst(isequal(p[1]), syms)) - return lattice_process_input(last.(sorted_input), syms, args...) +# Splits parameters into vertex and edge parameters. +#function split_parameters(ps::Vector{<: Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) +function split_parameters(ps, p_vertex_syms, p_edge_syms) + vert_ps = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] + edge_ps = [p for p in ps if any(isequal(p[1]), p_edge_syms)] + return vert_ps, edge_ps end -# If the input is a matrix: Processes the input and gives it in a form where it is a vector of vectors -# (some of which may have a single value). Sends it back to lattice_process_input for reprocessing. -function lattice_process_input(input::Matrix{<:Number}, args...) - lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) + +# Converts the values for initial condition/vertex parameters to the correct form: +# Map from symbolics to to vectors of length either 1 (for uniform values) or num_verts. +function vertex_value_map(values, lrs) + isempty(values) && (return Pair{BasicSymbolic{Real}, Vector{Float64}}[]) + return [entry[1] => vertex_value_form(entry[2], lrs, entry[1]) for entry in values] end -# Possibly we want to support this type of input at some point. -function lattice_process_input(input::Array{<:Number, 3}, args...) - error("3 dimensional array parameter input currently not supported.") +# Converts the values for a specific component (species/parameter) to the correct vector form. +function vertex_value_form(values, lrs::LatticeReactionSystem, sym) + (values isa AbstractArray) || (return [values]) + if values isa Vector + if has_grid_lattice(lrs) && (size(values) == grid_size(lrs)) + vertex_value_form(values, lrs.num_verts, lrs.lattice, sym) + end + if (length(values) != lrs.num_verts) + error("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertexes ($(lrs.num_verts)).") + end + return values + end + return vertex_value_form(values, lrs.num_verts, lrs.lattice, sym) end -# If the input is a Vector containing both vectors and single values, converts it to the Vector{<:Vector} form. -# Technically this last lattice_process_input is probably not needed. -function lattice_process_input(input::Vector{<:Any}, args...) - isempty(input) ? Vector{Vector{Float64}}() : - lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], - args...) +# Converts values to correct vector form for a Cartesian grid lattice. +function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::CartesianGridRej{S,T}, sym) where {S,T} + if size(values) != lattice.dims + error("The values for $sym did not have the same format as the lattice. Expected a $(lattice.dims) array, got one of size $(size(values))") + end + if (length(values) != num_verts) + error("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertexes ($(num_verts)).") + end + return [values[flat_idx] for flat_idx in 1:num_verts] end -# If the input is of the correct form already, return it. -lattice_process_input(input::Vector{<:Vector}, syms::Vector{BasicSymbolic{Real}}, n::Int64) = input - -# Checks that a value vector have the right length, as well as that of all its sub vectors. -# Error check if e.g. the user does not provide values for all species/parameters, -# or for one: provides a vector of values, but that has the wrong length -# (e.g providing 7 values for one species, but there are 8 vertexes). -function check_vector_lengths(input::Vector{<:Vector}, n_syms, n_locations) - if (length(input)!=n_syms) - error("Missing values for some initial conditions/parameters. Expected $n_syms values, got $(length(input)).") +# Converts values to correct vector form for a masked grid lattice. +function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::Array{Bool, T}, sym) where {T} + if size(values) != size(lattice) + error("The values for $sym did not have the same format as the lattice. Expected a $(size(lattice)) array, got one of size $(size(values))") end - if !isempty(setdiff(unique(length.(input)), [1, n_locations])) - error("Some inputs where given values of inappropriate length.") + return_values = Vector{typeof(values[1])}(undef, num_verts) + cur_idx = 0 + for i = 1:length(lattice) + lattice[i] || continue + return_values[cur_idx += 1] = values[i] end + if (length(return_values) != num_verts) + error("You have provided ($(length(return_values))) values for $sym. This is not equal to the number of vertexes ($(num_verts)).") + end + return return_values end -# For transport parameters, if the lattice was given as an undirected graph of size n: -# this is converted to a directed graph of size 2n. -# If transport parameters are given with n values, we want to use the same value for both directions. -# Since the order of edges in the new graph is non-trivial, this function -# distributes the n input values to a 2n length vector, putting the correct value in each position. -function duplicate_trans_params!(edge_ps::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) - cum_adjacency_counts = [0;cumsum(length.(lrs.lattice.fadjlist[1:end-1]))] - for idx in 1:length(edge_ps) - # If the edge parameter already values for each directed edge, we can continue. - (2length(edge_ps[idx]) == lrs.num_edges) || continue # - - # This entire thing depends on the fact that, in the edges(lattice) iterator, the edges are sorted by: - # (1) Their source node - # (2) Their destination node. - - # A vector where we will put the edge parameters new values. - # Has the correct length (the number of directed edges in the lattice). - new_vals = Vector{Float64}(undef, lrs.num_edges) - # As we loop through the edges of the di-graph, this keeps track of each edge's index in the original graph. - original_edge_count = 0 - for edge in edges(lrs.lattice) # For each edge. - # The digraph conversion only adds edges so that src > dst. - (edge.src < edge.dst) ? (original_edge_count += 1) : continue - # For original edge i -> j, finds the index of i -> j in DiGraph. - idx_fwd = cum_adjacency_counts[edge.src] + findfirst(isequal(edge.dst),lrs.lattice.fadjlist[edge.src]) - # For original edge i -> j, finds the index of j -> i in DiGraph. - idx_bwd = cum_adjacency_counts[edge.dst] + findfirst(isequal(edge.src),lrs.lattice.fadjlist[edge.dst]) - new_vals[idx_fwd] = edge_ps[idx][original_edge_count] - new_vals[idx_bwd] = edge_ps[idx][original_edge_count] - end - # Replaces the edge parameters values with the updated value vector. - edge_ps[idx] = new_vals - end +# Converts the values for initial condition/vertex parameters to the correct form: +# Map from symbolics to to vectors of length either 1 (for uniform values) or num_verts. +function edge_value_map(values, lrs) + isempty(values) && (return Pair{BasicSymbolic{Real}, SparseMatrixCSC{Float64, Int64}}[]) + return [entry[1] => edge_value_form(entry[2], lrs, entry[1]) for entry in values] end +# Converts the values for a specific component (species/parameter) to the correct vector form. +function edge_value_form(values, lrs::LatticeReactionSystem, sym) + # If a scalar have been given, converts it to a size (1,1) sparse matrix. + (values isa SparseMatrixCSC) || (return sparse([1], [1], [values])) + + # Error checks. + if nnz(values) != lrs.num_edges + error("You have provided ($(nnz(values))) values for $sym. This is not equal to the number of edges ($(lrs.num_edges)).") + end + if !all(Base.isstored(values, e[1], e[2]) for e in lrs.edge_iterator) + error("Values was not provided for some edges for edge parameter $sym.") + end -# For a set of input values on the given forms, and their symbolics, convert into a dictionary. -vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) -# Produces a dictionary with all parameter values. -function param_dict(vert_ps, edge_ps, lrs) - merge(vals_to_dict(vertex_parameters(lrs), vert_ps), - vals_to_dict(edge_parameters(lrs), edge_ps)) + return values end -# Computes the transport rates and stores them in a desired format -# (a Dictionary from species index to rates across all edges). -function compute_all_transport_rates(vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) - # Creates a dict, allowing us to access the values of wll parameters. - p_val_dict = param_dict(vert_ps, edge_ps, lrs) +# Creates a map, taking each species (with transportation) to its transportation rate. +# The species is represented by its index (in species(lrs). +# If the rate is uniform across all edges, the transportation rate will be a size (1,1) sparse matrix. +# Else, the rate will be a size (num_verts,num_verts) sparse matrix. +# In the first step, computes a map from species symbolics form to value(s). +# Second step converts to map from species index to value(s). +function make_sidxs_to_transrate_map(vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, + edge_ps::Vector{Pair{BasicSymbolic{Real},SparseMatrixCSC{T, Int64}}}, + lrs::LatticeReactionSystem) where T + p_val_dict = Dict(vcat(vert_ps, edge_ps)) + transport_rates_speciesmap = compute_all_transport_rates(p_val_dict, lrs) + return Pair{Int64,SparseMatrixCSC{T, Int64}}[ + speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] for spat_rates in transport_rates_speciesmap + ] +end +# Computes the transport rates for all species with transportation rates. Output is a map +# taking each species; symbolics form to its transportation rates across all edges. +function compute_all_transport_rates(p_val_dict, lrs::LatticeReactionSystem) # For all species with transportation, compute their transportation rate (across all edges). # This is a vector, pairing each species to these rates. - unsorted_rates = [s => compute_transport_rates(get_transport_rate_law(s, lrs), p_val_dict, lrs.num_edges) + unsorted_rates = [s => compute_transport_rates(get_transport_rate_law(s, lrs), p_val_dict, lrs) for s in spatial_species(lrs)] # Sorts all the species => rate pairs according to their species index in species(::ReactionSystem). - return sort(unsorted_rates; by=rate -> findfirst(isequal(rate[1]), species(lrs))) + return sort(unsorted_rates; by = rate -> findfirst(isequal(rate[1]), species(lrs))) end # For a species, retrieves the symbolic expression for its transportation rate # (likely only a single parameter, such as `D`, but could be e.g. L*D, where L and D are parameters). -# We could allows several transportation reactions for one species and simply sum them though, easy change. -function get_transport_rate_law(s::BasicSymbolic{Real}, lrs::LatticeReactionSystem) +# If there are several transportation reactions for the species, their sum is used. +#function get_transport_rate_law(s::BasicSymbolic{Real}, lrs::LatticeReactionSystem) +function get_transport_rate_law(s, lrs) rates = filter(sr -> isequal(s, sr.species), lrs.spatial_reactions) - (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") - return rates[1].rate + return sum(getfield.(rates, :rate)) end # For the numeric expression describing the rate of transport (likely only a single parameter, e.g. `D`), # and the values of all our parameters, computes the transport rate(s). # If all parameters the rate depend on are uniform all edges, this becomes a length 1 vector. # Else a vector with each value corresponding to the rate at one specific edge. -function compute_transport_rates(rate_law::Num, - p_val_dict::Dict{SymbolicUtils.BasicSymbolic{Real}, Vector{Float64}}, num_edges::Int64) +#function compute_transport_rates(rate_law::Num, p_val_dict, lrs::LatticeReactionSystem) +function compute_transport_rates(rate_law, p_val_dict, lrs) # Finds parameters involved in rate and create a function evaluating the rate law. relevant_ps = Symbolics.get_variables(rate_law) rate_law_func = drop_expr(@RuntimeGeneratedFunction(build_function(rate_law, relevant_ps...))) - # If all these parameters are spatially uniform. `rates` becomes a vector with 1 value. - if all(length(p_val_dict[P]) == 1 for P in relevant_ps) - return [rate_law_func([p_val_dict[p][1] for p in relevant_ps]...)] - # If at least on parameter the rate depends on have a value varying across all edges, - # we have to compute one rate value for each edge. + # If all these parameters are spatially uniform, the rates becomes a size (1,1) sparse matrix. + # Else, the rates becomes a size (num_verts,num_verts) sparse matrix. + if all(size(p_val_dict[p]) == (1,1) for p in relevant_ps) + relevant_p_vals = [get_edge_value(p_val_dict[p], [1 => 1]) for p in relevant_ps] + return sparse([1],[1],rate_law_func(relevant_p_vals...)) else - return [rate_law_func([get_component_value(p_val_dict[p], idxE) for p in relevant_ps]...) - for idxE in 1:num_edges] + transport_rates = spzeros(lrs.num_verts, lrs.num_verts) + for e in lrs.edge_iterator + relevant_p_vals = [get_edge_value(p_val_dict[p], e) for p in relevant_ps] + transport_rates[e...] = rate_law_func(relevant_p_vals...)[1] + end + return transport_rates end end -# Creates a map, taking each species (with transportation) to its transportation rate. -# The species is represented by its index (in species(lrs). -# If the rate is uniform across all edges, the vector will be length 1 (with this value), -# else there will be a separate value for each edge. -# Pair{Int64, Vector{T}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). -function make_sidxs_to_transrate_map(vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{T}}, - lrs::LatticeReactionSystem) where T - transport_rates_speciesmap = compute_all_transport_rates(vert_ps, edge_ps, lrs) - return Pair{Int64, Vector{T}}[ - speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] for spat_rates in transport_rates_speciesmap - ] +# Produces a dictionary with all parameters' values. Vertex parameters have their values converted to +# a sparse matrix (with one value for each edge, always using the source vertex's value) +function param_dict(vert_ps, edge_ps, lrs) + return merge(Dict(zip(vertex_parameters(lrs), vert_ps)), Dict(zip(edge_parameters(lrs), edge_ps))) end ### Accessing Unknown & Parameter Array Values ### + +# Converts a vector of vectors to a long vector. +# These are used when the initial condition is converted to a single vector (from vector of vector form). +function expand_component_values(values, num_verts) + vcat([get_vertex_value.(values, vert) for vert in 1:num_verts]...) +end + # Gets the index in the u array of species s in vertex vert (when their are num_species species). get_index(vert::Int64, s::Int64, num_species::Int64) = (vert - 1) * num_species + s # Gets the indexes in the u array of all species in vertex vert (when their are num_species species). get_indexes(vert::Int64, num_species::Int64) = ((vert - 1) * num_species + 1):(vert * num_species) -# For vectors of length 1 or n, we want to get value idx (or the one value, if length is 1). -# This function gets that. Here: -# - values is the vector with the values of the component across all locations -# (where the internal vectors may or may not be of size 1). -# - component_idx is the initial condition species/vertex parameter/edge parameters's index. -# This is predominantly used for parameters, for initial conditions, -# it is only used once (at initialisation) to re-process the input vector. -# - location_idx is the index of the vertex or edge for which we wish to access a initial condition or parameter values. -# The first two function takes the full value vector, and call the function of at the components specific index. -function get_component_value(values::Vector{<:Vector}, component_idx::Int64, - location_idx::Int64) - get_component_value(values[component_idx], location_idx) -end -# Sometimes we have pre-computed, for each component, whether it's vector is length 1 or not. -# This is stored in location_types. -function get_component_value(values::Vector{<:Vector}, component_idx::Int64, - location_idx::Int64, location_types::Vector{Bool}) - get_component_value(values[component_idx], location_idx, location_types[component_idx]) +# Returns the value of a parameter in an edge. For vertex parameters, uses their values in the source. +function get_edge_value(values::Vector{T}, edge) where {T} + return (length(values) == 1) ? values[1] : values[edge[1]] end -# For a components value (which is a vector of either length 1 or some other length), retrieves its value. -function get_component_value(values::Vector{<:Number}, location_idx::Int64) - get_component_value(values, location_idx, length(values) == 1) +function get_edge_value(values::SparseMatrixCSC{T, Int64}, edge) where {T} + return (size(values) == (1,1)) ? values[1,1] : values[edge[1],edge[2]] end -# Again, the location type (length of the value vector) may be pre-computed. -function get_component_value(values::Vector{<:Number}, location_idx::Int64, - location_type::Bool) - location_type ? values[1] : values[location_idx] + +# Returns the value of an initial condition of parameter in a vertex. +function get_vertex_value(values::Vector{T}, vert_idx) where {T} + return (length(values) == 1) ? values[1] : values[vert_idx] end -# Converts a vector of vectors to a long vector. -# These are used when the initial condition is converted to a single vector (from vector of vector form). -function expand_component_values(values::Vector{<:Vector}, n) - vcat([get_component_value.(values, comp) for comp in 1:n]...) + + + + + + + + +# Finds the transport rate of a parameter going from a source vertex to a destination vertex. +function get_transport_rate(transport_rate, edge::Pair{Int64,Int64}, t_rate_idx_types::Bool) + return t_rate_idx_types ? transport_rate[1,1] : transport_rate[edge[1],edge[2]] end -function expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) - vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) +# Finds the transportation rate for a specific species and a `LatticeTransportODEf` struct. +function get_transport_rate(trans_s_idx::Int64, f_func::LatticeTransportODEf, edge::Pair{Int64,Int64}) + get_transport_rate(f_func.transport_rates[trans_s_idx][2], edge, f_func.t_rate_idx_types[trans_s_idx]) end -# Creates a view of the vert_ps vector at a given location. -# Provides a work vector to which the converted vector is written. -function view_vert_ps_vector!(work_vert_ps, vert_ps, comp, enumerated_vert_ps_idx_types) + + +# Updates the internal work_vert_ps vector for a given location. +# To this vector, we write the systems parameter values at a specific vertex. +function update_work_vert_ps!(work_vert_ps, vert_ps, comp, vert_ps_idx_types) # Loops through all parameters. - for (idx,loc_type) in enumerated_vert_ps_idx_types + for (idx,loc_type) in enumerate(vert_ps_idx_types) # If the parameter is uniform across the spatial structure, it will have a length-1 value vector # (which value we write to the work vector). # Else, we extract it value at the specific location. work_vert_ps[idx] = (loc_type ? vert_ps[idx][1] : vert_ps[idx][comp]) end - return work_vert_ps end # Input is always either a LatticeTransportODEf or LatticeTransportODEjac function (which fields we then pass on). -function view_vert_ps_vector!(lt_ode_func, vert_ps, comp) - return view_vert_ps_vector!(lt_ode_func.work_vert_ps, vert_ps, comp, enumerate(lt_ode_func.v_ps_idx_types)) +function update_work_vert_ps!(lt_ode_func, vert_ps, comp) + return update_work_vert_ps!(lt_ode_func.work_vert_ps, vert_ps, comp, lt_ode_func.v_ps_idx_types) end # Expands a u0/p information stored in Vector{Vector{}} for to Matrix form diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index 20476eeee9..52c13dbc68 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -15,7 +15,7 @@ rng = StableRNG(12345) t = default_t() ### Tests Simulations Don't Error ### -for grid in [small_2d_grid, short_path, small_directed_cycle, +for grid in [small_2d_graph_grid, short_path, small_directed_cycle, small_1d_cartesian_grid, small_2d_cartesian_grid, small_3d_cartesian_grid, small_1d_masked_grid, small_2d_masked_grid, small_3d_masked_grid, random_2d_masked_grid] @@ -25,32 +25,28 @@ for grid in [small_2d_grid, short_path, small_directed_cycle, u0_1 = [:S => 999.0, :I => 1.0, :R => 0.0] u0_2 = [:S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), :I => 1.0, :R => 0.0] u0_3 = [ - :S => 950.0, - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ] - u0_4 = [ :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), :I => 50 * rand_v_vals(lrs.lattice), :R => 50 * rand_v_vals(lrs.lattice), ] - for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + for u0 in [u0_1, u0_2, u0_3] p1 = [:α => 0.1 / 1000, :β => 0.01] p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] p3 = [ :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), :β => 0.02 * rand_v_vals(lrs.lattice), ] - for pV in [p1, p2, p3, p4] + for pV in [p1, p2, p3] pE_1 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) pE_2 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), + pE_3 = map(sp -> sp => rand_e_vals(lrs, 0.01), spatial_param_syms(lrs)) - for pE in [pE_1, pE_2, pE_3, pE_4] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE)) + for pE in [pE_1, pE_2, pE_3] + isempty(spatial_param_syms(lrs)) && (pE = Vector{Pair{Symbol, Float64}}()) + oprob = ODEProblem(lrs, u0, (0.0, 500.0), [pV; pE]) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), [pV; pE]; jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) end end @@ -63,23 +59,24 @@ for grid in [small_2d_grid, short_path, small_directed_cycle, u0_1 = [:X => 1.0, :Y => 20.0] u0_2 = [:X => rand_v_vals(lrs.lattice, 10.0), :Y => 2.0] u0_3 = [:X => rand_v_vals(lrs.lattice, 20), :Y => rand_v_vals(lrs.lattice, 10)] - for u0 in [u0_1, u0_2, u0_3, u0_4] + for u0 in [u0_1, u0_2, u0_3] p1 = [:A => 1.0, :B => 4.0] p2 = [:A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :B => 4.0] p3 = [ :A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :B => 4.0 .+ rand_v_vals(lrs.lattice, 1.0), ] - for pV in [p1, p2, p3, p4] + for pV in [p1, p2, p3] pE_1 = map(sp -> sp => 0.2, spatial_param_syms(lrs)) pE_2 = map(sp -> sp => rand(rng), spatial_param_syms(lrs)) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), + pE_3 = map(sp -> sp => rand_e_vals(lrs, 0.2), spatial_param_syms(lrs)) - for pE in [pE_1, pE_2, pE_3, pE_4] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + for pE in [pE_1, pE_2, pE_3] + isempty(spatial_param_syms(lrs)) && (pE = Vector{Pair{Symbol, Float64}}()) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), [pV; pE]) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); sparse = false) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), [pV; pE]; sparse = false) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) end end @@ -96,7 +93,7 @@ let pV = brusselator_p pE = [:dX => 0.2] oprob_nonspatial = ODEProblem(brusselator_system, u0, (0.0, 100.0), pV) - oprob_spatial = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + oprob_spatial = ODEProblem(lrs, u0, (0.0, 100.0), [pV; pE]) sol_nonspatial = solve(oprob_nonspatial, QNDF(); abstol = 1e-12, reltol = 1e-12) sol_spatial = solve(oprob_spatial, QNDF(); abstol = 1e-12, reltol = 1e-12) @@ -119,7 +116,9 @@ let lattice = path_graph(3) lrs = LatticeReactionSystem(rs, [tr], lattice); - D_vals = [0.2, 0.2, 0.3, 0.3] + D_vals = spzeros(3,3) + D_vals[1,2] = 0.2; D_vals[2,1] = 0.2; + D_vals[2,3] = 0.3; D_vals[3,2] = 0.3; u0 = [:X => [1.0, 2.0, 3.0], :Y => 1.0] ps = [:pX => [2.0, 2.5, 3.0], :pY => 0.5, :d => 0.1, :D => D_vals] oprob = ODEProblem(lrs, u0, (0.0, 0.0), ps; jac=true, sparse=true) @@ -131,7 +130,8 @@ let pX1, pX2, pX3 = pX pY, = pY d, = d - D1, D2, D3, D4 = D_vals + D1 = D_vals[1,2]; D2 = D_vals[2,1]; + D3 = D_vals[2,3]; D4 = D_vals[3,2]; du[1] = pX1 - d*X1 - D1*X1 + D2*X2 du[2] = pY*X1 - d*Y1 du[3] = pX2 - d*X2 + D1*X1 - (D2+D3)*X2 + D4*X3 @@ -145,7 +145,8 @@ let pX1, pX2, pX3 = pX pY, = pY d, = d - D1, D2, D3, D4 = D_vals + D1 = D_vals[1,2]; D2 = D_vals[2,1]; + D3 = D_vals[2,3]; D4 = D_vals[3,2]; J .= 0.0 @@ -195,7 +196,7 @@ let u0 = [ :X => 1.0 .+ rand_v_vals(lrs.lattice), :Y => 2.0 * rand_v_vals(lrs.lattice), - :XY => 0.5, + :XY => 0.5 ] oprob = ODEProblem(lrs, u0, (0.0, 1000.0), binding_p; tstops = 0.1:0.1:1000.0) ss = solve(oprob, Tsit5()).u[end] @@ -207,14 +208,14 @@ end # Checks that various combinations of jac and sparse gives the same result. let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_graph_grid) u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] pV = brusselator_p pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = false) - oprob_sparse = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = true) - oprob_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = false) - oprob_sparse_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = true) + oprob = ODEProblem(lrs, u0, (0.0, 50.0), [pV; pE]; jac = false, sparse = false) + oprob_sparse = ODEProblem(lrs, u0, (0.0, 50.0), [pV; pE]; jac = false, sparse = true) + oprob_jac = ODEProblem(lrs, u0, (0.0, 50.0), [pV; pE]; jac = true, sparse = false) + oprob_sparse_jac = ODEProblem(lrs, u0, (0.0, 50.0), [pV; pE]; jac = true, sparse = true) ss = solve(oprob, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end] @test all(isapprox.(ss, @@ -228,42 +229,6 @@ let rtol = 0.0001)) end -# Checks that, when non directed graphs are provided, the parameters are re-ordered correctly. -let - # Create the same lattice (one as digraph, one not). Algorithm depends on Graphs.jl reordering edges, hence the jumbled order. - lattice_1 = SimpleGraph(5) - lattice_2 = SimpleDiGraph(5) - - add_edge!(lattice_1, 5, 2) - add_edge!(lattice_1, 1, 4) - add_edge!(lattice_1, 1, 3) - add_edge!(lattice_1, 4, 3) - add_edge!(lattice_1, 4, 5) - - add_edge!(lattice_2, 4, 1) - add_edge!(lattice_2, 3, 4) - add_edge!(lattice_2, 5, 4) - add_edge!(lattice_2, 5, 2) - add_edge!(lattice_2, 4, 3) - add_edge!(lattice_2, 4, 5) - add_edge!(lattice_2, 3, 1) - add_edge!(lattice_2, 2, 5) - add_edge!(lattice_2, 1, 4) - add_edge!(lattice_2, 1, 3) - - lrs_1 = LatticeReactionSystem(SIR_system, SIR_srs_2, lattice_1) - lrs_2 = LatticeReactionSystem(SIR_system, SIR_srs_2, lattice_2) - - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] - pV = [:α => 0.1 / 1000, :β => 0.01] - - pE_1 = [:dS => [1.3, 1.4, 2.5, 3.4, 4.5], :dI => 0.01, :dR => 0.02] - pE_2 = [:dS => [1.3, 1.4, 2.5, 1.3, 3.4, 1.4, 3.4, 4.5, 2.5, 4.5], :dI => 0.01, :dR => 0.02] - ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), (pV, pE_1)), Tsit5()).u[end] - ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), (pV, pE_2)), Tsit5()).u[end] - @test all(isapprox.(ss_1, ss_2)) -end - ### Test Grid Types ### # Tests that identical lattices (using different types of lattices) give identical results. @@ -273,9 +238,9 @@ let lrs1_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_1d_masked_grid) lrs1_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_1d_graph_grid) - oprob1_cartesian = ODEProblem(lrs1_cartesian, sigmaB_u0, (0.0,10.0), sigmaB_p) - oprob1_masked = ODEProblem(lrs1_masked, sigmaB_u0, (0.0,10.0), sigmaB_p) - oprob1_graph = ODEProblem(lrs1_graph, sigmaB_u0, (0.0,10.0), sigmaB_p) + oprob1_cartesian = ODEProblem(lrs1_cartesian, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) + oprob1_masked = ODEProblem(lrs1_masked, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) + oprob1_graph = ODEProblem(lrs1_graph, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) @test solve(oprob1_cartesian, QNDF()) == solve(oprob1_masked, QNDF()) == solve(oprob1_graph, QNDF()) # 2d lattices. @@ -283,9 +248,9 @@ let lrs2_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_masked_grid) lrs2_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_graph_grid) - oprob2_cartesian = ODEProblem(lrs2_cartesian, sigmaB_u0, (0.0,10.0), sigmaB_p) - oprob2_masked = ODEProblem(lrs2_masked, sigmaB_u0, (0.0,10.0), sigmaB_p) - oprob2_graph = ODEProblem(lrs2_graph, sigmaB_u0, (0.0,10.0), sigmaB_p) + oprob2_cartesian = ODEProblem(lrs2_cartesian, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) + oprob2_masked = ODEProblem(lrs2_masked, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) + oprob2_graph = ODEProblem(lrs2_graph, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) @test solve(oprob2_cartesian, QNDF()) == solve(oprob2_masked, QNDF()) == solve(oprob2_graph, QNDF()) # 3d lattices. @@ -293,36 +258,39 @@ let lrs3_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_3d_masked_grid) lrs3_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_3d_graph_grid) - oprob3_cartesian = ODEProblem(lrs3_cartesian, sigmaB_u0, (0.0,10.0), sigmaB_p) - oprob3_masked = ODEProblem(lrs3_masked, sigmaB_u0, (0.0,10.0), sigmaB_p) - oprob3_graph = ODEProblem(lrs3_graph, sigmaB_u0, (0.0,10.0), sigmaB_p) + oprob3_cartesian = ODEProblem(lrs3_cartesian, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) + oprob3_masked = ODEProblem(lrs3_masked, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) + oprob3_graph = ODEProblem(lrs3_graph, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) @test solve(oprob3_cartesian, QNDF()) == solve(oprob3_masked, QNDF()) == solve(oprob3_graph, QNDF()) end # Tests that input parameter and u0 values can be given using different types of input for 2d lattices. # Tries both for cartesian and masked (where all vertexes are `true`). +# Tries for Vector, Tuple, and Dictionary inputs. let - for lattice in [CartesianGrid(3,4), fill(true, 3, 4)] - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, lattice) + for lattice in [CartesianGrid((4,3)), fill(true, 4, 3)] + lrs = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) # Initial condition values. - S_vals_vec = [100, 200, 300, 100, 100, 100, 200, 200, 200, 300, 300, 300] - S_vals_mat = [100, 200, 300; 100, 100, 100; 200, 200, 200; 300, 300, 300] + S_vals_vec = [100., 100., 200., 300., 200., 100., 200., 300., 300., 100., 200., 300.] + S_vals_mat = [100. 200. 300.; 100. 100. 100.; 200. 200. 200.; 300. 300. 300.] SIR_u0_vec = [:S => S_vals_vec, :I => 1.0, :R => 0.0] SIR_u0_mat = [:S => S_vals_mat, :I => 1.0, :R => 0.0] - + # Parameter values. - β_vals_vec = [0.01, 0.02, 0.03, 0.01, 0.01, 0.02, 0.02, 0.02, 0.02, 0.03, 0.03, 0.03] - β_vals_mat = [0.01 0.02 0.03; 0.01 0.01 0.02; 0.02 0.02 0.02; 0.03 0.03 0.03] - SIR_p_vec = [:α => 0.1 / 1000, :β => β_vals_vec] - SIR_p_mat = [:α => 0.1 / 1000, :β => β_vals_mat] - - sol1 = solve(ODEProblem(lrs, SIR_u0_vec, (0.0, 10.0), SIR_p_vec)) - sol2 = solve(ODEProblem(lrs, SIR_u0_mat, (0.0, 10.0), SIR_p_vec)) - sol3 = solve(ODEProblem(lrs, SIR_u0_vec, (0.0, 10.0), SIR_p_mat)) - sol4 = solve(ODEProblem(lrs, SIR_u0_mat, (0.0, 10.0), SIR_p_mat)) - - @test sol1 == sol2 == sol3 = sol4 + β_vals_vec = [0.01, 0.01, 0.02, 0.03, 0.02, 0.01, 0.02, 0.03, 0.03, 0.01, 0.02, 0.03] + β_vals_mat = [0.01 0.02 0.03; 0.01 0.01 0.01; 0.02 0.02 0.02; 0.03 0.03 0.03] + SIR_p_vec = [:α => 0.1 / 1000, :β => β_vals_vec, :dS => 0.01] + SIR_p_mat = [:α => 0.1 / 1000, :β => β_vals_mat, :dS => 0.01] + + oprob = ODEProblem(lrs, SIR_u0_vec, (0.0, 10.0), SIR_p_vec) + sol_base = solve(oprob, Tsit5()) + for u0_base in [SIR_u0_vec, SIR_u0_mat], ps_base in [SIR_p_vec, SIR_p_mat] + for u0 in [u0_base, Tuple(u0_base), Dict(u0_base)], ps in [ps_base, Tuple(ps_base), Dict(ps_base)] + sol = solve(ODEProblem(lrs, u0, (0.0, 10.0), ps), Tsit5()) + @test sol == sol_base + end + end end end @@ -330,28 +298,29 @@ end # Tries when several of the mask values are `false`. let lattice = [true true false; true false false; true true true; false true true] - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, lattice) - + lrs = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) + # Initial condition values. 999 is used for empty points. - S_vals_vec = [100, 200, 100, 200, 200, 200, 300, 300] - S_vals_mat = [100, 200, 999; 100, 999, 999; 200, 200, 200; 999, 300, 300] + S_vals_vec = [100.0, 100.0, 200.0, 200.0, 200.0, 300.0, 200.0, 300.0] + S_vals_mat = [100.0 200.0 999.0; 100.0 999.0 999.0; 200.0 200.0 200.0; 999.0 300.0 300.0] S_vals_sparse_mat = sparse(S_vals_mat .* lattice) SIR_u0_vec = [:S => S_vals_vec, :I => 1.0, :R => 0.0] SIR_u0_mat = [:S => S_vals_mat, :I => 1.0, :R => 0.0] SIR_u0_sparse_mat = [:S => S_vals_sparse_mat, :I => 1.0, :R => 0.0] - + # Parameter values. 9.99 is used for empty points. - β_vals_vec = [0.01, 0.02, 0.03, 0.01, 0.01, 0.02, 0.02, 0.02, 0.02, 0.03, 0.03, 0.03] + β_vals_vec = [0.01, 0.01, 0.02, 0.02, 0.02, 0.03, 0.02, 0.03] β_vals_mat = [0.01 0.02 9.99; 0.01 9.99 9.99; 0.02 0.02 0.02; 9.99 0.03 0.03] β_vals_sparse_mat = sparse(β_vals_mat .* lattice) - SIR_p_vec = [:α => 0.1 / 1000, :β => β_vals_vec] - SIR_p_mat = [:α => 0.1 / 1000, :β => β_vals_mat] - SIR_p_sparse_mat = [:α => 0.1 / 1000, :β => β_vals_sparse_mat] - - sol = solve(ODEProblem(lrs, SIR_u0_vec, (0.0, 10.0), SIR_p_vec)) + SIR_p_vec = [:α => 0.1 / 1000, :β => β_vals_vec, :dS => 0.01] + SIR_p_mat = [:α => 0.1 / 1000, :β => β_vals_mat, :dS => 0.01] + SIR_p_sparse_mat = [:α => 0.1 / 1000, :β => β_vals_sparse_mat, :dS => 0.01] + + oprob = ODEProblem(lrs, SIR_u0_vec, (0.0, 10.0), SIR_p_vec) + sol = solve(oprob, Tsit5()) for u0 in [SIR_u0_vec, SIR_u0_mat, SIR_u0_sparse_mat] for p in [SIR_p_vec, SIR_p_mat, SIR_p_sparse_mat] - @test sol == solve(ODEProblem(lrs, u0, (0.0, 10.0), p)) + @test sol == solve(ODEProblem(lrs, u0, (0.0, 10.0), p), Tsit5()) end end end @@ -367,13 +336,13 @@ let tr_macros_1 = @transport_reaction dS S tr_macros_2 = @transport_reaction dI I - lrs_1 = LatticeReactionSystem(SIR_system, [tr_1, tr_2], small_2d_grid) - lrs_2 = LatticeReactionSystem(SIR_system, [tr_macros_1, tr_macros_2], small_2d_grid) + lrs_1 = LatticeReactionSystem(SIR_system, [tr_1, tr_2], small_2d_graph_grid) + lrs_2 = LatticeReactionSystem(SIR_system, [tr_macros_1, tr_macros_2], small_2d_graph_grid) u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] pV = [:α => 0.1 / 1000, :β => 0.01] pE = [:dS => 0.01, :dI => 0.01] - ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] - ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] + ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), [pV; pE]), Tsit5()).u[end] + ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), [pV; pE]), Tsit5()).u[end] @test all(isapprox.(ss_1, ss_2)) end @@ -383,17 +352,17 @@ let SIR_tr_I_alt = @transport_reaction dI1*dI2 I SIR_tr_R_alt = @transport_reaction log(dR1)+dR2 R SIR_srs_2_alt = [SIR_tr_S_alt, SIR_tr_I_alt, SIR_tr_R_alt] - lrs_1 = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - lrs_2 = LatticeReactionSystem(SIR_system, SIR_srs_2_alt, small_2d_grid) - + lrs_1 = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_graph_grid) + lrs_2 = LatticeReactionSystem(SIR_system, SIR_srs_2_alt, small_2d_graph_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] pV = [:α => 0.1 / 1000, :β => 0.01] pE_1 = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - pE_2 = [:dS1 => 0.005, :dS1 => 0.005, :dI1 => 2, :dI2 => 0.005, :dR1 => 1.010050167084168, :dR2 => 1.0755285551056204e-16] - - ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), (pV, pE_1)), Tsit5()).u[end] - ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), (pV, pE_2)), Tsit5()).u[end] - @test all(isapprox.(ss_1, ss_2)) + pE_2 = [:dS1 => 0.003, :dS2 => 0.007, :dI1 => 2, :dI2 => 0.005, :dR1 => 1.010050167084168, :dR2 => 1.0755285551056204e-16] + + ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), [pV; pE_1]), Tsit5()).u[end] + ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), [pV; pE_2]), Tsit5()).u[end] + @test ss_1 == ss_2 end # Tries various ways of creating TransportReactions. @@ -422,7 +391,7 @@ let tr_alt_1_6 = @transport_reaction dCu_ELigand Cu_ELigand tr_alt_1_7 = @transport_reaction dNewspecies2 Newspecies2 CuH_Amination_srs_alt_1 = [tr_alt_1_1, tr_alt_1_2, tr_alt_1_3, tr_alt_1_4, tr_alt_1_5, tr_alt_1_6, tr_alt_1_7] - lrs_1 = LatticeReactionSystem(CuH_Amination_system_alt_1, CuH_Amination_srs_alt_1, small_2d_grid) + lrs_1 = LatticeReactionSystem(CuH_Amination_system_alt_1, CuH_Amination_srs_alt_1, small_2d_graph_grid) CuH_Amination_system_alt_2 = @reaction_network begin @species Newspecies1(t) Newspecies2(t) @@ -448,53 +417,30 @@ let tr_alt_2_6 = TransportReaction(dCu_ELigand, Cu_ELigand) tr_alt_2_7 = TransportReaction(dNewspecies2, Newspecies2) CuH_Amination_srs_alt_2 = [tr_alt_2_1, tr_alt_2_2, tr_alt_2_3, tr_alt_2_4, tr_alt_2_5, tr_alt_2_6, tr_alt_2_7] - lrs_2 = LatticeReactionSystem(CuH_Amination_system_alt_2, CuH_Amination_srs_alt_2, small_2d_grid) + lrs_2 = LatticeReactionSystem(CuH_Amination_system_alt_2, CuH_Amination_srs_alt_2, small_2d_graph_grid) u0 = [CuH_Amination_u0; :Newspecies1 => 0.1; :Newspecies2 => 0.1] pV = [CuH_Amination_p; :dLigand => 0.01; :dSilane => 0.01; :dCu_ELigand => 0.009; :dStyrene => -10000.0] pE = [:dAmine_E => 0.011, :dNewspecies1 => 0.013, :dDecomposition => 0.015, :dNewspecies2 => 0.016, :dCuoAc => -10000.0] - ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] - ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] + ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), [pV; pE]), Tsit5()).u[end] + ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), [pV; pE]), Tsit5()).u[end] @test all(isequal.(ss_1, ss_2)) end ### Tests Special Cases ### -# Create network with various combinations of graph/di-graph and parameters. +# Create network using either graphs or di-graphs. let lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_digraph.lattice), :R => 0.0] + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_digraph), :R => 0.0] pV = SIR_p - pE_digraph_1 = [:dS => [0.10, 0.12, 0.10, 0.14, 0.12, 0.14], :dI => 0.01, :dR => 0.01] - pE_digraph_2 = [[0.10, 0.12, 0.10, 0.14, 0.12, 0.14], 0.01, 0.01] - pE_digraph_3 = [0.10 0.12 0.10 0.14 0.12 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01 0.01 0.01] - pE_graph_1 = [:dS => [0.10, 0.12, 0.14], :dI => 0.01, :dR => 0.01] - pE_graph_2 = [[0.10, 0.12, 0.14], 0.01, 0.01] - pE_graph_3 = [0.10 0.12 0.14; 0.01 0.01 0.01; 0.01 0.01 0.01] - oprob_digraph_1 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_1)) - oprob_digraph_2 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_2)) - oprob_digraph_3 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_3)) - oprob_graph_11 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_1)) - oprob_graph_12 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_1)) - oprob_graph_21 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_2)) - oprob_graph_22 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_2)) - oprob_graph_31 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_3)) - oprob_graph_32 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_3)) - sim_end_digraph_1 = solve(oprob_digraph_1, Tsit5()).u[end] - sim_end_digraph_2 = solve(oprob_digraph_2, Tsit5()).u[end] - sim_end_digraph_3 = solve(oprob_digraph_3, Tsit5()).u[end] - sim_end_graph_11 = solve(oprob_graph_11, Tsit5()).u[end] - sim_end_graph_12 = solve(oprob_graph_12, Tsit5()).u[end] - sim_end_graph_21 = solve(oprob_graph_21, Tsit5()).u[end] - sim_end_graph_22 = solve(oprob_graph_22, Tsit5()).u[end] - sim_end_graph_31 = solve(oprob_graph_31, Tsit5()).u[end] - sim_end_graph_32 = solve(oprob_graph_32, Tsit5()).u[end] - - @test all(sim_end_digraph_1 .== sim_end_digraph_2 .== sim_end_digraph_3 .== - sim_end_graph_11 .== sim_end_graph_12 .== sim_end_graph_21 .== - sim_end_graph_22 .== sim_end_graph_31 .== sim_end_graph_32) + pE = [:dS => 0.10, :dI => 0.01, :dR => 0.01] + oprob_digraph = ODEProblem(lrs_digraph, u0, (0.0, 500.0), [pV; pE]) + oprob_graph = ODEProblem(lrs_graph, u0, (0.0, 500.0), [pV; pE]) + + @test solve(oprob_digraph, Tsit5()) == solve(oprob_graph, Tsit5()) end # Creates networks where some species or parameters have no effect on the system. @@ -511,80 +457,43 @@ let TransportReaction(dZ, Z), TransportReaction(dV, V), ] - lrs_alt = LatticeReactionSystem(binding_system_alt, binding_srs_alt, small_2d_grid) + lrs_alt = LatticeReactionSystem(binding_system_alt, binding_srs_alt, small_2d_graph_grid) u0_alt = [ :X => 1.0, - :Y => 2.0 * rand_v_vals(lrs_alt.lattice), + :Y => 2.0 * rand_v_vals(lrs_alt), :XY => 0.5, - :Z => 2.0 * rand_v_vals(lrs_alt.lattice), + :Z => 2.0 * rand_v_vals(lrs_alt), :V => 0.5, :W => 1.0, ] p_alt = [ :k1 => 2.0, - :k2 => 0.1 .+ rand_v_vals(lrs_alt.lattice), - :dX => 1.0 .+ rand_e_vals(lrs_alt.lattice), + :k2 => 0.1 .+ rand_v_vals(lrs_alt), + :dX => rand_e_vals(lrs_alt), :dXY => 3.0, - :dZ => rand_e_vals(lrs_alt.lattice), + :dZ => rand_e_vals(lrs_alt), :dV => 0.2, :p1 => 1.0, - :p2 => rand_v_vals(lrs_alt.lattice), + :p2 => rand_v_vals(lrs_alt), ] oprob_alt = ODEProblem(lrs_alt, u0_alt, (0.0, 10.0), p_alt) - ss_alt = solve(oprob_alt, Tsit5()).u[end] - + ss_alt = solve(oprob_alt, Tsit5(); abstol=1e-9, reltol=1e-9).u[end] + binding_srs_main = [TransportReaction(dX, X), TransportReaction(dXY, XY)] - lrs = LatticeReactionSystem(binding_system, binding_srs_main, small_2d_grid) + lrs = LatticeReactionSystem(binding_system, binding_srs_main, small_2d_graph_grid) u0 = u0_alt[1:3] p = p_alt[1:4] oprob = ODEProblem(lrs, u0, (0.0, 10.0), p) - ss = solve(oprob, Tsit5()).u[end] - + ss = solve(oprob, Tsit5(); abstol=1e-9, reltol=1e-9).u[end] + + i = 3 + ss_alt[((i - 1) * 6 + 1):((i - 1) * 6 + 3)] ≈ ss[((i - 1) * 3 + 1):((i - 1) * 3 + 3)] + for i in 1:25 - @test isapprox(ss_alt[((i - 1) * 6 + 1):((i - 1) * 6 + 3)], - ss[((i - 1) * 3 + 1):((i - 1) * 3 + 3)]) < 1e-3 - end -end - -# Provides initial conditions and parameters in various different ways. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_grid) - u0_1 = [:S => 990.0, :I => [1.0, 3.0, 2.0, 5.0], :R => 0.0] - u0_2 = [990.0, [1.0, 3.0, 2.0, 5.0], 0.0] - u0_3 = [990.0 990.0 990.0 990.0; 1.0 3.0 2.0 5.0; 0.0 0.0 0.0 0.0] - pV_1 = [:α => 0.1 / 1000, :β => [0.01, 0.02, 0.01, 0.03]] - pV_2 = [0.1 / 1000, [0.01, 0.02, 0.01, 0.03]] - pV_3 = [0.1/1000 0.1/1000 0.1/1000 0.1/1000; 0.01 0.02 0.01 0.03] - pE_1 = [:dS => [0.01, 0.02, 0.03, 0.04], :dI => 0.01, :dR => 0.01] - pE_2 = [[0.01, 0.02, 0.03, 0.04], :0.01, 0.01] - pE_3 = [0.01 0.02 0.03 0.04; 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01] - - p1 = [ - :α => 0.1 / 1000, - :β => [0.01, 0.02, 0.01, 0.03], - :dS => [0.01, 0.02, 0.03, 0.04], - :dI => 0.01, - :dR => 0.01, - ] - ss_1_1 = solve(ODEProblem(lrs, u0_1, (0.0, 1.0), p1), Tsit5()).u[end] - for u0 in [u0_1, u0_2, u0_3], pV in [pV_1, pV_2, pV_3], pE in [pE_1, pE_2, pE_3] - ss = solve(ODEProblem(lrs, u0, (0.0, 1.0), (pV, pE)), Tsit5()).u[end] - @test all(isequal.(ss, ss_1_1)) + @test ss_alt[((i - 1) * 6 + 1):((i - 1) * 6 + 3)] ≈ ss[((i - 1) * 3 + 1):((i - 1) * 3 + 3)] end end -# Confirms parameters can be provided in [pV; pE] and (pV, pE) form. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - p1 = ([:α => 0.1 / 1000, :β => 0.01], [:dS => 0.01, :dI => 0.01, :dR => 0.01]) - p2 = [:α => 0.1 / 1000, :β => 0.01, :dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob1 = ODEProblem(lrs, u0, (0.0, 500.0), p1; jac = false) - oprob2 = ODEProblem(lrs, u0, (0.0, 500.0), p2; jac = false) - - @test all(isapprox.(solve(oprob1, Tsit5()).u[end], solve(oprob2, Tsit5()).u[end])) -end - ### Compare to Hand-written Functions ### # Compares the brusselator for a line of cells. @@ -673,8 +582,8 @@ let u0V = [:X => u0[1:2:(end - 1)], :Y => u0[2:2:end]] pV = [:A => p[1], :B => p[2]] pE = [:dX => p[3]] - ofun_aut_dense = ODEProblem(lrs, u0V, tspan, (pV, pE); jac = true, sparse = false).f - ofun_aut_sparse = ODEProblem(lrs, u0V, tspan, (pV, pE); jac = true, sparse = true).f + ofun_aut_dense = ODEProblem(lrs, u0V, tspan, [pV; pE]; jac = true, sparse = false).f + ofun_aut_sparse = ODEProblem(lrs, u0V, tspan, [pV; pE]; jac = true, sparse = true).f du_hw_dense = deepcopy(u0) du_hw_sparse = deepcopy(u0) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 17ff9b3940..c8c8ae3968 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -3,25 +3,31 @@ # Fetch packages. using Catalyst, Graphs, Test +# Fetch test networks. +include("../spatial_test_networks.jl") + # Pre declares a grid. -grid = Graphs.grid([2, 2]) +grids = [CartesianGrid((2,2)), fill(true, 2, 2), Graphs.grid([2, 2])] ### Tests LatticeReactionSystem Getters Correctness ### + # Test case 1. let rs = @reaction_network begin (p, 1), 0 <--> X end - tr = @transport_reaction d X - lrs = LatticeReactionSystem(rs, [tr], grid) - - @unpack X, p = rs - d = edge_parameters(lrs)[1] - @test issetequal(species(lrs), [X]) - @test issetequal(spatial_species(lrs), [X]) - @test issetequal(parameters(lrs), [p, d]) - @test issetequal(vertex_parameters(lrs), [p]) - @test issetequal(edge_parameters(lrs), [d]) + tr = @transport_reaction d X + for grid in grids + lrs = LatticeReactionSystem(rs, [tr], grid) + + @unpack X, p = rs + d = edge_parameters(lrs)[1] + @test issetequal(species(lrs), [X]) + @test issetequal(spatial_species(lrs), [X]) + @test issetequal(parameters(lrs), [p, d]) + @test issetequal(vertex_parameters(lrs), [p]) + @test issetequal(edge_parameters(lrs), [d]) + end end # Test case 2. @@ -44,14 +50,16 @@ let end tr_1 = @transport_reaction dX X tr_2 = @transport_reaction dY Y - lrs = LatticeReactionSystem(rs, [tr_1, tr_2], grid) - - @unpack X, Y, pX, pY, dX, dY = rs - @test issetequal(species(lrs), [X, Y]) - @test issetequal(spatial_species(lrs), [X, Y]) - @test issetequal(parameters(lrs), [pX, pY, dX, dY]) - @test issetequal(vertex_parameters(lrs), [pX, pY, dY]) - @test issetequal(edge_parameters(lrs), [dX]) + for grid in grids + lrs = LatticeReactionSystem(rs, [tr_1, tr_2], grid) + + @unpack X, Y, pX, pY, dX, dY = rs + @test issetequal(species(lrs), [X, Y]) + @test issetequal(spatial_species(lrs), [X, Y]) + @test issetequal(parameters(lrs), [pX, pY, dX, dY]) + @test issetequal(vertex_parameters(lrs), [pX, pY, dY]) + @test issetequal(edge_parameters(lrs), [dX]) + end end # Test case 4. @@ -62,14 +70,16 @@ let (pY, 1), 0 <--> Y end tr_1 = @transport_reaction dX X - lrs = LatticeReactionSystem(rs, [tr_1], grid) - - @unpack dX, p, X, Y, pX, pY = rs - @test issetequal(species(lrs), [X, Y]) - @test issetequal(spatial_species(lrs), [X]) - @test issetequal(parameters(lrs), [dX, p, pX, pY]) - @test issetequal(vertex_parameters(lrs), [dX, p, pX, pY]) - @test issetequal(edge_parameters(lrs), []) + for grid in grids + lrs = LatticeReactionSystem(rs, [tr_1], grid) + + @unpack dX, p, X, Y, pX, pY = rs + @test issetequal(species(lrs), [X, Y]) + @test issetequal(spatial_species(lrs), [X]) + @test issetequal(parameters(lrs), [dX, p, pX, pY]) + @test issetequal(vertex_parameters(lrs), [dX, p, pX, pY]) + @test issetequal(edge_parameters(lrs), []) + end end # Test case 5. @@ -90,16 +100,18 @@ let tr_2 = @transport_reaction dY Y tr_3 = @transport_reaction dZ Z tr_4 = TransportReaction(dV, V) - tr_5 = TransportReaction(dW, W) - lrs = LatticeReactionSystem(rs, [tr_1, tr_2, tr_3, tr_4, tr_5], grid) - - @unpack pX, pY, pZ, pV, dX, dY, X, Y, Z, V = rs - dZ, dV, dW = edge_parameters(lrs)[2:end] - @test issetequal(species(lrs), [W, X, Y, Z, V]) - @test issetequal(spatial_species(lrs), [X, Y, Z, V, W]) - @test issetequal(parameters(lrs), [pX, pY, dX, dY, pZ, pV, dZ, dV, dW]) - @test issetequal(vertex_parameters(lrs), [pX, pY, dY, pZ, pV]) - @test issetequal(edge_parameters(lrs), [dX, dZ, dV, dW]) + tr_5 = TransportReaction(dW, W) + for grid in grids + lrs = LatticeReactionSystem(rs, [tr_1, tr_2, tr_3, tr_4, tr_5], grid) + + @unpack pX, pY, pZ, pV, dX, dY, X, Y, Z, V = rs + dZ, dV, dW = edge_parameters(lrs)[2:end] + @test issetequal(species(lrs), [W, X, Y, Z, V]) + @test issetequal(spatial_species(lrs), [X, Y, Z, V, W]) + @test issetequal(parameters(lrs), [pX, pY, dX, dY, pZ, pV, dZ, dV, dW]) + @test issetequal(vertex_parameters(lrs), [pX, pY, dY, pZ, pV]) + @test issetequal(edge_parameters(lrs), [dX, dZ, dV, dW]) + end end # Test case 6. @@ -108,9 +120,11 @@ let (p, 1), 0 <--> X end tr = @transport_reaction d X - lrs = LatticeReactionSystem(rs, [tr], grid) + for grid in grids + lrs = LatticeReactionSystem(rs, [tr], grid) - @test nameof(lrs) == :customname + @test nameof(lrs) == :customname + end end ### Tests Spatial Reactions Getters Correctness ### @@ -234,7 +248,9 @@ let (p, d), 0 <--> X end tr = @transport_reaction D Y - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + for grid in grids + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + end end # Network where the rate depend on a species @@ -244,7 +260,9 @@ let (p, d), 0 <--> X end tr = @transport_reaction D*Y X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + for grid in grids + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + end end # Network with edge parameter in non-spatial reaction rate. @@ -254,7 +272,9 @@ let (p, d), 0 <--> X end tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + for grid in grids + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + end end # Network where metadata has been added in rs (which is not seen in transport reaction). @@ -264,14 +284,16 @@ let (p, d), 0 <--> X end tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) - - rs = @reaction_network begin - @parameters D [description="Parameter with added metadata"] - (p, d), 0 <--> X + for grid in grids + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + + rs = @reaction_network begin + @parameters D [description="Parameter with added metadata"] + (p, d), 0 <--> X + end + tr = @transport_reaction D X + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) end - tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) end @@ -296,7 +318,7 @@ let end # Graph grids (cannot test diagonal connections). - for lattice in [small_2d_grid, small_3d_grid, undirected_cycle, small_directed_cycle, unconnected_graph] + for lattice in [small_2d_graph_grid, small_3d_graph_grid, undirected_cycle, small_directed_cycle, unconnected_graph] lrs1 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) @test lrs1.num_edges == iterator_count(lrs1.edge_iterator) end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl index 7771898c2f..8b50805ae0 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl @@ -3,32 +3,35 @@ # Fetch packages. using Catalyst, Graphs, OrdinaryDiffEq, Test +# Fetch test networks. +include("../spatial_test_networks.jl") + ### Run Tests ### # Test errors when attempting to create networks with dimension > 3. let - @test_throws Exception LatticeReactionSystem(brusselator_system, srs, CartesianGrid((5, 5, 5, 5))) - @test_throws Exception LatticeReactionSystem(brusselator_system, srs, fill(true, 5, 5, 5, 5)) + @test_throws Exception LatticeReactionSystem(brusselator_system, brusselator_srs_1, CartesianGrid((5, 5, 5, 5))) + @test_throws Exception LatticeReactionSystem(brusselator_system, brusselator_srs_1, fill(true, 5, 5, 5, 5)) end # Checks that getter functions give the correct output. let # Create LatticeReactionsSystems. - cartesian_1d_lrs = LatticeReactionSystem(brusselator_system, srs, small_1d_cartesian_grid) - cartesian_2d_lrs = LatticeReactionSystem(brusselator_system, srs, small_2d_cartesian_grid) - cartesian_3d_lrs = LatticeReactionSystem(brusselator_system, srs, small_3d_cartesian_grid) - masked_1d_lrs = LatticeReactionSystem(brusselator_system, srs, small_1d_masked_grid) - masked_2d_lrs = LatticeReactionSystem(brusselator_system, srs, small_2d_masked_grid) - masked_3d_lrs = LatticeReactionSystem(brusselator_system, srs, small_3d_masked_grid) - graph_lrs = LatticeReactionSystem(brusselator_system, srs, small_2d_grid) + cartesian_1d_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_1d_cartesian_grid) + cartesian_2d_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_cartesian_grid) + cartesian_3d_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_3d_cartesian_grid) + masked_1d_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_1d_masked_grid) + masked_2d_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_masked_grid) + masked_3d_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_3d_masked_grid) + graph_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_graph_grid) # Test lattice type getters. @test has_cartesian_lattice(cartesian_2d_lrs) - @test has_cartesian_lattice(masked_2d_lrs) - @test has_cartesian_lattice(graph_lrs) + @test !has_cartesian_lattice(masked_2d_lrs) + @test !has_cartesian_lattice(graph_lrs) @test !has_masked_lattice(cartesian_2d_lrs) - @test !has_masked_lattice(masked_2d_lrs) + @test has_masked_lattice(masked_2d_lrs) @test !has_masked_lattice(graph_lrs) @test has_grid_lattice(cartesian_2d_lrs) @@ -49,17 +52,36 @@ let @test_throws Exception grid_dims(graph_lrs) end +# Checks grid dimensions for 2d and 3d grids where some dimension is equal to 1. +let + # Creates LatticeReactionSystems + cartesian_2d_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, CartesianGrid((1,5))) + cartesian_3d_lrs_1 = LatticeReactionSystem(brusselator_system, brusselator_srs_1, CartesianGrid((1,5,5))) + cartesian_3d_lrs_2 = LatticeReactionSystem(brusselator_system, brusselator_srs_1, CartesianGrid((1,1,5))) + masked_2d_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, fill(true, 1, 5)) + masked_3d_lrs_1 = LatticeReactionSystem(brusselator_system, brusselator_srs_1, fill(true, 1, 5,5)) + masked_3d_lrs_2 = LatticeReactionSystem(brusselator_system, brusselator_srs_1, fill(true, 1, 1,5)) + + # Check grid dimensions. + @test grid_dims(cartesian_2d_lrs) == 2 + @test grid_dims(cartesian_3d_lrs_1) == 3 + @test grid_dims(cartesian_3d_lrs_2) == 3 + @test grid_dims(masked_2d_lrs) == 2 + @test grid_dims(masked_3d_lrs_1) == 3 + @test grid_dims(masked_3d_lrs_2) == 3 +end + # Checks that some grids, created using different approaches, generates the same spatial structures. # Checks that some grids, created using different approaches, generates the same simulation output. let # Create LatticeReactionsSystems. - cartesian_grid = Graphs.grid([5, 5]) + cartesian_grid = CartesianGrid((5, 5)) masked_grid = fill(true, 5, 5) graph_grid = Graphs.grid([5, 5]) - cartesian_lrs = LatticeReactionSystem(brusselator_system, srs, cartesian_grid) - masked_lrs = LatticeReactionSystem(brusselator_system, srs, masked_grid) - graph_lrs = LatticeReactionSystem(brusselator_system, srs, graph_grid) + cartesian_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, cartesian_grid) + masked_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, masked_grid) + graph_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, graph_grid) # Check internal structures. @test cartesian_lrs.rs == masked_lrs.rs == graph_lrs.rs @@ -67,53 +89,74 @@ let @test cartesian_lrs.num_verts == masked_lrs.num_verts == graph_lrs.num_verts @test cartesian_lrs.num_edges == masked_lrs.num_edges == graph_lrs.num_edges @test cartesian_lrs.num_species == masked_lrs.num_species == graph_lrs.num_species - @test cartesian_lrs.spat_species == masked_lrs.spat_species == graph_lrs.spat_species - @test cartesian_lrs.parameters == masked_lrs.parameters == graph_lrs.parameters - @test cartesian_lrs.vertex_parameters == masked_lrs.vertex_parameters == graph_lrs.vertex_parameters - @test cartesian_lrs.edge_parameters == masked_lrs.edge_parameters == graph_lrs.edge_parameters - @test cartesian_lrs.directed_edges == masked_lrs.directed_edges == graph_lrs.directed_edges - @test cartesian_lrs.edge_list == masked_lrs.edge_list == graph_lrs.edge_list + @test isequal(cartesian_lrs.spat_species, masked_lrs.spat_species) + @test isequal(masked_lrs.spat_species, graph_lrs.spat_species) + @test isequal(cartesian_lrs.parameters, masked_lrs.parameters) + @test isequal(masked_lrs.parameters, graph_lrs.parameters) + @test isequal(cartesian_lrs.vertex_parameters, masked_lrs.vertex_parameters) + @test isequal(masked_lrs.edge_parameters, graph_lrs.edge_parameters) + @test issetequal(cartesian_lrs.edge_iterator, masked_lrs.edge_iterator) + @test issetequal(masked_lrs.edge_iterator, graph_lrs.edge_iterator) # Checks that simulations yields the same output. - u0 = [:X => rand_v_vals(graph_lrs.lattice, 10.0), :Y => 2.0] - pV = [:A => 0.5 .+ rand_v_vals(graph_lrs.lattice, 0.5), :B => 4.0] - pE= map(sp -> sp => 0.2, spatial_param_syms(lrs)) - - cartesian_oprob = ODEProblem(cartesian_lrs, u0, (0.0, 100.0), (pV, pE)) - masked_oprob = ODEProblem(masked_lrs, u0, (0.0, 100.0), (pV, pE)) - graph_oprob = ODEProblem(graph_lrs, u0, (0.0, 100.0), (pV, pE)) + X_vals = rand(cartesian_lrs.num_verts) + u0_cartesian = [:X => reshape(u0_vals, 5, 5), :Y => 2.0] + u0_masked = [:X => reshape(u0_vals, 5, 5), :Y => 2.0] + u0_graph = [:X => u0_vals, :Y => 2.0] + B_vals = rand(cartesian_lrs.num_verts) + pV_cartesian = [:A => 0.5 .+ reshape(B_vals, 5, 5), :B => 4.0] + pV_masked = [:A => 0.5 .+ reshape(B_vals, 5, 5), :B => 4.0] + pV_graph = [:A => 0.5 .+ B_vals, :B => 4.0] + pE = [:dX => 0.2] + + cartesian_oprob = ODEProblem(cartesian_lrs, u0_cartesian, (0.0, 100.0), [pV_cartesian; pE]) + masked_oprob = ODEProblem(masked_lrs, u0_masked, (0.0, 100.0), [pV_masked; pE]) + graph_oprob = ODEProblem(graph_lrs, u0_graph, (0.0, 100.0), [pV_graph; pE]) cartesian_sol = solve(cartesian_oprob, QNDF(); saveat=0.1) masked_sol = solve(masked_oprob, QNDF(); saveat=0.1) - graph_sol = solvegraph(_oprob, QNDF(); saveat=0.1) + graph_sol = solve(graph_oprob, QNDF(); saveat=0.1) - @test cartesian_sol.u ≈ masked_sol.u ≈ graph_sol + @test cartesian_sol.u == masked_sol.u == graph_sol.u end # Checks that a regular grid with absent vertices generate the same output as corresponding graph. let # Create LatticeReactionsSystems. masked_grid = [true true true; true false true; true true true] - graph_grid = cycle_graph(8) + graph_grid = SimpleGraph(8) + add_edge!(graph_grid, 1, 2); add_edge!(graph_grid, 2, 1); + add_edge!(graph_grid, 2, 3); add_edge!(graph_grid, 3, 2); + add_edge!(graph_grid, 3, 5); add_edge!(graph_grid, 5, 3); + add_edge!(graph_grid, 5, 8); add_edge!(graph_grid, 8, 5); + add_edge!(graph_grid, 8, 7); add_edge!(graph_grid, 7, 8); + add_edge!(graph_grid, 7, 6); add_edge!(graph_grid, 6, 7); + add_edge!(graph_grid, 6, 4); add_edge!(graph_grid, 4, 6); + add_edge!(graph_grid, 4, 1); add_edge!(graph_grid, 1, 4); + + masked_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, masked_grid) + graph_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, graph_grid) - masked_lrs = LatticeReactionSystem(brusselator_system, srs, masked_grid) - graph_lrs = LatticeReactionSystem(brusselator_system, srs, graph_grid) - # Check internal structures. @test masked_lrs.num_verts == graph_lrs.num_verts @test masked_lrs.num_edges == graph_lrs.num_edges - @test masked_lrs.edge_list == graph_lrs.edge_list - + @test issetequal(masked_lrs.edge_iterator, graph_lrs.edge_iterator) + # Checks that simulations yields the same output. - u0 = [:X => rand_v_vals(graph_lrs.lattice, 10.0), :Y => 2.0] - pV = [:A => 0.5 .+ rand_v_vals(graph_lrs.lattice, 0.5), :B => 4.0] - pE= map(sp -> sp => 0.2, spatial_param_syms(lrs)) - - masked_oprob = ODEProblem(masked_lrs, u0, (0.0, 100.0), (pV, pE)) - graph_oprob = ODEProblem(graph_lrs, u0, (0.0, 100.0), (pV, pE)) - - masked_sol = solve(masked_oprob, QNDF(); saveat=0.1) - graph_sol = solvegraph(_oprob, QNDF(); saveat=0.1) - - @test masked_sol.u ≈ graph_sol + u0_masked_grid = [:X => [1. 4. 6.; 2. 0. 7.; 3. 5. 8.], :Y => 2.0] + u0_graph_grid = [:X => [1., 2., 3., 4., 5., 6., 7., 8.], :Y => 2.0] + pV_masked_grid = [:A => 0.5 .+ [1. 4. 6.; 2. 0. 7.; 3. 5. 8.], :B => 4.0] + pV_graph_grid = [:A => 0.5 .+ [1., 2., 3., 4., 5., 6., 7., 8.], :B => 4.0] + pE = [:dX => 0.2] + + base_oprob = ODEProblem(masked_lrs, u0_masked_grid, (0.0, 100.0), [pV_masked_grid; pE]) + base_osol = solve(base_oprob, QNDF(); saveat=0.1, abstol=1e-9, reltol=1e-9) + + for jac in [false, true], sparse in [false, true] + masked_oprob = ODEProblem(masked_lrs, u0_masked_grid, (0.0, 100.0), [pV_masked_grid; pE]; jac, sparse) + graph_oprob = ODEProblem(graph_lrs, u0_graph_grid, (0.0, 100.0), [pV_graph_grid; pE]; jac, sparse) + masked_sol = solve(masked_oprob, QNDF(); saveat=0.1, abstol=1e-9, reltol=1e-9) + graph_sol = solve(graph_oprob, QNDF(); saveat=0.1, abstol=1e-9, reltol=1e-9) + @test base_osol ≈ masked_sol ≈ graph_sol + end end \ No newline at end of file diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index f4fcb17f50..4e68d5785f 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -8,10 +8,26 @@ rng = StableRNG(12345) ### Helper Functions ### # Generates randomised initial condition or parameter values. -rand_v_vals(grid) = rand(rng, nv(grid)) rand_v_vals(grid, x::Number) = rand_v_vals(grid) * x -rand_e_vals(grid) = rand(rng, ne(grid)) +rand_v_vals(lrs::LatticeReactionSystem) = rand_v_vals(lrs.lattice) +function rand_v_vals(grid::DiGraph) + return rand(rng, nv(grid)) +end +function rand_v_vals(grid::Catalyst.CartesianGridRej{S,T}) where {S,T} + return rand(rng, grid.dims) +end +function rand_v_vals(grid::Array{Bool, T}) where {T} + return rand(rng, size(grid)) +end + rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x +function rand_e_vals(lrs::LatticeReactionSystem) + e_vals = spzeros(lrs.num_verts, lrs.num_verts) + for e in lrs.edge_iterator + e_vals[e[1], e[2]] = rand() + end + return e_vals +end # Gets a symbol list of spatial parameters. function spatial_param_syms(lrs::LatticeReactionSystem) From 64fa97baa224020ea8cffcd0592bde171dcc0809 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 6 Feb 2024 16:18:54 -0500 Subject: [PATCH 086/446] up --- src/spatial_reaction_systems/utility.jl | 2 +- .../lattice_reaction_systems_ODEs.jl | 41 +++++++++++++++++++ test/spatial_test_networks.jl | 4 ++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 3b18fd31cf..4c4d95043c 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -44,7 +44,7 @@ function lattice_process_p(ps_in, ps_vertex_syms, ps_edge_syms, lrs::LatticeReac vert_ps, edge_ps = split_parameters(ps, ps_vertex_syms, ps_edge_syms) vert_ps = vertex_value_map(vert_ps, lrs) edge_ps = edge_value_map(edge_ps, lrs) - + return vert_ps, edge_ps end diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index 52c13dbc68..090481d3e8 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -494,6 +494,47 @@ let end end +# Tests various types of numbers for initial conditions/parameters (e.g. Real numbers, Float32, etc.). +let + # Declare u0 versions. + u0_Int64 = [:X => 2, :Y => [1, 1, 1, 2]] + u0_Float64 = [:X => 2.0, :Y => [1.0, 1.0, 1.0, 2.0]] + u0_Int32 = [:X => Int32(2), :Y => Int32.([1, 1, 1, 2])] + u0_Any = Pair{Symbol,Any}[:X => 2.0, :Y => [1.0, 1.0, 1.0, 2.0]] + u0s = (u0_Int64, u0_Float64, u0_Int32, u0_Any) + + # Declare parameter versions. + dY_vals = spzeros(4,4) + dY_vals[1,2] = 1; dY_vals[2,1] = 1; + dY_vals[1,3] = 1; dY_vals[3,1] = 1; + dY_vals[2,4] = 1; dY_vals[4,2] = 1; + dY_vals[3,4] = 2; dY_vals[4,3] = 2; + p_Int64 = (:A => [1, 1, 1, 2], :B => 4, :dX => 1, :dY => Int64.(dY_vals)) + p_Float64 = (:A => [1.0, 1.0, 1.0, 2.0], :B => 4.0, :dX => 1.0, :dY => Float64.(dY_vals)) + p_Int32 = (:A => Int32.([1, 1, 1, 2]), :B => Int32(4), :dX => Int32(1), :dY => Int32.(dY_vals)) + p_Any = Pair{Symbol,Any}[:A => [1.0, 1.0, 1.0, 2.0], :B => 4.0, :dX => 1.0, :dY => dY_vals] + ps = (p_Int64, p_Float64, p_Int32, p_Any) + + # Creates a base solution to compare all solution to. + lrs_base = LatticeReactionSystem(brusselator_system, brusselator_srs_2, very_small_2d_graph_grid) + oprob_base = ODEProblem(lrs_base, u0s[1], (0.0, 20.0), ps[1]) + sol_base = solve(oprob_base, QNDF(); abstol=1e-8, reltol=1e-8, saveat=0.1) + + # Checks all combinations of input types. + for grid in [very_small_2d_cartesian_grid, very_small_2d_masked_grid, very_small_2d_graph_grid] + lrs_base = LatticeReactionSystem(brusselator_system, brusselator_srs_2, grid) + for u0_base in u0s, p_base in ps + for u0 in [u0_base, Tuple(u0_base), Dict(u0_base)], p in [p_base, Tuple(p_base), Dict(p_base)] + for sparse in [false, true], jac in [false, true] + oprob = ODEProblem(lrs, u0, (0.0, 20.0), p; sparse, jac) + sol = solve(oprob, QNDF(); abstol=1e-8, reltol=1e-8, saveat=0.1) + @test sol == sol_base + end + end + end + end +end + ### Compare to Hand-written Functions ### # Compares the brusselator for a line of cells. diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index 4e68d5785f..ef1836958e 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -180,6 +180,8 @@ sigmaB_srs_2 = [sigmaB_tr_σB, sigmaB_tr_w, sigmaB_tr_v] ### Declares Lattices ### # Cartesian grids. +very_small_2d_cartesian_grid = CartesianGrid((2,2)) + small_1d_cartesian_grid = CartesianGrid(5) small_2d_cartesian_grid = CartesianGrid((5,5)) small_3d_cartesian_grid = CartesianGrid((5,5,5)) @@ -189,6 +191,8 @@ large_2d_cartesian_grid = CartesianGrid((100,100)) large_3d_cartesian_grid = CartesianGrid((100,100,100)) # Masked grids. +very_small_2d_masked_grid = fill(true, 2, 2) + small_1d_masked_grid = fill(true, 5) small_2d_masked_grid = fill(true, 5, 5) small_3d_masked_grid = fill(true, 5, 5, 5) From ad5c71ef9320afbb87950e4c1302e7951ec6a66f Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 6 Feb 2024 16:40:34 -0500 Subject: [PATCH 087/446] up --- src/Catalyst.jl | 2 +- .../lattice_reaction_systems.jl | 39 ++- .../lattice_reaction_systems_ODEs.jl | 242 +++++++++--------- .../lattice_reaction_systems.jl | 2 +- .../lattice_reaction_systems_lattice_types.jl | 6 +- 5 files changed, 160 insertions(+), 131 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index a5f51efb89..9f6da7d1d0 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -177,7 +177,7 @@ include("spatial_reaction_systems/lattice_reaction_systems.jl") export LatticeReactionSystem export spatial_species, vertex_parameters, edge_parameters export CartesianGrid, CartesianGridReJ # (Implemented in JumpProcesses) -export has_cartesian_lattice, has_masked_lattice, has_grid_lattice, has_graph_lattice, grid_dims +export has_cartesian_lattice, has_masked_lattice, has_grid_lattice, has_graph_lattice, grid_dims, grid_size # Various utility functions include("spatial_reaction_systems/utility.jl") diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index be75d75d73..47a3d75e70 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -208,21 +208,48 @@ ModelingToolkit.nameof(lrs::LatticeReactionSystem) = nameof(lrs.rs) # Checks if a lattice reaction system is a pure (linear) transport reaction system. is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReaction, lrs.spatial_reactions) -# Checks if a LatticeReactionSystem have a Cartesian grid lattice. +""" + has_cartesian_lattice(lrs::LatticeReactionSystem) + +Returns `true` if `lrs` was created using a cartesian grid lattice (e.g. created via `CartesianGrid(5,5)`). Else, returns `false`. +""" has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa CartesianGridRej{S,T} where {S,T} -# Checks if a LatticeReactionSystem have a masked grid lattice. + +""" + has_masked_lattice(lrs::LatticeReactionSystem) + +Returns `true` if `lrs` was created using a masked grid lattice (e.g. created via `[true true; true false]`). Else, returns `false`. +""" has_masked_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa Array{Bool, T} where T -# Checks if a LatticeReactionSystem have a grid lattice (cartesian or masked). + +""" + has_grid_lattice(lrs::LatticeReactionSystem) + +Returns `true` if `lrs` was created using a cartesian or masked grid lattice. Else, returns `false`. +""" has_grid_lattice(lrs::LatticeReactionSystem) = (has_cartesian_lattice(lrs) || has_masked_lattice(lrs)) -# Checks if a LatticeReactionSystem have a graph lattice. + +""" + has_graph_lattice(lrs::LatticeReactionSystem) + +Returns `true` if `lrs` was created using a graph grid lattice (e.g. created via `path_graph(5)`). Else, returns `false`. +""" has_graph_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa SimpleDiGraph -# Returns the size of the lattice of a LatticeReactionNetwork with a grid lattice. +""" + grid_size(lrs::LatticeReactionSystem) + +Returns the size of `lrs`'s lattice (only if it is a cartesian or masked grid lattice). E.g. for a lattice `CartesianGrid(4,6)`, `(4,6)` is returned. +""" function grid_size(lrs::LatticeReactionSystem) has_cartesian_lattice(lrs) && (return lrs.lattice.dims) has_masked_lattice(lrs) && (return size(lrs.lattice)) error("Grid size is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") end -# Returns the dimensions of a LatticeReactionNetwork with a grid lattice. +""" + grid_dims(lrs::LatticeReactionSystem) + +Returns the number of dimensions of `lrs`'s lattice (only if it is a cartesian or masked grid lattice). The output is either `1`, `2`, or `3`. +""" grid_dims(lrs::LatticeReactionSystem) = length(grid_size(lrs)) \ No newline at end of file diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index 090481d3e8..42cef58c28 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -229,10 +229,130 @@ let rtol = 0.0001)) end +# Compares Catalyst-generated to hand written one for the brusselator for a line of cells. +let + function spatial_brusselator_f(du, u, p, t) + # Non-spatial + for i in 1:2:(length(u) - 1) + du[i] = p[1] + 0.5 * (u[i]^2) * u[i + 1] - u[i] - p[2] * u[i] + du[i + 1] = p[2] * u[i] - 0.5 * (u[i]^2) * u[i + 1] + end + + # Spatial + du[1] += p[3] * (u[3] - u[1]) + du[end - 1] += p[3] * (u[end - 3] - u[end - 1]) + for i in 3:2:(length(u) - 3) + du[i] += p[3] * (u[i - 2] + u[i + 2] - 2u[i]) + end + end + function spatial_brusselator_jac(J, u, p, t) + J .= 0 + # Non-spatial + for i in 1:2:(length(u) - 1) + J[i, i] = u[i] * u[i + 1] - 1 - p[2] + J[i, i + 1] = 0.5 * (u[i]^2) + J[i + 1, i] = p[2] - u[i] * u[i + 1] + J[i + 1, i + 1] = -0.5 * (u[i]^2) + end + + # Spatial + J[1, 1] -= p[3] + J[1, 3] += p[3] + J[end - 1, end - 1] -= p[3] + J[end - 1, end - 3] += p[3] + for i in 3:2:(length(u) - 3) + J[i, i] -= 2 * p[3] + J[i, i - 2] += p[3] + J[i, i + 2] += p[3] + end + end + function spatial_brusselator_jac_sparse(J, u, p, t) + # Spatial + J.nzval .= 0.0 + J.nzval[7:6:(end - 9)] .= -2p[3] + J.nzval[1] = -p[3] + J.nzval[end - 3] = -p[3] + J.nzval[3:3:(end - 4)] .= p[3] + + # Non-spatial + for i in 1:1:Int64(lenth(u) / 2 - 1) + j = 6(i - 1) + 1 + J.nzval[j] = u[i] * u[i + 1] - 1 - p[2] + J.nzval[j + 1] = 0.5 * (u[i]^2) + J.nzval[j + 3] = p[2] - u[i] * u[i + 1] + J.nzval[j + 4] = -0.5 * (u[i]^2) + end + J.nzval[end - 3] = u[end - 1] * u[end] - 1 - p[end - 1] + J.nzval[end - 2] = 0.5 * (u[end - 1]^2) + J.nzval[end - 1] = p[2] - u[end - 1] * u[end] + J.nzval[end] = -0.5 * (u[end - 1]^2) + end + function make_jac_prototype(u0) + jac_prototype_pre = zeros(length(u0), length(u0)) + for i in 1:2:(length(u0) - 1) + jac_prototype_pre[i, i] = 1 + jac_prototype_pre[i + 1, i] = 1 + jac_prototype_pre[i, i + 1] = 1 + jac_prototype_pre[i + 1, i + 1] = 1 + end + for i in 3:2:(length(u0) - 1) + jac_prototype_pre[i - 2, i] = 1 + jac_prototype_pre[i, i - 2] = 1 + end + return sparse(jac_prototype_pre) + end + + u0 = 2 * rand(rng, 10000) + p = [1.0, 4.0, 0.1] + tspan = (0.0, 100.0) + + ofun_hw_dense = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac) + ofun_hw_sparse = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac, + jac_prototype = make_jac_prototype(u0)) + + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, + path_graph(Int64(length(u0) / 2))) + u0V = [:X => u0[1:2:(end - 1)], :Y => u0[2:2:end]] + pV = [:A => p[1], :B => p[2]] + pE = [:dX => p[3]] + ofun_aut_dense = ODEProblem(lrs, u0V, tspan, [pV; pE]; jac = true, sparse = false).f + ofun_aut_sparse = ODEProblem(lrs, u0V, tspan, [pV; pE]; jac = true, sparse = true).f + + du_hw_dense = deepcopy(u0) + du_hw_sparse = deepcopy(u0) + du_aut_dense = deepcopy(u0) + du_aut_sparse = deepcopy(u0) + + ofun_hw_dense(du_hw_dense, u0, p, 0.0) + ofun_hw_sparse(du_hw_sparse, u0, p, 0.0) + ofun_aut_dense(du_aut_dense, u0, p, 0.0) + ofun_aut_sparse(du_aut_sparse, u0, p, 0.0) + + @test isapprox(du_hw_dense, du_aut_dense) + @test isapprox(du_hw_sparse, du_aut_sparse) + + J_hw_dense = deepcopy(zeros(length(u0), length(u0))) + J_hw_sparse = deepcopy(make_jac_prototype(u0)) + J_aut_dense = deepcopy(zeros(length(u0), length(u0))) + J_aut_sparse = deepcopy(make_jac_prototype(u0)) + + ofun_hw_dense.jac(J_hw_dense, u0, p, 0.0) + ofun_hw_sparse.jac(J_hw_sparse, u0, p, 0.0) + ofun_aut_dense.jac(J_aut_dense, u0, p, 0.0) + ofun_aut_sparse.jac(J_aut_sparse, u0, p, 0.0) + + @test isapprox(J_hw_dense, J_aut_dense) + @test isapprox(J_hw_sparse, J_aut_sparse) +end + + ### Test Grid Types ### # Tests that identical lattices (using different types of lattices) give identical results. let + # Declares the diffusion parameters. + sigmaB_p_spat = [:DσB => 0.05, :Dw => 0.04, :Dv => 0.03] + # 1d lattices. lrs1_cartesian = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_1d_cartesian_grid) lrs1_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_1d_masked_grid) @@ -522,7 +642,7 @@ let # Checks all combinations of input types. for grid in [very_small_2d_cartesian_grid, very_small_2d_masked_grid, very_small_2d_graph_grid] - lrs_base = LatticeReactionSystem(brusselator_system, brusselator_srs_2, grid) + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_2, grid) for u0_base in u0s, p_base in ps for u0 in [u0_base, Tuple(u0_base), Dict(u0_base)], p in [p_base, Tuple(p_base), Dict(p_base)] for sparse in [false, true], jac in [false, true] @@ -533,122 +653,4 @@ let end end end -end - -### Compare to Hand-written Functions ### - -# Compares the brusselator for a line of cells. -let - function spatial_brusselator_f(du, u, p, t) - # Non-spatial - for i in 1:2:(length(u) - 1) - du[i] = p[1] + 0.5 * (u[i]^2) * u[i + 1] - u[i] - p[2] * u[i] - du[i + 1] = p[2] * u[i] - 0.5 * (u[i]^2) * u[i + 1] - end - - # Spatial - du[1] += p[3] * (u[3] - u[1]) - du[end - 1] += p[3] * (u[end - 3] - u[end - 1]) - for i in 3:2:(length(u) - 3) - du[i] += p[3] * (u[i - 2] + u[i + 2] - 2u[i]) - end - end - function spatial_brusselator_jac(J, u, p, t) - J .= 0 - # Non-spatial - for i in 1:2:(length(u) - 1) - J[i, i] = u[i] * u[i + 1] - 1 - p[2] - J[i, i + 1] = 0.5 * (u[i]^2) - J[i + 1, i] = p[2] - u[i] * u[i + 1] - J[i + 1, i + 1] = -0.5 * (u[i]^2) - end - - # Spatial - J[1, 1] -= p[3] - J[1, 3] += p[3] - J[end - 1, end - 1] -= p[3] - J[end - 1, end - 3] += p[3] - for i in 3:2:(length(u) - 3) - J[i, i] -= 2 * p[3] - J[i, i - 2] += p[3] - J[i, i + 2] += p[3] - end - end - function spatial_brusselator_jac_sparse(J, u, p, t) - # Spatial - J.nzval .= 0.0 - J.nzval[7:6:(end - 9)] .= -2p[3] - J.nzval[1] = -p[3] - J.nzval[end - 3] = -p[3] - J.nzval[3:3:(end - 4)] .= p[3] - - # Non-spatial - for i in 1:1:Int64(lenth(u) / 2 - 1) - j = 6(i - 1) + 1 - J.nzval[j] = u[i] * u[i + 1] - 1 - p[2] - J.nzval[j + 1] = 0.5 * (u[i]^2) - J.nzval[j + 3] = p[2] - u[i] * u[i + 1] - J.nzval[j + 4] = -0.5 * (u[i]^2) - end - J.nzval[end - 3] = u[end - 1] * u[end] - 1 - p[end - 1] - J.nzval[end - 2] = 0.5 * (u[end - 1]^2) - J.nzval[end - 1] = p[2] - u[end - 1] * u[end] - J.nzval[end] = -0.5 * (u[end - 1]^2) - end - function make_jac_prototype(u0) - jac_prototype_pre = zeros(length(u0), length(u0)) - for i in 1:2:(length(u0) - 1) - jac_prototype_pre[i, i] = 1 - jac_prototype_pre[i + 1, i] = 1 - jac_prototype_pre[i, i + 1] = 1 - jac_prototype_pre[i + 1, i + 1] = 1 - end - for i in 3:2:(length(u0) - 1) - jac_prototype_pre[i - 2, i] = 1 - jac_prototype_pre[i, i - 2] = 1 - end - return sparse(jac_prototype_pre) - end - - u0 = 2 * rand(rng, 10000) - p = [1.0, 4.0, 0.1] - tspan = (0.0, 100.0) - - ofun_hw_dense = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac) - ofun_hw_sparse = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac, - jac_prototype = make_jac_prototype(u0)) - - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, - path_graph(Int64(length(u0) / 2))) - u0V = [:X => u0[1:2:(end - 1)], :Y => u0[2:2:end]] - pV = [:A => p[1], :B => p[2]] - pE = [:dX => p[3]] - ofun_aut_dense = ODEProblem(lrs, u0V, tspan, [pV; pE]; jac = true, sparse = false).f - ofun_aut_sparse = ODEProblem(lrs, u0V, tspan, [pV; pE]; jac = true, sparse = true).f - - du_hw_dense = deepcopy(u0) - du_hw_sparse = deepcopy(u0) - du_aut_dense = deepcopy(u0) - du_aut_sparse = deepcopy(u0) - - ofun_hw_dense(du_hw_dense, u0, p, 0.0) - ofun_hw_sparse(du_hw_sparse, u0, p, 0.0) - ofun_aut_dense(du_aut_dense, u0, p, 0.0) - ofun_aut_sparse(du_aut_sparse, u0, p, 0.0) - - @test isapprox(du_hw_dense, du_aut_dense) - @test isapprox(du_hw_sparse, du_aut_sparse) - - J_hw_dense = deepcopy(zeros(length(u0), length(u0))) - J_hw_sparse = deepcopy(make_jac_prototype(u0)) - J_aut_dense = deepcopy(zeros(length(u0), length(u0))) - J_aut_sparse = deepcopy(make_jac_prototype(u0)) - - ofun_hw_dense.jac(J_hw_dense, u0, p, 0.0) - ofun_hw_sparse.jac(J_hw_sparse, u0, p, 0.0) - ofun_aut_dense.jac(J_aut_dense, u0, p, 0.0) - ofun_aut_sparse.jac(J_aut_sparse, u0, p, 0.0) - - @test isapprox(J_hw_dense, J_aut_dense) - @test isapprox(J_hw_sparse, J_aut_sparse) -end +end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index c8c8ae3968..ed29142640 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -7,7 +7,7 @@ using Catalyst, Graphs, Test include("../spatial_test_networks.jl") # Pre declares a grid. -grids = [CartesianGrid((2,2)), fill(true, 2, 2), Graphs.grid([2, 2])] +grids = [very_small_2d_cartesian_grid, very_small_2d_masked_grid, very_small_2d_graph_grid] ### Tests LatticeReactionSystem Getters Correctness ### diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl index 8b50805ae0..c75d0d1c96 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl @@ -100,9 +100,9 @@ let # Checks that simulations yields the same output. X_vals = rand(cartesian_lrs.num_verts) - u0_cartesian = [:X => reshape(u0_vals, 5, 5), :Y => 2.0] - u0_masked = [:X => reshape(u0_vals, 5, 5), :Y => 2.0] - u0_graph = [:X => u0_vals, :Y => 2.0] + u0_cartesian = [:X => reshape(X_vals, 5, 5), :Y => 2.0] + u0_masked = [:X => reshape(X_vals, 5, 5), :Y => 2.0] + u0_graph = [:X => X_vals, :Y => 2.0] B_vals = rand(cartesian_lrs.num_verts) pV_cartesian = [:A => 0.5 .+ reshape(B_vals, 5, 5), :B => 4.0] pV_masked = [:A => 0.5 .+ reshape(B_vals, 5, 5), :B => 4.0] From a265597c16bdef83a992b85f872e5b7c3f2c9475 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 6 Feb 2024 19:49:28 -0500 Subject: [PATCH 088/446] "finish" remake. --- .../lattice_reaction_systems.jl | 91 ++++---- .../spatial_ODE_systems.jl | 168 ++++++++------- .../spatial_reactions.jl | 21 +- src/spatial_reaction_systems/utility.jl | 202 ++++++++++-------- ...attice_reaction_systems_ODE_performance.jl | 16 +- .../lattice_reaction_systems_ODEs.jl | 11 +- 6 files changed, 271 insertions(+), 238 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 47a3d75e70..a525757753 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -4,57 +4,60 @@ # Adding the "<: MT.AbstractTimeDependentSystem" part messes up show, disabling me from creating LRSs. Should be fixed some time. struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem # Input values. - """The reaction system within each compartment.""" + """The (non-spatial) reaction system within each vertexes.""" rs::ReactionSystem{Q} - """The spatial reactions defined between individual nodes.""" + """The spatial reactions defined between individual vertexes.""" spatial_reactions::Vector{R} - """The graph on which the lattice is defined.""" + """The lattice on which the (discrete) spatial system is defined.""" lattice::S # Derived values. - """The number of compartments.""" + """The number of vertexes (compartments).""" num_verts::Int64 """The number of edges.""" num_edges::Int64 """The number of species.""" num_species::Int64 - """Species that may move spatially.""" + """List of species that may move spatially.""" spat_species::Vector{BasicSymbolic{Real}} """ All parameters related to the lattice reaction system - (both with spatial and non-spatial effects). + (both those whose values are tied to vertexes and edges). """ parameters::Vector{BasicSymbolic{Real}} """ - Parameters which values are tied to vertexes (adjacencies), - e.g. (possibly) have an unique value at each vertex of the system. + Parameters which values are tied to vertexes, + e.g. that possibly could have unique values at each vertex of the system. """ vertex_parameters::Vector{BasicSymbolic{Real}} """ - Parameters which values are tied to edges (adjacencies), - e.g. (possibly) have an unique value at each edge of the system. + Parameters whose values are tied to edges (adjacencies), + e.g. that possibly could have unique values at each edge of the system. """ edge_parameters::Vector{BasicSymbolic{Real}} """ - An iterator over all the edges on the lattice. - The format depends on the type of lattice (Cartesian grid, grid, or graph). + An iterator over all the lattice's edges. Currently, the format is always a Vector{Pair{Int64,Int64}}. + However, in the future, different types could potentially be used for different types of lattice + (E.g. for a Cartesian grid, we do not technically need to enumerate each edge) """ edge_iterator::T function LatticeReactionSystem(rs::ReactionSystem{Q}, spatial_reactions::Vector{R}, lattice::S, - num_verts, num_edges, edge_iterator::T) where {Q,R, S, T} + num_verts::Int64, num_edges::Int64, edge_iterator::T) where {Q,R,S,T} # Error checks. if !(R <: AbstractSpatialReaction) error("The second argument must be a vector of AbstractSpatialReaction subtypes.") end - # Computes derived values spatial species. Counts the total number of species. + # Computes the species which are parts of spatial reactions. Also counts total number of + # species types. if isempty(spatial_reactions) spat_species = Vector{BasicSymbolic{Real}}[] else spat_species = unique(reduce(vcat, [spatial_species(sr) for sr in spatial_reactions])) end + num_species = length(unique([species(rs); spat_species])) # Computes the sets of vertex, edge, and all, parameters. rs_edge_parameters = filter(isedgeparameter, parameters(rs)) @@ -66,10 +69,6 @@ struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem edge_parameters = unique([rs_edge_parameters; srs_edge_parameters]) vertex_parameters = filter(!isedgeparameter, parameters(rs)) - # Counts the number of species types. The number of vertexes and compartments is given in input. - # `num_edges` cannot be computed here, because how it is computed depends on the lattice and `typeof(edge_iterator)`. - num_species = length(unique([species(rs); spat_species])) - # Ensures the parameter order begins similarly to in the non-spatial ReactionSystem. ps = [parameters(rs); setdiff([edge_parameters; vertex_parameters], parameters(rs))] @@ -86,25 +85,26 @@ function LatticeReactionSystem(rs, srs, lattice_in::CartesianGridRej{S,T}; diago # Error checks. (length(lattice_in.dims) > 3) && error("Grids of higher dimension than 3 is currently not supported.") - # Ensures that the matrix has a 3d form (for intermediary computations, original is passed to constructor). + # Ensures that the matrix has a 3d form (used for intermediary computations only, + # the original is passed to the constructor). lattice = CartesianGrid((lattice_in.dims..., fill(1, 3-length(lattice_in.dims))...)) # Counts vertexes and edges. The `num_edges` count formula counts the number of internal, side, - # edge, and corner vertexes (on the grid). The number of edges from each depend on whether diagonal + # edge, and corner vertexes (on the grid). The number of edges from each depends on whether diagonal # connections are allowed. The formula holds even if l, m, and/or n are 1. l,m,n = lattice.dims num_verts = l * m * n (ni, ns, ne, nc) = diagonal_connections ? (26,17,11,7) : (6,5,4,3) - num_edges = ni*(l-2)*(m-2)*(n-2) + # Internal vertexes. - ns*(2(l-2)*(m-2) + 2(l-2)*(n-2) + 2(m-2)*(n-2)) + # Side vertexes. - ne*(4(l-2) + 4(m-2) + 4(n-2)) + # Edge vertexes. - nc*8 # Corner vertexes. + num_edges = ni*(l-2)*(m-2)*(n-2) + # Edges from internal vertexes. + ns*(2(l-2)*(m-2) + 2(l-2)*(n-2) + 2(m-2)*(n-2)) + # Edges from side vertexes. + ne*(4(l-2) + 4(m-2) + 4(n-2)) + # Edges from edge vertexes. + nc*8 # Edges from corner vertexes. # Creates an iterator over all edges. # Currently creates a full vector. Future version might be for efficient. edge_iterator = Vector{Pair{Int64,Int64}}(undef, num_edges) # Loops through, simultaneously, the coordinates of each position in the grid, as well as that - # coordinate's (scalar) flat index. For each grid point, loops through all potential neighbours. + # coordinate's (scalar) flat index. For each grid point, loops through all potential neighbours. indices = [(L, M, N) for L in 1:l, M in 1:m, N in 1:n] flat_indices = 1:num_verts next_vert = 0 @@ -117,7 +117,7 @@ function LatticeReactionSystem(rs, srs, lattice_in::CartesianGridRej{S,T}; diago !diagonal_connections && (count([L==LL, M==MM, N==NN]) == 2) || continue diagonal_connections && (L==LL) && (M==MM) && (N==NN) && continue - # Computes the neighbours scalar index. Add that connection to `edge_iterator`. + # Computes the neighbour's flat (scalar) index. Add the edge to edge_iterator. neighbour_idx = LL + (MM - 1) * l + (NN - 1) * m * l edge_iterator[next_vert += 1] = (idx => neighbour_idx) end @@ -132,17 +132,16 @@ function LatticeReactionSystem(rs, srs, lattice_in::Array{Bool, T}; diagonal_con dims = size(lattice_in) (length(dims) > 3) && error("Grids of higher dimension than 3 is currently not supported.") - # Ensures that the matrix has a 3d form (for intermediary computations, original is passed to constructor). - # Also gets some basic lattice information. + # Ensures that the matrix has a 3d form (used for intermediary computations only, + # the original is passed to the constructor). lattice = reshape(lattice_in, [dims...; fill(1, 3-length(dims))]...) # Counts vertexes (edges have to be counted after the iterator have been created). num_verts = count(lattice) - # Creates an iterator over all edges.Currently a full vector of all edges (as pairs). - edge_iterator = Vector{Pair{Int64,Int64}}() - # Makes a template matrix to store each vertex's index. The matrix is 0 where there are no vertex. + # Makes a template matrix to store each vertex's index. The matrix is 0 where there is no vertex. + # The template is used in the next step. idx_matrix = fill(0, size(lattice_in)) cur_vertex_idx = 0 for flat_idx in 1:length(lattice) @@ -151,8 +150,10 @@ function LatticeReactionSystem(rs, srs, lattice_in::Array{Bool, T}; diagonal_con end end - # Loops through, simultaneously, the coordinates of each position in the grid, as well as that - # coordinate's (scalar) flat index. For each grid point, loops through all potential neighbours. + # Creates an iterator over all edges. A vector with pairs of each edge's source to its destination. + edge_iterator = Vector{Pair{Int64,Int64}}() + # Loops through, the coordinates of each position in the grid. + # For each grid point, loops through all potential neighbours and adds edges to edge_iterator. l, m, n = size(lattice) indices = [(L, M, N) for L in 1:l, M in 1:m, N in 1:n] for (L, M, N) in indices @@ -169,7 +170,7 @@ function LatticeReactionSystem(rs, srs, lattice_in::Array{Bool, T}; diagonal_con !diagonal_connections && (count([L==LL, M==MM, N==NN]) == 2) || continue diagonal_connections && (L==LL) && (M==MM) && (N==NN) && continue - # Computes the neighbours scalar index. Add that connection to `edge_iterator`. + # Computes the neighbour's scalar index. Add that connection to `edge_iterator`. push!(edge_iterator, idx_matrix[L,M,N] => idx_matrix[LL,MM,NN]) end end @@ -188,6 +189,7 @@ end # Creates a LatticeReactionSystem from a (undirected) Graph lattice (graph grid). LatticeReactionSystem(rs, srs, lattice::SimpleGraph) = LatticeReactionSystem(rs, srs, DiGraph(lattice)) + ### Lattice ReactionSystem Getters ### # Get all species. @@ -197,9 +199,9 @@ spatial_species(lrs::LatticeReactionSystem) = lrs.spat_species # Get all parameters. ModelingToolkit.parameters(lrs::LatticeReactionSystem) = lrs.parameters -# Get all parameters which values are tied to vertexes (compartments). +# Get all parameters whose values are tied to vertexes (compartments). vertex_parameters(lrs::LatticeReactionSystem) = lrs.vertex_parameters -# Get all parameters which values are tied to edges (adjacencies). +# Get all parameters whose values are tied to edges (adjacencies). edge_parameters(lrs::LatticeReactionSystem) = lrs.edge_parameters # Gets the lrs name (same as rs name). @@ -211,35 +213,39 @@ is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReac """ has_cartesian_lattice(lrs::LatticeReactionSystem) -Returns `true` if `lrs` was created using a cartesian grid lattice (e.g. created via `CartesianGrid(5,5)`). Else, returns `false`. +Returns `true` if `lrs` was created using a cartesian grid lattice (e.g. created via `CartesianGrid(5,5)`). +Otherwise, returns `false`. """ has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa CartesianGridRej{S,T} where {S,T} """ has_masked_lattice(lrs::LatticeReactionSystem) -Returns `true` if `lrs` was created using a masked grid lattice (e.g. created via `[true true; true false]`). Else, returns `false`. +Returns `true` if `lrs` was created using a masked grid lattice (e.g. created via `[true true; true false]`). +Otherwise, returns `false`. """ has_masked_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa Array{Bool, T} where T """ has_grid_lattice(lrs::LatticeReactionSystem) -Returns `true` if `lrs` was created using a cartesian or masked grid lattice. Else, returns `false`. +Returns `true` if `lrs` was created using a cartesian or masked grid lattice. Otherwise, returns `false`. """ has_grid_lattice(lrs::LatticeReactionSystem) = (has_cartesian_lattice(lrs) || has_masked_lattice(lrs)) """ has_graph_lattice(lrs::LatticeReactionSystem) -Returns `true` if `lrs` was created using a graph grid lattice (e.g. created via `path_graph(5)`). Else, returns `false`. +Returns `true` if `lrs` was created using a graph grid lattice (e.g. created via `path_graph(5)`). +Otherwise, returns `false`. """ has_graph_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa SimpleDiGraph """ grid_size(lrs::LatticeReactionSystem) -Returns the size of `lrs`'s lattice (only if it is a cartesian or masked grid lattice). E.g. for a lattice `CartesianGrid(4,6)`, `(4,6)` is returned. +Returns the size of `lrs`'s lattice (only if it is a cartesian or masked grid lattice). +E.g. for a lattice `CartesianGrid(4,6)`, `(4,6)` is returned. """ function grid_size(lrs::LatticeReactionSystem) has_cartesian_lattice(lrs) && (return lrs.lattice.dims) @@ -250,6 +256,7 @@ end """ grid_dims(lrs::LatticeReactionSystem) -Returns the number of dimensions of `lrs`'s lattice (only if it is a cartesian or masked grid lattice). The output is either `1`, `2`, or `3`. +Returns the number of dimensions of `lrs`'s lattice (only if it is a cartesian or masked grid lattice). +The output is either `1`, `2`, or `3`. """ grid_dims(lrs::LatticeReactionSystem) = length(grid_size(lrs)) \ No newline at end of file diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index ecf5b6dd86..19b7b7e05e 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -2,43 +2,46 @@ # Functor with information for the forcing function of a spatial ODE with spatial movement on a lattice. struct LatticeTransportODEf{S,T} - """The ODEFunction of the (non-spatial) reaction system which generated this function.""" + """The ODEFunction of the (non-spatial) ReactionSystem that generated this LatticeTransportODEf instance.""" ofunc::S """The number of vertices.""" num_verts::Int64 """The number of species.""" num_species::Int64 - """The values of the parameters which values are tied to vertexes.""" + """The values of the parameters that are tied to vertexes.""" vert_ps::Vector{Vector{T}} """ - Temporary vector. For parameters which values are identical across the lattice, - at some point these have to be converted of a length num_verts vector. - To avoid re-allocation they are written to this vector. + Vector for storing temporary values. Repeatedly during simulations, we need to retrieve the + parameter values in a certain vertex. However, since most parameters (likely) are uniform + (and hence only have 1 value stored), we need to create a new vector each time we need to retrieve + the parameter values in a new vertex. To avoid relocating these values repeatedly, we write them + to this vector. """ work_vert_ps::Vector{T} """ - For each parameter in vert_ps, its value is a vector with length either num_verts or 1. - To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. - This check is done once, and the value stored to this array. True means a uniform value. + For each parameter in vert_ps, its value is a vector with a length of either num_verts or 1. + To know whenever a parameter's value needs expanding to the work_vert_ps array, its length needs checking. + This check is done once, and the value is stored in this array. True means a uniform value. """ v_ps_idx_types::Vector{Bool} """ - A vector of sparse, with a value for each species with transportation. - The first value is the species index (in the species(::ReactionSystem) vector), - and the second is a vector with its transport rate values. - If the transport rate is uniform (across all edges), that value is the only value in the vector. - Else, there is one value for each edge in the lattice. + A vector that stores, for each species with transportation, its transportation rate(s). + Each entry is a pair from (the index of) the transported species (in the `species(lrs)` vector) + to its transportation rate (each species only has a single transportation rate, the sum of all + its transportation reactions' rates). If the transportation rate is uniform across all edges, + stores a single value (in a size (1,1) sparse matrix). Otherwise, stores these in a sparse matrix + where value (i,j) is the species transportation rate from vertex i to vertex j. """ transport_rates::Vector{Pair{Int64,SparseMatrixCSC{T, Int64}}} """ - For each transport rate in transport_rates, its value is a (sparse) matrix with size either - (num_verts,num_verts) or (1,1). In the second case, that transportation rate is uniform across all edges. - To know how to access transport rate's value (without checking sizes), we can use this vector directly. - True means a uniform value. + For each transport rate in transport_rates, its value is a (sparse) matrix with a size of either + (num_verts,num_verts) or (1,1). In the second case, the transportation rate is uniform across + all edges. To avoid having to check which case holds for each transportation rate, we store the + corresponding case in this value. `true` means that a species has a uniform transportation rate. """ t_rate_idx_types::Vector{Bool} """ - A matrix, NxM, where N is the number of species with transportation and M the number of vertexes. + A matrix, NxM, where N is the number of species with transportation and M is the number of vertexes. Each value is the total rate at which that species leaves that vertex (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D). """ @@ -46,27 +49,28 @@ struct LatticeTransportODEf{S,T} """An iterator over all the edges of the lattice.""" edge_iterator::Vector{Pair{Int64, Int64}} - function LatticeTransportODEf(ofunc::S, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, transport_rates::Vector{Pair{Int64, SparseMatrixCSC{T, Int64}}}, + function LatticeTransportODEf(ofunc::S, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, + transport_rates::Vector{Pair{Int64, SparseMatrixCSC{T, Int64}}}, lrs::LatticeReactionSystem) where {S,T} - # Records. which parameters and rates are uniform and not. + # Records which parameters and rates are uniform and which are not. v_ps_idx_types = map(vp -> length(vp[2]) == 1, vert_ps) t_rate_idx_types = map(tr -> size(tr[2]) == (1,1), transport_rates) - # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a vector). - # This vector is sorted according to the parameters order. Here, we simply extract its values only. + # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a + # vector). This vector is already sorted according to the order of the parameters. Here, we extract + # its values only and put them into `vert_ps`. vert_ps = [vp[2] for vp in vert_ps] - # Computes the leaving rates. + # Computes the leaving rate matrix. leaving_rates = zeros(length(transport_rates), lrs.num_verts) - for (s_idx, trpair) in enumerate(transport_rates) - t_rate = trpair[2] + for (s_idx, tr_pair) in enumerate(transport_rates) for e in lrs.edge_iterator - # Updates the exit rate for species s_idx from vertex e.src - leaving_rates[s_idx, e[1]] += get_transport_rate(t_rate, e, t_rate_idx_types[s_idx]) + # Updates the exit rate for species s_idx from vertex e.src. + leaving_rates[s_idx, e[1]] += get_transport_rate(tr_pair[2], e, t_rate_idx_types[s_idx]) end end - # Declares `work_vert_ps` (used as storage during computation) and an iterator over the edges. + # Declares `work_vert_ps` (used as storage during computation) and the edge iterator. work_vert_ps = zeros(length(vert_ps)) edge_iterator = lrs.edge_iterator new{S,T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, @@ -76,25 +80,26 @@ end # Functor with information for the Jacobian function of a spatial ODE with spatial movement on a lattice. struct LatticeTransportODEjac{R,S,T} - """The ODEFunction of the (non-spatial) reaction system which generated this function.""" + """The ODEFunction of the (non-spatial) ReactionSystem that generated this LatticeTransportODEf instance.""" ofunc::R """The number of vertices.""" num_verts::Int64 """The number of species.""" num_species::Int64 - """The values of the parameters which values are tied to vertexes.""" + """The values of the parameters that are tied to vertexes.""" vert_ps::Vector{Vector{S}} """ - Temporary vector. For parameters which values are identical across the lattice, - at some point these have to be converted of a length(num_verts) vector. - To avoid re-allocation they are written to this vector. + Vector for storing temporary values. Repeatedly during simulations, we need to retrieve the + parameter values in a certain vertex. However, since most parameters (likely) are uniform + (and hence only have 1 value stored), we need to create a new vector each time we need to retrieve + the parameter values in a new vertex. To avoid relocating these values repeatedly, we write them + to this vector. """ - work_vert_ps::Vector{S} + work_vert_ps::Vector{S} """ - For each parameter in vert_ps, it either have length num_verts or 1. - To know whenever a parameter's value need expanding to the work_vert_ps array, - its length needs checking. This check is done once, and the value stored to this array. - This field (specifically) is an enumerate over that array. + For each parameter in vert_ps, its value is a vector with a length of either num_verts or 1. + To know whenever a parameter's value needs expanding to the work_vert_ps array, its length needs checking. + This check is done once, and the value is stored in this array. True means a uniform value. """ v_ps_idx_types::Vector{Bool} """Whether the Jacobian is sparse or not.""" @@ -102,11 +107,12 @@ struct LatticeTransportODEjac{R,S,T} """The transport rates. This is a dense or sparse matrix (depending on what type of Jacobian is used).""" jac_transport::T - function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{S}}}, lrs::LatticeReactionSystem, + function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{S}}}, jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, - sparse::Bool) where {R,S} - # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a vector). - # This vector is sorted according to the parameters order. Here, we simply extract its values only. + lrs::LatticeReactionSystem, sparse::Bool) where {R,S} + # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a + # vector). This vector is already sorted according to the order of the parameters. Here, we extract + # its values only and put them into `vert_ps`. vert_ps = [vp[2] for vp in vert_ps] work_vert_ps = zeros(lrs.num_verts) @@ -129,26 +135,24 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, error("Currently lattice ODE simulations are only supported when all spatial reactions are TransportReactions.") end - # Converts potential symmaps to varmaps - # Vertex and edge parameters may be given in a tuple, or in a common vector, making parameter case complicated. + # Converts potential symmaps to varmaps. u0_in = symmap_to_varmap(lrs, u0_in) p_in = symmap_to_varmap(lrs, p_in) # Converts u0 and p to their internal forms. - # u0 is simply a vector with all the species initial condition values across all vertexes. + # u0 is simply a vector with all the species' initial condition values across all vertexes. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. u0 = lattice_process_u0(u0_in, species(lrs), lrs) - # vert_ps and `edge_ps` are vector maps, taking each parameter's Symbolics to its value(s). - # vert_ps values are vectors. Here, index (i) is a parameters value in vertex i. - # edge_ps becomes sparse matrix. Here, index (i,j) is a parameters value in the edge from vertex i to vertex j. - # Uniform vertex/edge parameters stores only a single value (in a length 1 vector, or size 1x1 sparse matrix). - # This is the parameters single value. + # vert_ps and `edge_ps` are vector maps, taking each parameter's Symbolics representation to its value(s). + # vert_ps values are vectors. Here, index (i) is a parameter's value in vertex i. + # edge_ps becomes a sparse matrix. Here, index (i,j) is a parameter's value in the edge from vertex i to vertex j. + # Uniform vertex/edge parameters store only a single value (a length 1 vector, or size 1x1 sparse matrix). # In the `ODEProblem` vert_ps and edge_ps are merged (but for building the ODEFunction, they are separate). vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) - # Creates ODEProblem. + # Creates the ODEFunction. ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse, name, include_zero_odes, - combinatoric_ratelaws, remove_conserved, checks) + combinatoric_ratelaws, remove_conserved, checks) # Combines `vert_ps` and `edge_ps` to a single vector with values only (not a map). Creates ODEProblem. ps = [p[2] for p in [vert_ps; edge_ps]] @@ -156,15 +160,16 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, end # Builds an ODEFunction for a spatial ODEProblem. -function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{A,Vector{T}}}, - edge_ps::Vector{Pair{B,SparseMatrixCSC{T, Int64}}}, jac::Bool, sparse::Bool, - name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) where {A,B,T} +function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, + edge_ps::Vector{Pair{BasicSymbolic{Real},SparseMatrixCSC{T, Int64}}}, + jac::Bool, sparse::Bool, name, include_zero_odes, combinatoric_ratelaws, + remove_conserved, checks) where {T} if remove_conserved error("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s") end - # Creates a map, taking (the index in species(lrs) each species (with transportation) - # to its transportation rate (uniform or one value for each edge). + # Creates a map, taking (the index in species(lrs) of) each species (with transportation) + # to its transportation rate (uniform or one value for each edge). The rates are sparse matrices. transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) # Prepares the Jacobian and forcing functions (depending on jacobian and sparsity selection). @@ -172,18 +177,18 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{A,Ve if jac # `build_jac_prototype` currently assumes a sparse (non-spatial) Jacobian. Hence compute this. # `LatticeTransportODEjac` currently assumes a dense (non-spatial) Jacobian. Hence compute this. - # Long term we could write separate version of these functions for generic input. + # Long term we could write separate versions of these functions for generic input. ofunc_dense = ODEFunction(osys; jac = true, sparse = false) ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) - jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) + jac_transport = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) if sparse f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, lrs) - jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) - J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, true) - jac_prototype = jac_vals + jac_transport = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) + J = LatticeTransportODEjac(ofunc_dense, vert_ps, jac_transport, lrs, true) + jac_prototype = jac_transport else f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, lrs) - J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, false) + J = LatticeTransportODEjac(ofunc_dense, vert_ps, jac_transport, lrs, false) jac_prototype = nothing end else @@ -202,10 +207,13 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{A,Ve return ODEFunction(f; jac = J, jac_prototype = jac_prototype) end -# Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. -function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, transport_rates::Vector{Pair{Int64,SparseMatrixCSC{T, Int64}}}, - lrs::LatticeReactionSystem; set_nonzero = false) where T - # Finds the indexes of the transport species, and the species with transport only (and no non-spatial dynamics). +# Builds a jacobian prototype. +# If requested, populate it with the constant values of the Jacobian's transportation part. +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, + transport_rates::Vector{Pair{Int64,SparseMatrixCSC{T, Int64}}}, + lrs::LatticeReactionSystem; set_nonzero = false) where {T} + # Finds the indexes of both the transport species, + # and the species with transport only (that is, with no non-spatial dynamics but with spatial dynamics). trans_species = [tr[1] for tr in transport_rates] trans_only_species = filter(s_idx -> !Base.isstored(ns_jac_prototype, s_idx, s_idx), trans_species) @@ -221,7 +229,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, i_idxs = Vector{Int}(undef, num_entries) j_idxs = Vector{Int}(undef, num_entries) - # Indexes of elements due to non-spatial dynamics. + # Indexes of elements caused by non-spatial dynamics. for vert in 1:lrs.num_verts for n in 1:length(ns_i_idxs) i_idxs[idx] = get_index(vert, ns_i_idxs[n], lrs.num_species) @@ -230,9 +238,9 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, end end - # Indexes of elements due to spatial dynamics. + # Indexes of elements caused by spatial dynamics. for e in lrs.edge_iterator - # Indexes due to terms for a species leaves its current vertex (but does not have + # Indexes due to terms for a species leaving its source vertex (but does not have # non-spatial dynamics). If the non-spatial Jacobian is fully dense, these would already # be accounted for. for s_idx in trans_only_species @@ -240,7 +248,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, j_idxs[idx] = i_idxs[idx] idx += 1 end - # Indexes due to terms for species arriving into a new vertex. + # Indexes due to terms for species arriving into a destination vertex. for s_idx in trans_species i_idxs[idx] = get_index(e[1], s_idx, lrs.num_species) j_idxs[idx] = get_index(e[2], s_idx, lrs.num_species) @@ -248,7 +256,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, end end - # Create sparse jacobian prototype with 0-valued entries. + # Create a sparse Jacobian prototype with 0-valued entries. jac_prototype = sparse(i_idxs, j_idxs, zeros(num_entries)) # Set element values. @@ -273,25 +281,25 @@ end function (f_func::LatticeTransportODEf)(du, u, p, t) # Updates for non-spatial reactions. for vert_i in 1:(f_func.num_verts) - # Gets the indices of species at vertex i. + # Gets the indexes of all the species at vertex i. idxs = get_indexes(vert_i, f_func.num_species) - # Updates the vector which contains the vertex parameter values for vertex vert_i. + # Updates the work vector to contain the vertex parameter values for vertex vert_i. update_work_vert_ps!(f_func, p, vert_i) # Evaluate reaction contributions to du at vert_i. f_func.ofunc((@view du[idxs]), (@view u[idxs]), f_func.work_vert_ps, t) end - # s_idx is species index among transport species, s is index among all species. + # s_idx is the species index among transport species, s is the index among all species. # rates are the species' transport rates. for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) - # Rate for leaving vert_i + # Rate for leaving source vertex vert_i. for vert_i in 1:(f_func.num_verts) idx_src = get_index(vert_i, s, f_func.num_species) du[idx_src] -= f_func.leaving_rates[s_idx, vert_i] * u[idx_src] end - # Add rates for entering a given vertex via an incoming edge. + # Add rates for entering a destination vertex via an incoming edge. for e in f_func.edge_iterator idx_src = get_index(e[1], s, f_func.num_species) idx_dst = get_index(e[2], s, f_func.num_species) @@ -300,17 +308,17 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) end end -# Defines the jacobian functor's effect on the (spatial) ODE system. +# Defines the Jacobian functor's effect on the (spatial) ODE system. function (jac_func::LatticeTransportODEjac)(J, u, p, t) J .= 0.0 - # Update the Jacobian from reaction terms. + # Update the Jacobian from non-spatial reaction terms. for vert_i in 1:(jac_func.num_verts) idxs = get_indexes(vert_i, jac_func.num_species) update_work_vert_ps!(jac_func, p, vert_i) jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), jac_func.work_vert_ps, t) end - # Updates for the spatial reactions (adds the Jacobian values from the diffusion reactions). + # Updates for the spatial reactions (adds the Jacobian values from the transportation reactions). J .+= jac_func.jac_transport end diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 581bd7f735..b13afee14f 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -3,7 +3,7 @@ # Abstract spatial reaction structures. abstract type AbstractSpatialReaction end -### EdgeParameter Metadata ### +### Edge Parameter Metadata ### # Implements the edgeparameter metadata field. struct EdgeParameter end @@ -17,12 +17,13 @@ function isedgeparameter(x, default = false) Symbolics.getmetadata(x, EdgeParameter, default) end + ### Transport Reaction Structures ### # A transport reaction. These are simple to handle, and should cover most types of spatial reactions. # Only permit constant rates (possibly consisting of several parameters). struct TransportReaction <: AbstractSpatialReaction - """The rate function (excluding mass action terms). Currently only constants supported""" + """The rate function (excluding mass action terms). Currently, only constants supported""" rate::Any """The species that is subject to diffusion.""" species::BasicSymbolic{Real} @@ -40,7 +41,7 @@ function TransportReactions(transport_reactions) [TransportReaction(tr[1], tr[2]) for tr in transport_reactions] end -# Macro for creating a transport reaction. +# Macro for creating a TransportReactions. macro transport_reaction(rateex::ExprValues, species::ExprValues) make_transport_reaction(MacroTools.striplines(rateex), species) end @@ -70,17 +71,17 @@ function make_transport_reaction(rateex, species) end end -# Gets the parameters in a transport reaction. +# Gets the parameters in a TransportReactions. ModelingToolkit.parameters(tr::TransportReaction) = Symbolics.get_variables(tr.rate) -# Gets the species in a transport reaction. +# Gets the species in a TransportReactions. spatial_species(tr::TransportReaction) = [tr.species] -# Checks that a transport reaction is valid for a given reaction system. +# Checks that a TransportReactions is valid for a given reaction system. function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction; edge_parameters=[]) # Checks that the species exist in the reaction system. # (ODE simulation code becomes difficult if this is not required, - # as non-spatial jacobian and f function generated from rs is of wrong size). + # as non-spatial jacobian and f function generated from rs are of the wrong size). if !any(isequal(tr.species), species(rs)) error("Currently, species used in TransportReactions must have previously been declared within the non-spatial ReactionSystem. This is not the case for $(tr.species).") end @@ -135,8 +136,10 @@ function hash(tr::TransportReaction, h::UInt) Base.hash(tr.species, h) end + ### Utility ### -# Loops through a rate and extract all parameters. + +# Loops through a rate and extracts all parameters. function find_parameters_in_rate!(parameters, rateex::ExprValues) if rateex isa Symbol if rateex in [:t, :∅, :im, :nothing, CONSERVED_CONSTANT_SYMBOL] @@ -145,7 +148,7 @@ function find_parameters_in_rate!(parameters, rateex::ExprValues) push!(parameters, rateex) end elseif rateex isa Expr - # Note, this (correctly) skips $(...) expressions + # Note, this (correctly) skips $(...) expressions. for i in 2:length(rateex.args) find_parameters_in_rate!(parameters, rateex.args[i]) end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 4c4d95043c..f1c2a6e4ed 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -13,45 +13,46 @@ function _symbol_to_var(lrs::LatticeReactionSystem, sym) error("Could not find property parameter/species $sym in lattice reaction system.") end -# From u0 input, extracts their values and store them in the internal format. -# Internal format: a vector on the form [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]). -function lattice_process_u0(u0_in, u0_syms, lrs::LatticeReactionSystem) +# From u0 input, extract their values and store them in the internal format. +# Internal format: a vector on the form [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]). +function lattice_process_u0(u0_in, u0_syms::Vector{BasicSymbolic{Real}}, lrs::LatticeReactionSystem) # u0 values can be given in various forms. This converts it to a Vector{Pair{Symbolics,...}} form. # Top-level vector: Maps each species to its value(s). u0 = lattice_process_input(u0_in, u0_syms) # Species' initial condition values can be given in different forms (also depending on the lattice). - # This converts each species's values to a Vector. For species with uniform initial conditions, - # The value vector holds that value only. For spatially heterogeneous initial conditions, - # the vector have teh same length as the number of vertexes (with one value for each). + # This converts each species's values to a Vector. In it, for species with uniform initial conditions, + # it holds that value only. For spatially heterogeneous initial conditions, + # the vector has the same length as the number of vertexes (storing one value for each). u0 = vertex_value_map(u0, lrs) - # Converts the initial condition to a single Vector (with one values for each species and vertex). + # Converts the initial condition to a single Vector (with one value for each species and vertex). return expand_component_values([entry[2] for entry in u0], lrs.num_verts) end -# From p input, splits it into diffusion parameters and compartment parameters. +# From a parameter input, split it into vertex parameters and edge parameters. # Store these in the desired internal format. -function lattice_process_p(ps_in, ps_vertex_syms, ps_edge_syms, lrs::LatticeReactionSystem) +function lattice_process_p(ps_in, ps_vertex_syms::Vector{BasicSymbolic{Real}}, + ps_edge_syms::Vector{BasicSymbolic{Real}}, lrs::LatticeReactionSystem) # p values can be given in various forms. This converts it to a Vector{Pair{Symbolics,...}} form. # Top-level vector: Maps each parameter to its value(s). # Second-level: Contains either a vector (vertex parameters) or a sparse matrix (edge parameters). - # For uniform parameters these have size 1/1x1. Else, they have size num_verts/num_vertsxnum_verts. + # For uniform parameters these have size 1/(1,1). Else, they have size num_verts/(num_verts,num_verts). ps = lattice_process_input(ps_in, [ps_vertex_syms; ps_edge_syms]) # Split the parameter vector into one for vertex parameters and one for edge parameters. - # Next, converts the values to the correct form (vectors for vert_ps and sparse matrices for edge_ps). + # Next, convert their values to the correct form (vectors for vert_ps and sparse matrices for edge_ps). vert_ps, edge_ps = split_parameters(ps, ps_vertex_syms, ps_edge_syms) vert_ps = vertex_value_map(vert_ps, lrs) edge_ps = edge_value_map(edge_ps, lrs) - + return vert_ps, edge_ps end # The input (parameters or initial conditions) may either be a dictionary (symbolics to value(s).) # or a map (in vector or tuple form) from symbolics to value(s). This converts the input to a # (Vector) map from symbolics to value(s), where the entries have the same order as `syms`. -function lattice_process_input(input::Dict{BasicSymbolic{Real}, <:Any}, syms::Vector{BasicSymbolic{Real}}) +function lattice_process_input(input::Dict{BasicSymbolic{Real}, T}, syms::Vector{BasicSymbolic{Real}}) where {T} # Error checks if !isempty(setdiff(keys(input), syms)) error("You have provided values for the following unrecognised parameters/initial conditions: $(setdiff(keys(input), syms)).") @@ -62,40 +63,56 @@ function lattice_process_input(input::Dict{BasicSymbolic{Real}, <:Any}, syms::Ve return [sym => input[sym] for sym in syms] end -function lattice_process_input(input, syms::Vector{BasicSymbolic{Real}}) - lattice_process_input(Dict(input), syms) +function lattice_process_input(input, syms::Vector{BasicSymbolic{Real}}) + if ((input isa Vector) || (input isa Vector)) && all(entry isa Pair for entry in input) + return lattice_process_input(Dict(input), syms) + end + error("Input parameters/initial conditions have the wrong format ($(typeof(input))). These should either be a Dictionary, or a Tuple or a Vector (where each entry is a Pair taking a parameter/species to its value).") end # Splits parameters into vertex and edge parameters. -#function split_parameters(ps::Vector{<: Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) -function split_parameters(ps, p_vertex_syms, p_edge_syms) +# function split_parameters(ps::Vector{<: Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) +function split_parameters(ps, p_vertex_syms::Vector{BasicSymbolic{Real}}, p_edge_syms::Vector{BasicSymbolic{Real}}) vert_ps = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] edge_ps = [p for p in ps if any(isequal(p[1]), p_edge_syms)] return vert_ps, edge_ps end -# Converts the values for initial condition/vertex parameters to the correct form: -# Map from symbolics to to vectors of length either 1 (for uniform values) or num_verts. -function vertex_value_map(values, lrs) +# Converts the values for the initial conditions/vertex parameters to the correct form: +# A map vector from symbolics to vectors of either length 1 (for uniform values) or num_verts. +function vertex_value_map(values, lrs::LatticeReactionSystem) isempty(values) && (return Pair{BasicSymbolic{Real}, Vector{Float64}}[]) return [entry[1] => vertex_value_form(entry[2], lrs, entry[1]) for entry in values] end -# Converts the values for a specific component (species/parameter) to the correct vector form. -function vertex_value_form(values, lrs::LatticeReactionSystem, sym) + +# Converts the values for an individual species/vertex parameter to its correct vector form. +function vertex_value_form(values, lrs::LatticeReactionSystem, sym::BasicSymbolic{Real}) + # If the value is a scalar (i.e. uniform across the lattice), return it in vector form. (values isa AbstractArray) || (return [values]) + + # If the value is a vector (something all three lattice types accept). if values isa Vector + # For the case where we have a 1d (Cartesian or masked) grid, and the vector's values + # correspond to individual grid points. if has_grid_lattice(lrs) && (size(values) == grid_size(lrs)) vertex_value_form(values, lrs.num_verts, lrs.lattice, sym) end + + # For the case where the i'th value of the vector corresponds to the value in the i'th vertex. + # This is the only (non-uniform) case possible for graph grids. if (length(values) != lrs.num_verts) error("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertexes ($(lrs.num_verts)).") end return values end + + # (2d and 3d) Cartesian and masked grids can take non-vector, non-scalar, values input. return vertex_value_form(values, lrs.num_verts, lrs.lattice, sym) end -# Converts values to correct vector form for a Cartesian grid lattice. -function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::CartesianGridRej{S,T}, sym) where {S,T} + +# Converts values to the correct vector form for a Cartesian grid lattice. +function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::CartesianGridRej{S,T}, + sym::BasicSymbolic{Real}) where {S,T} if size(values) != lattice.dims error("The values for $sym did not have the same format as the lattice. Expected a $(lattice.dims) array, got one of size $(size(values))") end @@ -104,32 +121,40 @@ function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::Car end return [values[flat_idx] for flat_idx in 1:num_verts] end -# Converts values to correct vector form for a masked grid lattice. -function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::Array{Bool, T}, sym) where {T} + +# Converts values to the correct vector form for a masked grid lattice. +function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::Array{Bool,T}, + sym::BasicSymbolic{Real}) where {T} if size(values) != size(lattice) error("The values for $sym did not have the same format as the lattice. Expected a $(size(lattice)) array, got one of size $(size(values))") end + + # Pre-declares a vector with the values in each vertex (return_values). + # Loops through the lattice and the values, adding these to the return_values. return_values = Vector{typeof(values[1])}(undef, num_verts) cur_idx = 0 - for i = 1:length(lattice) - lattice[i] || continue - return_values[cur_idx += 1] = values[i] + for (idx,val) = enumerate(values) + lattice[idx] || continue + return_values[cur_idx += 1] = val end + + # Checks that the correct number of values was provided, and returns the values. if (length(return_values) != num_verts) error("You have provided ($(length(return_values))) values for $sym. This is not equal to the number of vertexes ($(num_verts)).") end return return_values end -# Converts the values for initial condition/vertex parameters to the correct form: -# Map from symbolics to to vectors of length either 1 (for uniform values) or num_verts. -function edge_value_map(values, lrs) +# Converts the values for the edge parameters to the correct form: +# A map vector from symbolics to sparse matrices of size either (1,1) or (num_verts,num_verts). +function edge_value_map(values, lrs::LatticeReactionSystem) isempty(values) && (return Pair{BasicSymbolic{Real}, SparseMatrixCSC{Float64, Int64}}[]) return [entry[1] => edge_value_form(entry[2], lrs, entry[1]) for entry in values] end -# Converts the values for a specific component (species/parameter) to the correct vector form. + +# Converts the values for an individual edge parameter to its correct sparse matrix form. function edge_value_form(values, lrs::LatticeReactionSystem, sym) - # If a scalar have been given, converts it to a size (1,1) sparse matrix. + # If the value is a scalar (i.e. uniform across the lattice), return it in sparse matrix form. (values isa SparseMatrixCSC) || (return sparse([1], [1], [values])) # Error checks. @@ -140,6 +165,8 @@ function edge_value_form(values, lrs::LatticeReactionSystem, sym) error("Values was not provided for some edges for edge parameter $sym.") end + # Unlike initial conditions/vertex parameters, (unless uniform) edge parameters' values are + # always provided in the same (sparse matrix) form. return values end @@ -147,12 +174,14 @@ end # The species is represented by its index (in species(lrs). # If the rate is uniform across all edges, the transportation rate will be a size (1,1) sparse matrix. # Else, the rate will be a size (num_verts,num_verts) sparse matrix. -# In the first step, computes a map from species symbolics form to value(s). -# Second step converts to map from species index to value(s). function make_sidxs_to_transrate_map(vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, edge_ps::Vector{Pair{BasicSymbolic{Real},SparseMatrixCSC{T, Int64}}}, - lrs::LatticeReactionSystem) where T + lrs::LatticeReactionSystem) where {T} + # Creates a dictionary with each parameter's value(s). p_val_dict = Dict(vcat(vert_ps, edge_ps)) + + # First, compute a map from species in their symbolics form to their values. + # Next, convert to map from species index to values. transport_rates_speciesmap = compute_all_transport_rates(p_val_dict, lrs) return Pair{Int64,SparseMatrixCSC{T, Int64}}[ speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] for spat_rates in transport_rates_speciesmap @@ -160,38 +189,30 @@ function make_sidxs_to_transrate_map(vert_ps::Vector{Pair{BasicSymbolic{Real},Ve end # Computes the transport rates for all species with transportation rates. Output is a map -# taking each species; symbolics form to its transportation rates across all edges. +# taking each species' symbolics form to its transportation rates across all edges. function compute_all_transport_rates(p_val_dict, lrs::LatticeReactionSystem) # For all species with transportation, compute their transportation rate (across all edges). # This is a vector, pairing each species to these rates. - unsorted_rates = [s => compute_transport_rates(get_transport_rate_law(s, lrs), p_val_dict, lrs) - for s in spatial_species(lrs)] + unsorted_rates = [s => compute_transport_rates(s, p_val_dict, lrs) for s in spatial_species(lrs)] - # Sorts all the species => rate pairs according to their species index in species(::ReactionSystem). + # Sorts all the species => rate pairs according to their species index in species(lrs). return sort(unsorted_rates; by = rate -> findfirst(isequal(rate[1]), species(lrs))) end -# For a species, retrieves the symbolic expression for its transportation rate -# (likely only a single parameter, such as `D`, but could be e.g. L*D, where L and D are parameters). -# If there are several transportation reactions for the species, their sum is used. -#function get_transport_rate_law(s::BasicSymbolic{Real}, lrs::LatticeReactionSystem) -function get_transport_rate_law(s, lrs) - rates = filter(sr -> isequal(s, sr.species), lrs.spatial_reactions) - return sum(getfield.(rates, :rate)) -end -# For the numeric expression describing the rate of transport (likely only a single parameter, e.g. `D`), -# and the values of all our parameters, computes the transport rate(s). -# If all parameters the rate depend on are uniform all edges, this becomes a length 1 vector. -# Else a vector with each value corresponding to the rate at one specific edge. -#function compute_transport_rates(rate_law::Num, p_val_dict, lrs::LatticeReactionSystem) -function compute_transport_rates(rate_law, p_val_dict, lrs) - # Finds parameters involved in rate and create a function evaluating the rate law. + +# For the expression describing the rate of transport (likely only a single parameter, e.g. `D`), +# and the values of all our parameters, compute the transport rate(s). +# If all parameters that the rate depends on are uniform across all edges, this becomes a length-1 vector. +# Else it becomes a vector where each value corresponds to the rate at one specific edge. +function compute_transport_rates(s::BasicSymbolic{Real}, p_val_dict, lrs::LatticeReactionSystem) + # Find parameters involved in the rate and create a function evaluating the rate law. + rate_law = get_transport_rate_law(s, lrs) relevant_ps = Symbolics.get_variables(rate_law) rate_law_func = drop_expr(@RuntimeGeneratedFunction(build_function(rate_law, relevant_ps...))) - # If all these parameters are spatially uniform, the rates becomes a size (1,1) sparse matrix. - # Else, the rates becomes a size (num_verts,num_verts) sparse matrix. + # If all these parameters are spatially uniform, the rates become a size (1,1) sparse matrix. + # Else, the rates become a size (num_verts,num_verts) sparse matrix. if all(size(p_val_dict[p]) == (1,1) for p in relevant_ps) - relevant_p_vals = [get_edge_value(p_val_dict[p], [1 => 1]) for p in relevant_ps] + relevant_p_vals = [get_edge_value(p_val_dict[p], 1 => 1) for p in relevant_ps] return sparse([1],[1],rate_law_func(relevant_p_vals...)) else transport_rates = spzeros(lrs.num_verts, lrs.num_verts) @@ -203,77 +224,70 @@ function compute_transport_rates(rate_law, p_val_dict, lrs) end end -# Produces a dictionary with all parameters' values. Vertex parameters have their values converted to -# a sparse matrix (with one value for each edge, always using the source vertex's value) -function param_dict(vert_ps, edge_ps, lrs) - return merge(Dict(zip(vertex_parameters(lrs), vert_ps)), Dict(zip(edge_parameters(lrs), edge_ps))) +# For a species, retrieve the symbolic expression for its transportation rate +# (likely only a single parameter, such as `D`, but could be e.g. L*D, where L and D are parameters). +# If there are several transportation reactions for the species, their sum is used. +function get_transport_rate_law(s::BasicSymbolic{Real}, lrs::LatticeReactionSystem) + rates = filter(sr -> isequal(s, sr.species), lrs.spatial_reactions) + return sum(getfield.(rates, :rate)) end ### Accessing Unknown & Parameter Array Values ### - -# Converts a vector of vectors to a long vector. +# Converts a vector of vectors to a single, long, vector. # These are used when the initial condition is converted to a single vector (from vector of vector form). -function expand_component_values(values, num_verts) +function expand_component_values(values::Vector{Vector{T}}, num_verts::Int64) where {T} vcat([get_vertex_value.(values, vert) for vert in 1:num_verts]...) end -# Gets the index in the u array of species s in vertex vert (when their are num_species species). +# Gets the index in the u array of species s in vertex vert (when there are num_species species). get_index(vert::Int64, s::Int64, num_species::Int64) = (vert - 1) * num_species + s -# Gets the indexes in the u array of all species in vertex vert (when their are num_species species). +# Gets the indexes in the u array of all species in vertex vert (when there are num_species species). get_indexes(vert::Int64, num_species::Int64) = ((vert - 1) * num_species + 1):(vert * num_species) -# Returns the value of a parameter in an edge. For vertex parameters, uses their values in the source. -function get_edge_value(values::Vector{T}, edge) where {T} +# Returns the value of a parameter in an edge. For vertex parameters, use their values in the source. +function get_edge_value(values::Vector{T}, edge::Pair{Int64,Int64}) where {T} return (length(values) == 1) ? values[1] : values[edge[1]] end -function get_edge_value(values::SparseMatrixCSC{T, Int64}, edge) where {T} +function get_edge_value(values::SparseMatrixCSC{T, Int64}, edge::Pair{Int64,Int64}) where {T} return (size(values) == (1,1)) ? values[1,1] : values[edge[1],edge[2]] end -# Returns the value of an initial condition of parameter in a vertex. -function get_vertex_value(values::Vector{T}, vert_idx) where {T} +# Returns the value of an initial condition of vertex parameter in a vertex. +function get_vertex_value(values::Vector{T}, vert_idx::Int64) where {T} return (length(values) == 1) ? values[1] : values[vert_idx] end - - - - - - - - -# Finds the transport rate of a parameter going from a source vertex to a destination vertex. -function get_transport_rate(transport_rate, edge::Pair{Int64,Int64}, t_rate_idx_types::Bool) +# Finds the transport rate of a parameter along a specific edge. +function get_transport_rate(transport_rate::SparseMatrixCSC{T, Int64}, edge::Pair{Int64,Int64}, + t_rate_idx_types::Bool) where {T} return t_rate_idx_types ? transport_rate[1,1] : transport_rate[edge[1],edge[2]] end -# Finds the transportation rate for a specific species and a `LatticeTransportODEf` struct. +# Finds the transportation rate for a specific species, LatticeTransportODEf struct, and edge. function get_transport_rate(trans_s_idx::Int64, f_func::LatticeTransportODEf, edge::Pair{Int64,Int64}) get_transport_rate(f_func.transport_rates[trans_s_idx][2], edge, f_func.t_rate_idx_types[trans_s_idx]) end - - -# Updates the internal work_vert_ps vector for a given location. -# To this vector, we write the systems parameter values at a specific vertex. -function update_work_vert_ps!(work_vert_ps, vert_ps, comp, vert_ps_idx_types) +# Updates the internal work_vert_ps vector for a given vertex. +# To this vector, we write the system's parameter values at the specific vertex. +function update_work_vert_ps!(work_vert_ps::Vector{S}, all_ps::Vector{T}, vert::Int64, + vert_ps_idx_types::Vector{Bool}) where {S,T} # Loops through all parameters. for (idx,loc_type) in enumerate(vert_ps_idx_types) # If the parameter is uniform across the spatial structure, it will have a length-1 value vector # (which value we write to the work vector). # Else, we extract it value at the specific location. - work_vert_ps[idx] = (loc_type ? vert_ps[idx][1] : vert_ps[idx][comp]) + work_vert_ps[idx] = (loc_type ? all_ps[idx][1] : all_ps[idx][vert]) end end -# Input is always either a LatticeTransportODEf or LatticeTransportODEjac function (which fields we then pass on). -function update_work_vert_ps!(lt_ode_func, vert_ps, comp) - return update_work_vert_ps!(lt_ode_func.work_vert_ps, vert_ps, comp, lt_ode_func.v_ps_idx_types) +# Input is either a LatticeTransportODEf or LatticeTransportODEjac function (which fields we pass on). +function update_work_vert_ps!(lt_ode_func, all_ps::Vector{T}, vert::Int64) where {T} + return update_work_vert_ps!(lt_ode_func.work_vert_ps, all_ps, vert, lt_ode_func.v_ps_idx_types) end # Expands a u0/p information stored in Vector{Vector{}} for to Matrix form # (currently only used in Spatial Jump systems). -function matrix_expand_component_values(values::Vector{<:Vector}, n) +function matrix_expand_component_values(values::Vector{<:Vector}, n::Int64) reshape(expand_component_values(values, n), length(values), n) end diff --git a/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl b/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl index 992d864b20..74328d6979 100644 --- a/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl +++ b/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl @@ -23,7 +23,7 @@ let u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] pV = SIR_p pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + oprob = ODEProblem(lrs, u0, (0.0, 500.0), [pV; pE]; jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) runtime_target = 0.00027 @@ -38,7 +38,7 @@ let u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] pV = SIR_p pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + oprob = ODEProblem(lrs, u0, (0.0, 500.0), [pV; pE]; jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) runtime_target = 0.12 @@ -53,7 +53,7 @@ let u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] pV = brusselator_p pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + oprob = ODEProblem(lrs, u0, (0.0, 100.0), [pV; pE]) @test SciMLBase.successful_retcode(solve(oprob, CVODE_BDF(linear_solver=:GMRES))) runtime_target = 0.013 @@ -68,7 +68,7 @@ let u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] pV = brusselator_p pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + oprob = ODEProblem(lrs, u0, (0.0, 100.0), [pV; pE]) @test SciMLBase.successful_retcode(solve(oprob, CVODE_BDF(linear_solver=:GMRES))) runtime_target = 11. @@ -99,7 +99,7 @@ let ] pV = CuH_Amination_p pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), [pV; pE]; jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) runtime_target = 0.0012 @@ -130,7 +130,7 @@ let ] pV = CuH_Amination_p pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), [pV; pE]; jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) runtime_target = 0.56 @@ -156,7 +156,7 @@ let ] pV = sigmaB_p pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE)) + oprob = ODEProblem(lrs, u0, (0.0, 50.0), [pV; pE]) @test SciMLBase.successful_retcode(solve(oprob, CVODE_BDF(linear_solver=:GMRES))) runtime_target = 0.61 @@ -182,7 +182,7 @@ let ] pV = sigmaB_p pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) # Time reduced from 50.0 (which casues Julai to crash). + oprob = ODEProblem(lrs, u0, (0.0, 10.0), [pV; pE]) # Time reduced from 50.0 (which casues Julai to crash). @test SciMLBase.successful_retcode(solve(oprob, CVODE_BDF(linear_solver=:GMRES))) runtime_target = 59. diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index 42cef58c28..aae6295254 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -303,7 +303,8 @@ let end u0 = 2 * rand(rng, 10000) - p = [1.0, 4.0, 0.1] + p_hw = [1.0, 4.0, 0.1] + p_aut = [[1.0], [4.0], sparse([1], [1], [0.1])] tspan = (0.0, 100.0) ofun_hw_dense = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac) @@ -323,10 +324,10 @@ let du_aut_dense = deepcopy(u0) du_aut_sparse = deepcopy(u0) - ofun_hw_dense(du_hw_dense, u0, p, 0.0) - ofun_hw_sparse(du_hw_sparse, u0, p, 0.0) - ofun_aut_dense(du_aut_dense, u0, p, 0.0) - ofun_aut_sparse(du_aut_sparse, u0, p, 0.0) + ofun_hw_dense(du_hw_dense, u0, p_hw, 0.0) + ofun_hw_sparse(du_hw_sparse, u0, p_hw, 0.0) + ofun_aut_dense(du_aut_dense, u0, p_aut, 0.0) + ofun_aut_sparse(du_aut_sparse, u0, p_aut, 0.0) @test isapprox(du_hw_dense, du_aut_dense) @test isapprox(du_hw_sparse, du_aut_sparse) From 17cba9e9a7a9ec19f18211548d3dcfdb4614195b Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 7 Feb 2024 08:57:24 -0500 Subject: [PATCH 089/446] up --- src/spatial_reaction_systems/utility.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index f1c2a6e4ed..8577affa4d 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -18,7 +18,7 @@ end function lattice_process_u0(u0_in, u0_syms::Vector{BasicSymbolic{Real}}, lrs::LatticeReactionSystem) # u0 values can be given in various forms. This converts it to a Vector{Pair{Symbolics,...}} form. # Top-level vector: Maps each species to its value(s). - u0 = lattice_process_input(u0_in, u0_syms) + u0 = lattice_process_input(u0_in, u0_syms) # Species' initial condition values can be given in different forms (also depending on the lattice). # This converts each species's values to a Vector. In it, for species with uniform initial conditions, From e7437f7969b11837b453c7d6e0868163560ac18c Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 7 Feb 2024 10:13:22 -0500 Subject: [PATCH 090/446] up --- src/spatial_reaction_systems/utility.jl | 4 +- .../lattice_reaction_systems_ODEs.jl | 32 +++---- .../lattice_reaction_systems_lattice_types.jl | 89 +++++++++++++++++++ 3 files changed, 107 insertions(+), 18 deletions(-) diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 8577affa4d..6cfdafe580 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -64,7 +64,7 @@ function lattice_process_input(input::Dict{BasicSymbolic{Real}, T}, syms::Vector return [sym => input[sym] for sym in syms] end function lattice_process_input(input, syms::Vector{BasicSymbolic{Real}}) - if ((input isa Vector) || (input isa Vector)) && all(entry isa Pair for entry in input) + if ((input isa Vector) || (input isa Tuple)) && all(entry isa Pair for entry in input) return lattice_process_input(Dict(input), syms) end error("Input parameters/initial conditions have the wrong format ($(typeof(input))). These should either be a Dictionary, or a Tuple or a Vector (where each entry is a Pair taking a parameter/species to its value).") @@ -95,7 +95,7 @@ function vertex_value_form(values, lrs::LatticeReactionSystem, sym::BasicSymboli # For the case where we have a 1d (Cartesian or masked) grid, and the vector's values # correspond to individual grid points. if has_grid_lattice(lrs) && (size(values) == grid_size(lrs)) - vertex_value_form(values, lrs.num_verts, lrs.lattice, sym) + return vertex_value_form(values, lrs.num_verts, lrs.lattice, sym) end # For the case where the i'th value of the vector corresponds to the value in the i'th vertex. diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index aae6295254..2acdd00e24 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -302,32 +302,32 @@ let return sparse(jac_prototype_pre) end - u0 = 2 * rand(rng, 10000) - p_hw = [1.0, 4.0, 0.1] - p_aut = [[1.0], [4.0], sparse([1], [1], [0.1])] + num_verts = 5000 + u0 = 2 * rand(rng, 2*num_verts) + p = [1.0, 4.0, 0.1] tspan = (0.0, 100.0) ofun_hw_dense = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac) ofun_hw_sparse = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac, jac_prototype = make_jac_prototype(u0)) - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, - path_graph(Int64(length(u0) / 2))) - u0V = [:X => u0[1:2:(end - 1)], :Y => u0[2:2:end]] - pV = [:A => p[1], :B => p[2]] - pE = [:dX => p[3]] - ofun_aut_dense = ODEProblem(lrs, u0V, tspan, [pV; pE]; jac = true, sparse = false).f - ofun_aut_sparse = ODEProblem(lrs, u0V, tspan, [pV; pE]; jac = true, sparse = true).f + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, path_graph(num_verts)) + u0_map = [:X => u0[1:2:(end - 1)], :Y => u0[2:2:end]] + ps_map = [:A => p[1], :B => p[2], :dX => p[3]] + oprob_aut_dense = ODEProblem(lrs, u0_map, tspan, ps_map; jac = true, sparse = false) + oprob_aut_sparse = ODEProblem(lrs, u0_map, tspan, ps_map; jac = true, sparse = true) + ofun_aut_dense = oprob_aut_dense.f + ofun_aut_sparse = oprob_aut_sparse.f du_hw_dense = deepcopy(u0) du_hw_sparse = deepcopy(u0) du_aut_dense = deepcopy(u0) du_aut_sparse = deepcopy(u0) - ofun_hw_dense(du_hw_dense, u0, p_hw, 0.0) - ofun_hw_sparse(du_hw_sparse, u0, p_hw, 0.0) - ofun_aut_dense(du_aut_dense, u0, p_aut, 0.0) - ofun_aut_sparse(du_aut_sparse, u0, p_aut, 0.0) + ofun_hw_dense(du_hw_dense, u0, p, 0.0) + ofun_hw_sparse(du_hw_sparse, u0, p, 0.0) + ofun_aut_dense(du_aut_dense, u0, oprob_aut_dense.p, 0.0) + ofun_aut_sparse(du_aut_sparse, u0, oprob_aut_dense.p, 0.0) @test isapprox(du_hw_dense, du_aut_dense) @test isapprox(du_hw_sparse, du_aut_sparse) @@ -339,8 +339,8 @@ let ofun_hw_dense.jac(J_hw_dense, u0, p, 0.0) ofun_hw_sparse.jac(J_hw_sparse, u0, p, 0.0) - ofun_aut_dense.jac(J_aut_dense, u0, p, 0.0) - ofun_aut_sparse.jac(J_aut_sparse, u0, p, 0.0) + ofun_aut_dense.jac(J_aut_dense, u0, oprob_aut_dense.p, 0.0) + ofun_aut_sparse.jac(J_aut_sparse, u0, oprob_aut_dense.p, 0.0) @test isapprox(J_hw_dense, J_aut_dense) @test isapprox(J_hw_sparse, J_aut_sparse) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl index c75d0d1c96..b3523523ab 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl @@ -6,6 +6,7 @@ using Catalyst, Graphs, OrdinaryDiffEq, Test # Fetch test networks. include("../spatial_test_networks.jl") + ### Run Tests ### # Test errors when attempting to create networks with dimension > 3. @@ -159,4 +160,92 @@ let graph_sol = solve(graph_oprob, QNDF(); saveat=0.1, abstol=1e-9, reltol=1e-9) @test base_osol ≈ masked_sol ≈ graph_sol end +end + +# For a system which is a single ine of vertexes: (O-O-O-O-X-O-O-O), ensures that different simulations +# approach yield the same result. Checks for both masked and Cartesian grid. For both, simulates where +# initial conditions/vertex parameters are either a vector of the same length as the number of vertexes (7), +# Or as the grid. Here, we try grid sizes (n), (1,n), and (1,n,1) (so the same grid, but in 1d, 2d, and 3d). +# For the Cartesian grid, we cannot represent the gap, so we make simulations both for length-4 and +# length-3 grids. +let + # Declares the initial condition/parameter values. + S_vals = [500.0, 600.0, 700.0, 800.0, 0.0, 900.0, 1000.0, 1100.0] + I_val = 1.0 + R_val = 1.0 + α_vals = [0.1, 0.11, 0.12, 0.13, 0.0, 0.14, 0.15, 0.16] + β_val = 0.01 + dS_val = 0.05 + SIR_p = [:α => 0.1 / 1000, :β => 0.01] + + # Declares the grids (1d, 2d, and 3d). For each dimension, there are a 2 Cartesian grids (length 4 and 3). + cart_grid_1d_1 = CartesianGrid(4) + cart_grid_1d_2 = CartesianGrid(3) + cart_grid_2d_1 = CartesianGrid((4,1)) + cart_grid_2d_2 = CartesianGrid((3,1)) + cart_grid_3d_1 = CartesianGrid((1,4,1)) + cart_grid_3d_2 = CartesianGrid((1,3,1)) + + masked_grid_1d = [true, true, true, true, false, true, true, true] + masked_grid_2d = reshape(masked_grid_1d,8,1) + masked_grid_3d = reshape(masked_grid_1d,1,8,1) + + # Creaets a base solution to which we will compare all simulations. + lrs_base = LatticeReactionSystem(SIR_system, SIR_srs_1, masked_grid_1d) + oprob_base = ODEProblem(lrs_base, [:S => S_vals, :I => I_val, :R => R_val], (0.0, 100.0), [:α => α_vals, :β => β_val, :dS => dS_val]) + sol_base = solve(oprob_base, Tsit5(); saveat = 1.0, abstol = 1e-9, reltol = 1e-9) + + # Checks simulations for the masked grid (covering all 7 vertices, with a gap in the middle). + for grid in [masked_grid_1d, masked_grid_2d, masked_grid_3d] + # Checks where the values are vectors of length equal to the number of vertices. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_1, grid) + u0 = [:S => [S_vals[1:4]; S_vals[6:8]], :I => I_val, :R => R_val] + ps = [:α => [α_vals[1:4]; α_vals[6:8]], :β => β_val, :dS => dS_val] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps) + sol = solve(oprob, Tsit5(); saveat = 1.0, abstol = 1e-9, reltol = 1e-9) + @test sol ≈ sol_base + + # Checks where the values are arrays of size equal to the grid. + u0 = [:S => reshape(S_vals, grid_size(lrs)), :I => I_val, :R => R_val] + ps = [:α => reshape(α_vals, grid_size(lrs)), :β => β_val, :dS => dS_val] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps) + sol = solve(oprob, Tsit5(); saveat = 1.0, abstol = 1e-9, reltol = 1e-9) + @test sol ≈ sol_base + end + + # Checks simulations for the first Cartesian grids (covering vertices 1 to 4). + for grid in [cart_grid_1d_1, cart_grid_2d_1, cart_grid_3d_1] + # Checks where the values are vectors of length equal to the number of vertices. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_1, grid) + u0 = [:S => S_vals[1:4], :I => I_val, :R => R_val] + ps = [:α => α_vals[1:4], :β => β_val, :dS => dS_val] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps) + sol = solve(oprob, Tsit5(); saveat = 1.0, abstol = 1e-9, reltol = 1e-9) + @test sol[:,:] ≈ sol_base[1:12,:] + + # Checks where the values are arrays of size equal to the grid. + u0 = [:S => reshape(S_vals[1:4], grid_size(lrs)), :I => I_val, :R => R_val] + ps = [:α => reshape(α_vals[1:4], grid_size(lrs)), :β => β_val, :dS => dS_val] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps) + sol = solve(oprob, Tsit5(); saveat = 1.0, abstol = 1e-9, reltol = 1e-9) + @test sol[:,:] ≈ sol_base[1:12,:] + end + + # Checks simulations for the second Cartesian grids (covering vertices 6 to 8). + for grid in [cart_grid_1d_2, cart_grid_2d_2, cart_grid_3d_2] + # Checks where the values are vectors of length equal to the number of vertices. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_1, grid) + u0 = [:S => S_vals[6:8], :I => I_val, :R => R_val] + ps = [:α => α_vals[6:8], :β => β_val, :dS => dS_val] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps) + sol = solve(oprob, Tsit5(); saveat = 1.0, abstol = 1e-9, reltol = 1e-9) + @test sol[:,:] ≈ sol_base[13:end,:] + + # Checks where the values are arrays of size equal to the grid. + u0 = [:S => reshape(S_vals[6:8], grid_size(lrs)), :I => I_val, :R => R_val] + ps = [:α => reshape(α_vals[6:8], grid_size(lrs)), :β => β_val, :dS => dS_val] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps) + sol = solve(oprob, Tsit5(); saveat = 1.0, abstol = 1e-9, reltol = 1e-9) + @test sol[:,:] ≈ sol_base[13:end,:] + end end \ No newline at end of file From 48f27b84ab46ccf4de772dc023ef2c8d70768d0d Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 7 Feb 2024 20:28:53 -0500 Subject: [PATCH 091/446] Additional inner remake, add new functions for generating edge parameter values --- src/Catalyst.jl | 1 + .../lattice_reaction_systems.jl | 396 +++++++++++++----- .../spatial_ODE_systems.jl | 30 +- src/spatial_reaction_systems/utility.jl | 14 +- .../lattice_reaction_systems_ODEs.jl | 2 +- .../lattice_reaction_systems.jl | 72 ++++ .../lattice_reaction_systems_lattice_types.jl | 13 +- test/spatial_test_networks.jl | 4 +- 8 files changed, 391 insertions(+), 141 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 9f6da7d1d0..e235f2b40c 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -178,6 +178,7 @@ export LatticeReactionSystem export spatial_species, vertex_parameters, edge_parameters export CartesianGrid, CartesianGridReJ # (Implemented in JumpProcesses) export has_cartesian_lattice, has_masked_lattice, has_grid_lattice, has_graph_lattice, grid_dims, grid_size +export make_edge_p_values, make_directed_edge_values # Various utility functions include("spatial_reaction_systems/utility.jl") diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index a525757753..01d60cce5e 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,18 +1,23 @@ +### New Type Unions ### + +# Cartesian and masked grids share several traits, hence we declare a common (union) type for them. +const GridLattice{N,T} = Union{Array{Bool, N}, CartesianGridRej{N,T}} + ### Lattice Reaction Network Structure ### # Describes a spatial reaction network over a lattice. -# Adding the "<: MT.AbstractTimeDependentSystem" part messes up show, disabling me from creating LRSs. Should be fixed some time. +# Adding the "<: MT.AbstractTimeDependentSystem" part messes up show, disabling me from creating LRSs. Should be fixed sometime. struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem # Input values. - """The (non-spatial) reaction system within each vertexes.""" + """The (non-spatial) reaction system within each vertex.""" rs::ReactionSystem{Q} - """The spatial reactions defined between individual vertexes.""" + """The spatial reactions defined between individual vertices.""" spatial_reactions::Vector{R} """The lattice on which the (discrete) spatial system is defined.""" lattice::S # Derived values. - """The number of vertexes (compartments).""" + """The number of vertices (compartments).""" num_verts::Int64 """The number of edges.""" num_edges::Int64 @@ -23,11 +28,11 @@ struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem spat_species::Vector{BasicSymbolic{Real}} """ All parameters related to the lattice reaction system - (both those whose values are tied to vertexes and edges). + (both those whose values are tied to vertices and edges). """ parameters::Vector{BasicSymbolic{Real}} """ - Parameters which values are tied to vertexes, + Parameters which values are tied to vertices, e.g. that possibly could have unique values at each vertex of the system. """ vertex_parameters::Vector{BasicSymbolic{Real}} @@ -50,7 +55,7 @@ struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem error("The second argument must be a vector of AbstractSpatialReaction subtypes.") end - # Computes the species which are parts of spatial reactions. Also counts total number of + # Computes the species which are parts of spatial reactions. Also counts the total number of # species types. if isempty(spatial_reactions) spat_species = Vector{BasicSymbolic{Real}}[] @@ -72,7 +77,7 @@ struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem # Ensures the parameter order begins similarly to in the non-spatial ReactionSystem. ps = [parameters(rs); setdiff([edge_parameters; vertex_parameters], parameters(rs))] - # Checks that all spatial reactions are valid for this reactions system. + # Checks that all spatial reactions are valid for this reaction system. foreach(sr -> check_spatial_reaction_validity(rs, sr; edge_parameters=edge_parameters), spatial_reactions) return new{Q,R,S,T}(rs, spatial_reactions, lattice, num_verts, num_edges, num_species, @@ -80,117 +85,137 @@ struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem end end -# Creates a LatticeReactionSystem from a CartesianGrid lattice (cartesian grid). -function LatticeReactionSystem(rs, srs, lattice_in::CartesianGridRej{S,T}; diagonal_connections=false) where {S,T} +# Creates a LatticeReactionSystem from a (directed) Graph lattice (graph grid). +function LatticeReactionSystem(rs, srs, lattice::DiGraph) + num_verts = nv(lattice) + num_edges = ne(lattice) + edge_iterator = [e.src => e.dst for e in edges(lattice)] + return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator) +end +# Creates a LatticeReactionSystem from a (undirected) Graph lattice (graph grid). +LatticeReactionSystem(rs, srs, lattice::SimpleGraph) = LatticeReactionSystem(rs, srs, DiGraph(lattice)) + +# Creates a LatticeReactionSystem from a CartesianGrid lattice (cartesian grid) or a Boolean Array +# lattice (masked grid). These two are quite similar, so much code can be reused in a single interface. +function LatticeReactionSystem(rs, srs, lattice::GridLattice{N,T}; diagonal_connections=false) where {N,T} # Error checks. - (length(lattice_in.dims) > 3) && error("Grids of higher dimension than 3 is currently not supported.") + (N > 3) && error("Grids of higher dimension than 3 is currently not supported.") + + # Computes the number of vertices and edges. The two grid types (Cartesian and masked) each + # uses their own function for this. + num_verts = count_verts(lattice) + num_edges = count_edges(lattice; diagonal_connections) + + # Finds all the grid's edges. First computer `flat_to_grid_idx` which is a vector which takes + # each vertex's flat (scalar) index to its grid index (e.g. (3,5) for a 2d grid). Next compute + # `grid_to_flat_idx` which is an array (of the same size as the grid) that does the reverse conversion. + # Especially for masked grids these have non-trivial forms. + flat_to_grid_idx, grid_to_flat_idx = get_index_converters(lattice, num_verts) + # Simultaneously iterates through all vertices' flat and grid indices. Finds the (grid) indices + # of their neighbours (different approaches for the two grid types). Converts these to flat + # indices and adds the edges to `edge_iterator`. + cur_vert = 0 + g_size = grid_size(lattice) + edge_iterator = Vector{Pair{Int64,Int64}}(undef, num_edges) + for (flat_idx, grid_idx) in enumerate(flat_to_grid_idx) + for neighbour_grid_idx in get_neighbours(lattice, grid_idx, g_size; diagonal_connections) + cur_vert += 1 + edge_iterator[cur_vert] = flat_idx => grid_to_flat_idx[neighbour_grid_idx...] + end + end + + return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator) +end + + +### LatticeReactionSystem Helper Functions ### +# Note, most of these are specifically for (Cartesian or masked) grids, we call them `grid`, not `lattice`. - # Ensures that the matrix has a 3d form (used for intermediary computations only, - # the original is passed to the constructor). - lattice = CartesianGrid((lattice_in.dims..., fill(1, 3-length(lattice_in.dims))...)) +# Counts the number of vertices on a (Cartesian or masked) grid. +count_verts(grid::CartesianGridRej{N,T}) where {N,T} = prod(grid_size(grid)) +count_verts(grid::Array{Bool, N}) where {N} = count(grid) - # Counts vertexes and edges. The `num_edges` count formula counts the number of internal, side, - # edge, and corner vertexes (on the grid). The number of edges from each depends on whether diagonal - # connections are allowed. The formula holds even if l, m, and/or n are 1. - l,m,n = lattice.dims - num_verts = l * m * n +# Counts and edges on a Cartesian grid. The formula counts the number of internal, side, edge, and +# corner vertices (on the grid). `l,m,n = grid_dims(grid),1,1` ensures that "extra" dimensions get +# length 1. The formula holds even if one or more of l, m, and n are 1. +function count_edges(grid::CartesianGridRej{N,T}; diagonal_connections = false) where {N,T} + l,m,n = grid_size(grid)...,1,1 (ni, ns, ne, nc) = diagonal_connections ? (26,17,11,7) : (6,5,4,3) - num_edges = ni*(l-2)*(m-2)*(n-2) + # Edges from internal vertexes. - ns*(2(l-2)*(m-2) + 2(l-2)*(n-2) + 2(m-2)*(n-2)) + # Edges from side vertexes. - ne*(4(l-2) + 4(m-2) + 4(n-2)) + # Edges from edge vertexes. - nc*8 # Edges from corner vertexes. - - # Creates an iterator over all edges. - # Currently creates a full vector. Future version might be for efficient. - edge_iterator = Vector{Pair{Int64,Int64}}(undef, num_edges) - # Loops through, simultaneously, the coordinates of each position in the grid, as well as that - # coordinate's (scalar) flat index. For each grid point, loops through all potential neighbours. - indices = [(L, M, N) for L in 1:l, M in 1:m, N in 1:n] - flat_indices = 1:num_verts - next_vert = 0 - for ((L, M, N), idx) in zip(indices, flat_indices) - for LL in max(L - 1, 1):min(L + 1, l), - MM in max(M - 1, 1):min(M + 1, m), - NN in max(N - 1, 1):min(N + 1, n) - - # Which (LL,MM,NN) indexes are valid neighbours depends on whether diagonal connects are permitted. - !diagonal_connections && (count([L==LL, M==MM, N==NN]) == 2) || continue - diagonal_connections && (L==LL) && (M==MM) && (N==NN) && continue - - # Computes the neighbour's flat (scalar) index. Add the edge to edge_iterator. - neighbour_idx = LL + (MM - 1) * l + (NN - 1) * m * l - edge_iterator[next_vert += 1] = (idx => neighbour_idx) - end + num_edges = ni*(l-2)*(m-2)*(n-2) + # Edges from internal vertices. + ns*(2(l-2)*(m-2) + 2(l-2)*(n-2) + 2(m-2)*(n-2)) + # Edges from side vertices. + ne*(4(l-2) + 4(m-2) + 4(n-2)) + # Edges from edge vertices. + nc*8 # Edges from corner vertices. + return num_edges +end + +# Counts and edges on a masked grid. Does so by looping through all the vertices of the grid, +# finding their neighbours, and updating the edge count accordingly. +function count_edges(grid::Array{Bool, N}; diagonal_connections = false) where {N} + g_size = grid_size(grid) + num_edges = 0 + for grid_idx in get_grid_indices(grid) + grid[grid_idx] || continue + num_edges += length(get_neighbours(grid, Tuple(grid_idx), g_size; diagonal_connections)) end + return num_edges +end - return LatticeReactionSystem(rs, srs, lattice_in, num_verts, num_edges, edge_iterator) +# For a (1d, 2d, or 3d) (Cartesian or masked) grid, returns a vector and an array, permitting the +# conversion between a vertex's flat (scalar) and grid indices. E.g. for a 2d grid, if grid point (3,2) +# corresponds to the fifth vertex, then `flat_to_grid_idx[5] = (3,2)` and `grid_to_flat_idx[3,2] = 5`. +function get_index_converters(grid::GridLattice{N,T}, num_verts) where {N,T} + flat_to_grid_idx = Vector{typeof(grid_size(grid))}(undef, num_verts) + grid_to_flat_idx = Array{Int64}(undef, grid_size(grid)) + + # Loops through the flat and grid indices simultaneously, adding them to their respective converters. + cur_flat_idx = 0 + for grid_idx in get_grid_indices(grid) + # For a masked grid, grid points with `false` values are skipped. + (grid isa Array{Bool}) && (!grid[grid_idx]) && continue + + cur_flat_idx += 1 + flat_to_grid_idx[cur_flat_idx] = grid_idx + grid_to_flat_idx[grid_idx] = cur_flat_idx + end + return flat_to_grid_idx, grid_to_flat_idx end -# Creates a LatticeReactionSystem from a Boolean Array lattice (masked grid). -function LatticeReactionSystem(rs, srs, lattice_in::Array{Bool, T}; diagonal_connections=false) where {T} - # Error checks. - dims = size(lattice_in) - (length(dims) > 3) && error("Grids of higher dimension than 3 is currently not supported.") - - # Ensures that the matrix has a 3d form (used for intermediary computations only, - # the original is passed to the constructor). - lattice = reshape(lattice_in, [dims...; fill(1, 3-length(dims))]...) - - # Counts vertexes (edges have to be counted after the iterator have been created). - num_verts = count(lattice) - - - # Makes a template matrix to store each vertex's index. The matrix is 0 where there is no vertex. - # The template is used in the next step. - idx_matrix = fill(0, size(lattice_in)) - cur_vertex_idx = 0 - for flat_idx in 1:length(lattice) - if lattice[flat_idx] - idx_matrix[flat_idx] = (cur_vertex_idx += 1) - end +# For a vertex's grid index, and a lattice, returns the grid indices of all its (valid) neighbours. +function get_neighbours(grid::GridLattice{N,T}, grid_idx, g_size; diagonal_connections = false) where {N,T} + # Depending on the grid's dimension, find all potential neighbours. + if grid_dims(grid) == 1 + potential_neighbours = [grid_idx .+ (i) for i in -1:1] + elseif grid_dims(grid) == 2 + potential_neighbours = [grid_idx .+ (i,j) for i in -1:1 for j in -1:1] + else + potential_neighbours = [grid_idx .+ (i,j,k) for i in -1:1 for j in -1:1 for k in -1:1] end - # Creates an iterator over all edges. A vector with pairs of each edge's source to its destination. - edge_iterator = Vector{Pair{Int64,Int64}}() - # Loops through, the coordinates of each position in the grid. - # For each grid point, loops through all potential neighbours and adds edges to edge_iterator. - l, m, n = size(lattice) - indices = [(L, M, N) for L in 1:l, M in 1:m, N in 1:n] - for (L, M, N) in indices - # Ensures that we are in a valid lattice point. - lattice[L,M,N] || continue - for LL in max(L - 1, 1):min(L + 1, l), - MM in max(M - 1, 1):min(M + 1, m), - NN in max(N - 1, 1):min(N + 1, n) - - # Ensures that the neighbour is a valid lattice point. - lattice[LL,MM,NN] || continue - - # Which (LL,MM,NN) indexes are valid neighbours depends on whether diagonal connects are permitted. - !diagonal_connections && (count([L==LL, M==MM, N==NN]) == 2) || continue - diagonal_connections && (L==LL) && (M==MM) && (N==NN) && continue - - # Computes the neighbour's scalar index. Add that connection to `edge_iterator`. - push!(edge_iterator, idx_matrix[L,M,N] => idx_matrix[LL,MM,NN]) - end + # Depending on whether diagonal connections are used or not, find valid neighbours. + if diagonal_connections + filter!(n_idx -> n_idx !== grid_idx, potential_neighbours) + else + filter!(n_idx -> count(n_idx .== grid_idx) == (length(g_size) - 1), potential_neighbours) end - num_edges = length(edge_iterator) - return LatticeReactionSystem(rs, srs, lattice_in, num_verts, num_edges, edge_iterator) + # Removes neighbours outside of the grid, and returns the full list. + return filter(n_idx -> is_valid_grid_point(grid, n_idx, g_size), potential_neighbours) end -# Creates a LatticeReactionSystem from a (directed) Graph lattice (graph grid). -function LatticeReactionSystem(rs, srs, lattice::DiGraph) - num_verts = nv(lattice) - num_edges = ne(lattice) - edge_iterator = [e.src => e.dst for e in edges(lattice)] - return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator) +# Checks if a grid index corresponds to a valid grid point. First, check that each dimension of the +# index is within the grid's bounds. Next, perform an extra check for the masked grid. +function is_valid_grid_point(grid::GridLattice{N,T}, grid_idx, g_size) where {N,T} + all(0 < g_idx <= dim_leng for (g_idx, dim_leng) in zip(grid_idx, g_size)) || return false + return (grid isa Array{Bool}) ? grid[grid_idx...] : true end -# Creates a LatticeReactionSystem from a (undirected) Graph lattice (graph grid). -LatticeReactionSystem(rs, srs, lattice::SimpleGraph) = LatticeReactionSystem(rs, srs, DiGraph(lattice)) +# Gets an iterator over a grid's grid indices. Separate function so we can handle the two grid types +# separately (i.e. not calling `CartesianIndices(ones(grid_size(grid)))` unnecessarily for masked grids). +get_grid_indices(grid::CartesianGridRej{N,T}) where {N,T} = CartesianIndices(ones(grid_size(grid))) +get_grid_indices(grid::Array{Bool, N}) where {N} = CartesianIndices(grid) -### Lattice ReactionSystem Getters ### + +### LatticeReactionSystem Getters ### # Get all species. species(lrs::LatticeReactionSystem) = unique([species(lrs.rs); lrs.spat_species]) @@ -199,14 +224,11 @@ spatial_species(lrs::LatticeReactionSystem) = lrs.spat_species # Get all parameters. ModelingToolkit.parameters(lrs::LatticeReactionSystem) = lrs.parameters -# Get all parameters whose values are tied to vertexes (compartments). +# Get all parameters whose values are tied to vertices (compartments). vertex_parameters(lrs::LatticeReactionSystem) = lrs.vertex_parameters # Get all parameters whose values are tied to edges (adjacencies). edge_parameters(lrs::LatticeReactionSystem) = lrs.edge_parameters -# Gets the lrs name (same as rs name). -ModelingToolkit.nameof(lrs::LatticeReactionSystem) = nameof(lrs.rs) - # Checks if a lattice reaction system is a pure (linear) transport reaction system. is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReaction, lrs.spatial_reactions) @@ -216,7 +238,7 @@ is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReac Returns `true` if `lrs` was created using a cartesian grid lattice (e.g. created via `CartesianGrid(5,5)`). Otherwise, returns `false`. """ -has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa CartesianGridRej{S,T} where {S,T} +has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa CartesianGridRej{N,T} where {N,T} """ has_masked_lattice(lrs::LatticeReactionSystem) @@ -224,7 +246,7 @@ has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa CartesianGri Returns `true` if `lrs` was created using a masked grid lattice (e.g. created via `[true true; true false]`). Otherwise, returns `false`. """ -has_masked_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa Array{Bool, T} where T +has_masked_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa Array{Bool, N} where N """ has_grid_lattice(lrs::LatticeReactionSystem) @@ -247,11 +269,10 @@ has_graph_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa SimpleDiGraph Returns the size of `lrs`'s lattice (only if it is a cartesian or masked grid lattice). E.g. for a lattice `CartesianGrid(4,6)`, `(4,6)` is returned. """ -function grid_size(lrs::LatticeReactionSystem) - has_cartesian_lattice(lrs) && (return lrs.lattice.dims) - has_masked_lattice(lrs) && (return size(lrs.lattice)) - error("Grid size is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") -end +grid_size(lrs::LatticeReactionSystem) = grid_size(lrs.lattice) +grid_size(lattice::CartesianGridRej{N,T}) where {N,T} = lattice.dims +grid_size(lattice::Array{Bool, N}) where {N} = size(lattice) +grid_size(lattice::Graph) = error("Grid size is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") """ grid_dims(lrs::LatticeReactionSystem) @@ -259,4 +280,151 @@ end Returns the number of dimensions of `lrs`'s lattice (only if it is a cartesian or masked grid lattice). The output is either `1`, `2`, or `3`. """ -grid_dims(lrs::LatticeReactionSystem) = length(grid_size(lrs)) \ No newline at end of file +grid_dims(lrs::LatticeReactionSystem) = grid_dims(lrs.lattice) +grid_dims(lattice::GridLattice{N,T}) where {N,T} = return N +grid_dims(lattice::Graph) = error("Grid dimensions is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") + + +### Generic Getters ### + +# Gets the lrs name (same as rs name). +ModelingToolkit.nameof(lrs::LatticeReactionSystem) = nameof(lrs.rs) + + +### Edge Parameter Value Generators ### + +""" + make_edge_p_values(lrs::LatticeReactionSystem, make_edge_p_value::Function) + +Generates edge parameter values for a lattice reaction system. Only work for (Cartesian or masked) +grid lattices (without diagonal adjacencies). + +Input: +- `lrs`: The lattice reaction system for which values should be generated. + - `make_edge_p_value`: a function describing a rule for generating the edge parameter values. + +Output: + - `ep_vals`: A sparse matrix of size (num_verts,num_verts) (where num_verts is the number of + vertices in `lrs`). Here, `eps[i,j]` is filled only if there is an edge going from vertex i to + vertex j. The value of `eps[i,j]` is determined by `make_edge_p_value`. + +Here, `make_edge_p_value` should take two arguments, `src_vert` and `dst_vert`, which correspond to +the grid indices of an edge's source and destination vertices, respectively. It outputs a single value, +which is the value assigned to that edge. + +Example: + In the following example, we assign the value `0.1` to all edges, except for the one leading from + vertex (1,1) to vertex (1,2), to which we assign the value `1.0`. +```julia +using Catalyst +rn = @reaction_network begin + (p,d), 0 <--> X +end +tr = @transport_reaction D X +lattice = CartesianGrid((5,5)) +lrs = LatticeReactionSystem(rn, [tr], lattice) + +function make_edge_p_value(src_vert, dst_vert) + if src_vert == (1,1) && dst_vert == (1,2) + return 1.0 + else + return 0.1 + end +end + +D_vals = make_edge_p_values(lrs, make_edge_p_value) +``` +""" +function make_edge_p_values(lrs::LatticeReactionSystem, make_edge_p_value::Function, ) + if has_graph_lattice(lrs) + error("The `make_edge_p_values` function is only meant for lattices with (Cartesian or masked) grid structures. It cannot be applied to graph lattices.") + end + + # Makes the flat to index grid converts. Predeclared the edge parameter value sparse matrix. + flat_to_grid_idx = get_index_converters(lrs.lattice, lrs.num_verts)[1] + values = spzeros(lrs.num_verts,lrs.num_verts) + + # Loops through all edges, and applies the value function to these. + for e in lrs.edge_iterator + # This extra step is needed to ensure that `0` is stored if make_edge_p_value yields a 0. + # If not, then the sparse matrix simply becomes empty in that position. + values[e[1],e[2]] = eps() + + values[e[1],e[2]] = make_edge_p_value(flat_to_grid_idx[e[1]], flat_to_grid_idx[e[2]]) + end + + return values +end + +""" + make_directed_edge_values(lrs::LatticeReactionSystem, x_vals::Tuple{T,T}, y_vals::Tuple{T,T} = (undef,undef), + z_vals::Tuple{T,T} = (undef,undef)) where {T} + +Generates edge parameter values for a lattice reaction system. Only work for (Cartesian or masked) +grid lattices (without diagonal adjacencies). Each dimension (x, and possibly y and z), and +direction has assigned its own constant edge parameter value. + +Input: + - `lrs`: The lattice reaction system for which values should be generated. + - `x_vals::Tuple{T,T}`: The values in the increasing (from a lower x index to a higher x index) + and decreasing (from a higher x index to a lower x index) direction along the x dimension. + - `y_vals::Tuple{T,T}`: The values in the increasing and decreasing direction along the y dimension. + Should only be used for 2 and 3-dimensional grids. + - `z_vals::Tuple{T,T}`: The values in the increasing and decreasing direction along the z dimension. + Should only be used for 3-dimensional grids. + +Output: + - `ep_vals`: A sparse matrix of size (num_verts,num_verts) (where num_verts is the number of + vertices in `lrs`). Here, `eps[i,j]` is filled only if there is an edge going from vertex i to + vertex j. The value of `eps[i,j]` is determined by the `x_vals`, `y_vals`, and `z_vals` Tuples, + and vertices i and j's relative position in the grid. + +It should be noted that two adjacent vertices will always be different in exactly a single dimension +(x, y, or z). The corresponding tuple determines which value is assigned. + +Example: + In the following example, we wish to have diffusion in the x dimension, but a constant flow from + low y values to high y values (so not transportation from high to low y). We achieve it in the + following manner: +```julia +using Catalyst +rn = @reaction_network begin + (p,d), 0 <--> X +end +tr = @transport_reaction D X +lattice = CartesianGrid((5,5)) +lrs = LatticeReactionSystem(rn, [tr], lattice) + +D_vals = make_directed_edge_values(lrs, (0.1, 0.1), (0.1, 0.0)) +``` +Here, since we have a 2d grid, we only provide the first two Tuples to `make_directed_edge_values`. +""" +function make_directed_edge_values(lrs::LatticeReactionSystem, x_vals::Tuple{T,T}, y_vals::Union{Nothing,Tuple{T,T}} = nothing, + z_vals::Union{Nothing,Tuple{T,T}} = nothing) where {T} + # Error checks. + if has_graph_lattice(lrs) + error("The `make_directed_edge_values` function is only meant for lattices with (Cartesian or masked) grid structures. It cannot be applied to graph lattices.") + end + if count(!isnothing(flow) for flow in [x_vals, y_vals, z_vals]) != grid_dims(lrs) + error("You must provide flows in the same number of dimensions as your lattice has dimensions. The lattice have $(grid_dims(lrs)), and flows where provided for $(count(isnothing(flow) for flow in [x_vals, y_vals, z_vals])) dimensions.") + end + + # Declares a function that assigns the correct flow value for a given edge. + function directed_vals_func(src_vert, dst_vert) + if count(src_vert .== dst_vert) != (grid_dims(lrs) - 1) + error("The `make_directed_edge_values` function can only be applied to lattices with rigid (non-diagonal) grid structure. It is being evaluated for the edge from $(src_vert) to $(dst_vert), which does not seem directly adjacent on a grid.") + elseif src_vert[1] != dst_vert[1] + return (src_vert[1] < dst_vert[1]) ? x_vals[1] : x_vals[2] + elseif src_vert[2] != dst_vert[2] + return (src_vert[2] < dst_vert[2]) ? y_vals[1] : y_vals[2] + elseif src_vert[3] != dst_vert[3] + return (src_vert[3] < dst_vert[3]) ? z_vals[1] : z_vals[2] + else + error("Problem when evaluating adjacency type for the edge from $(src_vert) to $(dst_vert).") + end + end + + # Uses the make_edge_p_values function to compute the output. + return make_edge_p_values(lrs, directed_vals_func) +end + diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 19b7b7e05e..98256e5e78 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -8,7 +8,7 @@ struct LatticeTransportODEf{S,T} num_verts::Int64 """The number of species.""" num_species::Int64 - """The values of the parameters that are tied to vertexes.""" + """The values of the parameters that are tied to vertices.""" vert_ps::Vector{Vector{T}} """ Vector for storing temporary values. Repeatedly during simulations, we need to retrieve the @@ -41,7 +41,7 @@ struct LatticeTransportODEf{S,T} """ t_rate_idx_types::Vector{Bool} """ - A matrix, NxM, where N is the number of species with transportation and M is the number of vertexes. + A matrix, NxM, where N is the number of species with transportation and M is the number of vertices. Each value is the total rate at which that species leaves that vertex (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D). """ @@ -86,7 +86,7 @@ struct LatticeTransportODEjac{R,S,T} num_verts::Int64 """The number of species.""" num_species::Int64 - """The values of the parameters that are tied to vertexes.""" + """The values of the parameters that are tied to vertices.""" vert_ps::Vector{Vector{S}} """ Vector for storing temporary values. Repeatedly during simulations, we need to retrieve the @@ -140,7 +140,7 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = symmap_to_varmap(lrs, p_in) # Converts u0 and p to their internal forms. - # u0 is simply a vector with all the species' initial condition values across all vertexes. + # u0 is simply a vector with all the species' initial condition values across all vertices. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. u0 = lattice_process_u0(u0_in, species(lrs), lrs) # vert_ps and `edge_ps` are vector maps, taking each parameter's Symbolics representation to its value(s). @@ -212,24 +212,24 @@ end function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, transport_rates::Vector{Pair{Int64,SparseMatrixCSC{T, Int64}}}, lrs::LatticeReactionSystem; set_nonzero = false) where {T} - # Finds the indexes of both the transport species, + # Finds the indices of both the transport species, # and the species with transport only (that is, with no non-spatial dynamics but with spatial dynamics). trans_species = [tr[1] for tr in transport_rates] trans_only_species = filter(s_idx -> !Base.isstored(ns_jac_prototype, s_idx, s_idx), trans_species) - # Finds the indexes of all terms in the non-spatial jacobian. + # Finds the indices of all terms in the non-spatial jacobian. ns_jac_prototype_idxs = findnz(ns_jac_prototype) ns_i_idxs = ns_jac_prototype_idxs[1] ns_j_idxs = ns_jac_prototype_idxs[2] - # Prepares vectors to store i and j indexes of Jacobian entries. + # Prepares vectors to store i and j indices of Jacobian entries. idx = 1 num_entries = lrs.num_verts * length(ns_i_idxs) + lrs.num_edges * (length(trans_only_species) + length(trans_species)) i_idxs = Vector{Int}(undef, num_entries) j_idxs = Vector{Int}(undef, num_entries) - # Indexes of elements caused by non-spatial dynamics. + # Indices of elements caused by non-spatial dynamics. for vert in 1:lrs.num_verts for n in 1:length(ns_i_idxs) i_idxs[idx] = get_index(vert, ns_i_idxs[n], lrs.num_species) @@ -238,7 +238,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, end end - # Indexes of elements caused by spatial dynamics. + # Indices of elements caused by spatial dynamics. for e in lrs.edge_iterator # Indexes due to terms for a species leaving its source vertex (but does not have # non-spatial dynamics). If the non-spatial Jacobian is fully dense, these would already @@ -281,7 +281,7 @@ end function (f_func::LatticeTransportODEf)(du, u, p, t) # Updates for non-spatial reactions. for vert_i in 1:(f_func.num_verts) - # Gets the indexes of all the species at vertex i. + # Gets the indices of all the species at vertex i. idxs = get_indexes(vert_i, f_func.num_species) # Updates the work vector to contain the vertex parameter values for vertex vert_i. @@ -294,15 +294,15 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) # s_idx is the species index among transport species, s is the index among all species. # rates are the species' transport rates. for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) - # Rate for leaving source vertex vert_i. - for vert_i in 1:(f_func.num_verts) - idx_src = get_index(vert_i, s, f_func.num_species) + # Rate for leaving source vertex vert_i. + for vert_i in 1:(f_func.num_verts) + idx_src = get_index(vert_i, s, f_func.num_species) du[idx_src] -= f_func.leaving_rates[s_idx, vert_i] * u[idx_src] end # Add rates for entering a destination vertex via an incoming edge. - for e in f_func.edge_iterator + for e in f_func.edge_iterator idx_src = get_index(e[1], s, f_func.num_species) - idx_dst = get_index(e[2], s, f_func.num_species) + idx_dst = get_index(e[2], s, f_func.num_species) du[idx_dst] += get_transport_rate(s_idx, f_func, e) * u[idx_src] end end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 6cfdafe580..f5b68091ad 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -23,7 +23,7 @@ function lattice_process_u0(u0_in, u0_syms::Vector{BasicSymbolic{Real}}, lrs::La # Species' initial condition values can be given in different forms (also depending on the lattice). # This converts each species's values to a Vector. In it, for species with uniform initial conditions, # it holds that value only. For spatially heterogeneous initial conditions, - # the vector has the same length as the number of vertexes (storing one value for each). + # the vector has the same length as the number of vertices (storing one value for each). u0 = vertex_value_map(u0, lrs) # Converts the initial condition to a single Vector (with one value for each species and vertex). @@ -101,7 +101,7 @@ function vertex_value_form(values, lrs::LatticeReactionSystem, sym::BasicSymboli # For the case where the i'th value of the vector corresponds to the value in the i'th vertex. # This is the only (non-uniform) case possible for graph grids. if (length(values) != lrs.num_verts) - error("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertexes ($(lrs.num_verts)).") + error("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertices ($(lrs.num_verts)).") end return values end @@ -111,13 +111,13 @@ function vertex_value_form(values, lrs::LatticeReactionSystem, sym::BasicSymboli end # Converts values to the correct vector form for a Cartesian grid lattice. -function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::CartesianGridRej{S,T}, - sym::BasicSymbolic{Real}) where {S,T} +function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::CartesianGridRej{N,T}, + sym::BasicSymbolic{Real}) where {N,T} if size(values) != lattice.dims error("The values for $sym did not have the same format as the lattice. Expected a $(lattice.dims) array, got one of size $(size(values))") end if (length(values) != num_verts) - error("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertexes ($(num_verts)).") + error("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertices ($(num_verts)).") end return [values[flat_idx] for flat_idx in 1:num_verts] end @@ -140,7 +140,7 @@ function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::Arr # Checks that the correct number of values was provided, and returns the values. if (length(return_values) != num_verts) - error("You have provided ($(length(return_values))) values for $sym. This is not equal to the number of vertexes ($(num_verts)).") + error("You have provided ($(length(return_values))) values for $sym. This is not equal to the number of vertices ($(num_verts)).") end return return_values end @@ -242,7 +242,7 @@ end # Gets the index in the u array of species s in vertex vert (when there are num_species species). get_index(vert::Int64, s::Int64, num_species::Int64) = (vert - 1) * num_species + s -# Gets the indexes in the u array of all species in vertex vert (when there are num_species species). +# Gets the indices in the u array of all species in vertex vert (when there are num_species species). get_indexes(vert::Int64, num_species::Int64) = ((vert - 1) * num_species + 1):(vert * num_species) # Returns the value of a parameter in an edge. For vertex parameters, use their values in the source. diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index 2acdd00e24..a2052ffb5f 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -386,7 +386,7 @@ let end # Tests that input parameter and u0 values can be given using different types of input for 2d lattices. -# Tries both for cartesian and masked (where all vertexes are `true`). +# Tries both for cartesian and masked (where all vertices are `true`). # Tries for Vector, Tuple, and Dictionary inputs. let for lattice in [CartesianGrid((4,3)), fill(true, 4, 3)] diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index ed29142640..1a729563c3 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -324,3 +324,75 @@ let end end +### Tests Edge Value Computation Helper Functions ### + +# Checks that all species ends up in the correct place in in a pure flow system (checking various dimensions). +let + # Prepares a system with a single species which is transported only. + rn = @reaction_network begin + @species X(t) + end + n = 5 + tr = @transport_reaction D X + tspan = (0.0, 10000.0) + u0 = [:X => 1.0] + + # Checks the 1d case. + lrs = LatticeReactionSystem(rn, [tr], CartesianGrid(n)) + ps = [:D => make_directed_edge_values(lrs, (10.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test isapprox(solve(oprob, Tsit5())[end][5], n, rtol=1e-6) + + # Checks the 2d case (both with 1d and 2d flow). + lrs = LatticeReactionSystem(rn, [tr], CartesianGrid((n,n))) + + ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (0.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test all(isapprox.(solve(oprob, Tsit5())[end][5:5:25], n, rtol=1e-6)) + + ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test isapprox(solve(oprob, Tsit5())[end][25], n^2, rtol=1e-6) + + # Checks the 3d case (both with 1d and 2d flow). + lrs = LatticeReactionSystem(rn, [tr], CartesianGrid((n,n,n))) + + ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (0.0, 0.0), (0.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test all(isapprox.(solve(oprob, Tsit5())[end][5:5:125], n, rtol=1e-6)) + + ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0), (0.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test all(isapprox.(solve(oprob, Tsit5())[end][25:25:125], n^2, rtol=1e-6)) + + ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0), (1.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test isapprox(solve(oprob, Tsit5())[end][125], n^3, rtol=1e-6) +end + +# Checks that erroneous input yields errors. +let + rn = @reaction_network begin + (p,d), 0 <--> X + end + tr = @transport_reaction D X + tspan = (0.0, 10000.0) + make_edge_p_value(src_vert, dst_vert) = rand() + + # Graph grids. + lrs = LatticeReactionSystem(rn, [tr], path_graph(5)) + @test_throws Exception make_edge_p_values(lrs, make_edge_p_value,) + @test_throws Exception make_directed_edge_values(lrs, (1.0, 0.0)) + + # Wrong dimensions to `make_directed_edge_values`. + lrs_1d = LatticeReactionSystem(rn, [tr], CartesianGrid(5)) + lrs_2d = LatticeReactionSystem(rn, [tr], fill(true,5,5)) + lrs_3d = LatticeReactionSystem(rn, [tr], CartesianGrid((5,5,5))) + + @test_throws Exception make_directed_edge_values(lrs_1d, (1.0, 0.0), (1.0, 0.0)) + @test_throws Exception make_directed_edge_values(lrs_1d, (1.0, 0.0), (1.0, 0.0), (1.0, 0.0)) + @test_throws Exception make_directed_edge_values(lrs_2d, (1.0, 0.0)) + @test_throws Exception make_directed_edge_values(lrs_2d, (1.0, 0.0), (1.0, 0.0), (1.0, 0.0)) + @test_throws Exception make_directed_edge_values(lrs_3d, (1.0, 0.0)) + @test_throws Exception make_directed_edge_values(lrs_3d, (1.0, 0.0), (1.0, 0.0)) +end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl index b3523523ab..0f30979dfc 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl @@ -51,6 +51,15 @@ let @test grid_dims(masked_2d_lrs) == 2 @test grid_dims(masked_3d_lrs) == 3 @test_throws Exception grid_dims(graph_lrs) + + # Checks grid sizes. + @test grid_size(cartesian_1d_lrs) == (5,) + @test grid_size(cartesian_2d_lrs) == (5,5) + @test grid_size(cartesian_3d_lrs) == (5,5,5) + @test grid_size(masked_1d_lrs) == (5,) + @test grid_size(masked_2d_lrs) == (5,5) + @test grid_size(masked_3d_lrs) == (5,5,5) + @test_throws Exception grid_size(graph_lrs) end # Checks grid dimensions for 2d and 3d grids where some dimension is equal to 1. @@ -162,9 +171,9 @@ let end end -# For a system which is a single ine of vertexes: (O-O-O-O-X-O-O-O), ensures that different simulations +# For a system which is a single ine of vertices: (O-O-O-O-X-O-O-O), ensures that different simulations # approach yield the same result. Checks for both masked and Cartesian grid. For both, simulates where -# initial conditions/vertex parameters are either a vector of the same length as the number of vertexes (7), +# initial conditions/vertex parameters are either a vector of the same length as the number of vertices (7), # Or as the grid. Here, we try grid sizes (n), (1,n), and (1,n,1) (so the same grid, but in 1d, 2d, and 3d). # For the Cartesian grid, we cannot represent the gap, so we make simulations both for length-4 and # length-3 grids. diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index ef1836958e..2dc1b6dea4 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -13,10 +13,10 @@ rand_v_vals(lrs::LatticeReactionSystem) = rand_v_vals(lrs.lattice) function rand_v_vals(grid::DiGraph) return rand(rng, nv(grid)) end -function rand_v_vals(grid::Catalyst.CartesianGridRej{S,T}) where {S,T} +function rand_v_vals(grid::Catalyst.CartesianGridRej{N,T}) where {N,T} return rand(rng, grid.dims) end -function rand_v_vals(grid::Array{Bool, T}) where {T} +function rand_v_vals(grid::Array{Bool, N}) where {N} return rand(rng, size(grid)) end From 944a41e0c97c80c5e432212e7fa7ab4ca6e9136f Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 8 Feb 2024 10:25:37 -0500 Subject: [PATCH 092/446] up --- .../lattice_reaction_systems.jl | 27 ++++++++++++++++++- test/spatial_test_networks.jl | 8 +++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 1a729563c3..8d08bfdaa2 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,7 +1,7 @@ ### Preparations ### # Fetch packages. -using Catalyst, Graphs, Test +using Catalyst, Graphs, OrdinaryDiffEq, Test # Fetch test networks. include("../spatial_test_networks.jl") @@ -326,6 +326,31 @@ end ### Tests Edge Value Computation Helper Functions ### +# Checks that computes the correct values across various types of grids. +let + # Prepares the model and the function that determines the edge values. + rn = @reaction_network begin + (p,d), 0 <--> X + end + tr = @transport_reaction D X + function make_edge_p_value(src_vert, dst_vert) + return prod(src_vert) + prod(dst_vert) + end + + # Loops through a variety of grids, checks that `make_edge_p_values` yields the correct values. + for grid in [small_1d_cartesian_grid, small_2d_cartesian_grid, small_3d_cartesian_grid, + small_1d_masked_grid, small_2d_masked_grid, small_3d_masked_grid, + random_1d_masked_grid, random_2d_masked_grid, random_3d_masked_grid] + lrs = LatticeReactionSystem(rn, [tr], grid) + flat_to_grid_idx = Catalyst.get_index_converters(lrs.lattice, lrs.num_verts)[1] + edge_values = make_edge_p_values(lrs, make_edge_p_value) + + for e in lrs.edge_iterator + @test edge_values[e[1], e[2]] == make_edge_p_value(flat_to_grid_idx[e[1]], flat_to_grid_idx[e[2]]) + end + end +end + # Checks that all species ends up in the correct place in in a pure flow system (checking various dimensions). let # Prepares a system with a single species which is transported only. diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index 2dc1b6dea4..3b87b6de60 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -24,7 +24,7 @@ rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x function rand_e_vals(lrs::LatticeReactionSystem) e_vals = spzeros(lrs.num_verts, lrs.num_verts) for e in lrs.edge_iterator - e_vals[e[1], e[2]] = rand() + e_vals[e[1], e[2]] = rand(rng) end return e_vals end @@ -201,9 +201,9 @@ large_1d_masked_grid = fill(true, 5) large_2d_masked_grid = fill(true, 5, 5) large_3d_masked_grid = fill(true, 5, 5, 5) -random_1d_masked_grid = rand([true, true, true, false], 10) -random_2d_masked_grid = rand([true, true, true, false], 10, 10) -random_3d_masked_grid = rand([true, true, true, false], 10, 10, 10) +random_1d_masked_grid = rand(rng, [true, true, true, false], 10) +random_2d_masked_grid = rand(rng, [true, true, true, false], 10, 10) +random_3d_masked_grid = rand(rng, [true, true, true, false], 10, 10, 10) # Graph - grids. very_small_2d_graph_grid = Graphs.grid([2, 2]) From 475a95e0a0066635f9e900b440e527f56b82e4d6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 19 Apr 2024 10:57:48 -0400 Subject: [PATCH 093/446] save progress --- .../lattice_reaction_systems.jl | 72 ++++++++++++++++--- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 01d60cce5e..ef9a9584dd 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -6,8 +6,7 @@ const GridLattice{N,T} = Union{Array{Bool, N}, CartesianGridRej{N,T}} ### Lattice Reaction Network Structure ### # Describes a spatial reaction network over a lattice. -# Adding the "<: MT.AbstractTimeDependentSystem" part messes up show, disabling me from creating LRSs. Should be fixed sometime. -struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem +struct LatticeReactionSystem{Q,R,S,T} <: MT.AbstractTimeDependentSystem # Input values. """The (non-spatial) reaction system within each vertex.""" rs::ReactionSystem{Q} @@ -54,6 +53,21 @@ struct LatticeReactionSystem{Q,R,S,T} # <: MT.AbstractTimeDependentSystem if !(R <: AbstractSpatialReaction) error("The second argument must be a vector of AbstractSpatialReaction subtypes.") end + if !isempty(MT.get_systems(rs)) + error("A non-flattened (hierarchical) `ReactionSystem` was used as input. `LatticeReactionSystem`s can only be based on non-hierarchical `ReactionSystem`s.") + end + if length(species(rs)) != length(states(rs)) + error("The `ReactionSystem` used as input contain variable unknowns (in addition to species unknowns). This is not permitted (the input `ReactionSystem` must contain species unknowns only).") + end + if length(reactions(rs)) != length(equations(rs)) + error("The `ReactionSystem` used as input contain equations (in addition to reactions). This is not permitted.") + end + if !isempty(MT.continuous_events(rs)) || !isempty(MT.discrete_events(rs)) + @warn "The `ReactionSystem` used as input to `LatticeReactionSystem contain events. These will be ignored in any simulations based on the created `LatticeReactionSystem`." + end + if !isempty(observed(rs)) + @warn "The `ReactionSystem` used as input to `LatticeReactionSystem contain observables. It will not be possible to access these from the created `LatticeReactionSystem`." + end # Computes the species which are parts of spatial reactions. Also counts the total number of # species types. @@ -215,15 +229,11 @@ get_grid_indices(grid::CartesianGridRej{N,T}) where {N,T} = CartesianIndices(one get_grid_indices(grid::Array{Bool, N}) where {N} = CartesianIndices(grid) -### LatticeReactionSystem Getters ### +### LatticeReactionSystem-specific Getters ### -# Get all species. -species(lrs::LatticeReactionSystem) = unique([species(lrs.rs); lrs.spat_species]) # Get all species that may be transported. spatial_species(lrs::LatticeReactionSystem) = lrs.spat_species -# Get all parameters. -ModelingToolkit.parameters(lrs::LatticeReactionSystem) = lrs.parameters # Get all parameters whose values are tied to vertices (compartments). vertex_parameters(lrs::LatticeReactionSystem) = lrs.vertex_parameters # Get all parameters whose values are tied to edges (adjacencies). @@ -285,11 +295,53 @@ grid_dims(lattice::GridLattice{N,T}) where {N,T} = return N grid_dims(lattice::Graph) = error("Grid dimensions is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") -### Generic Getters ### +### Catalyst-based Getters ### + +# Get all species. +species(lrs::LatticeReactionSystem) = unique([species(lrs.rs); lrs.spat_species]) + +# Generic ones (simply forwards call to the non-spatial system). +reactions(lrs::LatticeReactionSystem) = reactions(lrs.rs) + +### ModelingToolkit-based Getters ### + +# Get all parameters. +MT.parameters(lrs::LatticeReactionSystem) = lrs.parameters + +# Generic ones (simply forwards call to the non-spatial system). +MT.nameof(lrs::LatticeReactionSystem) = MT.nameof(lrs.rs) +MT.get_iv(lrs::LatticeReactionSystem) = MT.get_iv(lrs.rs) +MT.equations(lrs::LatticeReactionSystem) = MT.equations(lrs.rs) +MT.equations(lrs::LatticeReactionSystem) = MT.equations(lrs.rs) +MT.states(lrs::LatticeReactionSystem) = MT.states(lrs.rs) +#MT.unknowns(lrs::LatticeReactionSystem) = MT.unknowns(lrs.rs) +MT.parameters(lrs::LatticeReactionSystem) = MT.parameters(lrs.rs) +MT.get_metadata(lrs::LatticeReactionSystem) = MT.get_metadata(lrs.rs) + +# Lattice reaction systems should not be combined with compositional modelling. +# Maybe these should be allowed anyway? Still feel a bit weird +function MT.get_eqs(lrs::LatticeReactionSystem) + error("The `get_eqs` function is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `equations` instead.") + # MT.get_eqs(lrs.rs) +end +function MT.get_states(lrs::LatticeReactionSystem) + error("The `get_unknowns` is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `unknowns` instead.") + # MT.get_states(lrs.rs) +end +function MT.get_ps(lrs::LatticeReactionSystem) + error("The `get_ps` function is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `parameters` instead.") + # MT.get_ps(lrs.rs) +end -# Gets the lrs name (same as rs name). -ModelingToolkit.nameof(lrs::LatticeReactionSystem) = nameof(lrs.rs) +# Technically should not be used, but has to be declared for the `show` function to work. +function MT.get_systems(lrs::LatticeReactionSystem) + return [] +end +# Other non-relevant getters. +function MT.independent_variables(lrs::LatticeReactionSystem) + error("LatticeReactionSystems are used to model a spatial systems on a discrete lattice. The `independent_variables` function is used to retrieve the independent variables of systems with time and space independent variables. LatticeReactionSystems can only have a single independent variable (the time one). If you want to retrieve this one, please consider using the `get_iv` function.)") +end ### Edge Parameter Value Generators ### From f4bcceebc7af6f779919f23ac0233d08cf03fbe1 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 29 Apr 2024 14:58:34 -0400 Subject: [PATCH 094/446] up --- .../lattice_reaction_systems.jl | 80 ++++++++++--------- .../spatial_ODE_systems.jl | 38 ++++----- src/spatial_reaction_systems/utility.jl | 24 +++--- .../lattice_reaction_systems_ODEs.jl | 30 +++---- .../lattice_reaction_systems.jl | 10 +-- .../lattice_reaction_systems_lattice_types.jl | 44 +++++----- test/spatial_test_networks.jl | 8 +- 7 files changed, 122 insertions(+), 112 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index ef9a9584dd..c7f7974478 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -9,7 +9,7 @@ const GridLattice{N,T} = Union{Array{Bool, N}, CartesianGridRej{N,T}} struct LatticeReactionSystem{Q,R,S,T} <: MT.AbstractTimeDependentSystem # Input values. """The (non-spatial) reaction system within each vertex.""" - rs::ReactionSystem{Q} + reactionsystem::ReactionSystem{Q} """The spatial reactions defined between individual vertices.""" spatial_reactions::Vector{R} """The lattice on which the (discrete) spatial system is defined.""" @@ -24,7 +24,7 @@ struct LatticeReactionSystem{Q,R,S,T} <: MT.AbstractTimeDependentSystem num_species::Int64 """List of species that may move spatially.""" - spat_species::Vector{BasicSymbolic{Real}} + spatial_species::Vector{BasicSymbolic{Real}} """ All parameters related to the lattice reaction system (both those whose values are tied to vertices and edges). @@ -231,16 +231,27 @@ get_grid_indices(grid::Array{Bool, N}) where {N} = CartesianIndices(grid) ### LatticeReactionSystem-specific Getters ### -# Get all species that may be transported. -spatial_species(lrs::LatticeReactionSystem) = lrs.spat_species - -# Get all parameters whose values are tied to vertices (compartments). -vertex_parameters(lrs::LatticeReactionSystem) = lrs.vertex_parameters -# Get all parameters whose values are tied to edges (adjacencies). -edge_parameters(lrs::LatticeReactionSystem) = lrs.edge_parameters +# Basic getters (because `LatticeReactionSystem`s are `AbstractSystem`s), normal `lrs.field` does not +# work and these getters must be used throughout all code. +reactionsystem(lrs::LatticeReactionSystem) = getfield(lrs, :reactionsystem) +spatial_reactions(lrs::LatticeReactionSystem) = getfield(lrs, :spatial_reactions) +lattice(lrs::LatticeReactionSystem) = getfield(lrs, :lattice) +num_verts(lrs::LatticeReactionSystem) = getfield(lrs, :num_verts) +num_edges(lrs::LatticeReactionSystem) = getfield(lrs, :num_edges) +num_species(lrs::LatticeReactionSystem) = getfield(lrs, :num_species) +spatial_species(lrs::LatticeReactionSystem) = getfield(lrs, :spatial_species) +MT.parameters(lrs::LatticeReactionSystem) = getfield(lrs, :parameters) +vertex_parameters(lrs::LatticeReactionSystem) = getfield(lrs, :vertex_parameters) +edge_parameters(lrs::LatticeReactionSystem) = getfield(lrs, :edge_parameters) +edge_iterator(lrs::LatticeReactionSystem) = getfield(lrs, :edge_iterator) + +# Non-trivial getters. +""" + is_transport_system(lrs::LatticeReactionSystem) -# Checks if a lattice reaction system is a pure (linear) transport reaction system. -is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReaction, lrs.spatial_reactions) +Returns `true` if all spatial reactions in `lrs` are `TransportReaction`s. +""" +is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReaction, spatial_reactions(lrs)) """ has_cartesian_lattice(lrs::LatticeReactionSystem) @@ -248,7 +259,7 @@ is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReac Returns `true` if `lrs` was created using a cartesian grid lattice (e.g. created via `CartesianGrid(5,5)`). Otherwise, returns `false`. """ -has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa CartesianGridRej{N,T} where {N,T} +has_cartesian_lattice(lrs::LatticeReactionSystem) = lattice(lrs) isa CartesianGridRej{N,T} where {N,T} """ has_masked_lattice(lrs::LatticeReactionSystem) @@ -256,7 +267,7 @@ has_cartesian_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa CartesianGri Returns `true` if `lrs` was created using a masked grid lattice (e.g. created via `[true true; true false]`). Otherwise, returns `false`. """ -has_masked_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa Array{Bool, N} where N +has_masked_lattice(lrs::LatticeReactionSystem) = lattice(lrs) isa Array{Bool, N} where N """ has_grid_lattice(lrs::LatticeReactionSystem) @@ -271,7 +282,7 @@ has_grid_lattice(lrs::LatticeReactionSystem) = (has_cartesian_lattice(lrs) || ha Returns `true` if `lrs` was created using a graph grid lattice (e.g. created via `path_graph(5)`). Otherwise, returns `false`. """ -has_graph_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa SimpleDiGraph +has_graph_lattice(lrs::LatticeReactionSystem) = lattice(lrs) isa SimpleDiGraph """ grid_size(lrs::LatticeReactionSystem) @@ -279,7 +290,7 @@ has_graph_lattice(lrs::LatticeReactionSystem) = lrs.lattice isa SimpleDiGraph Returns the size of `lrs`'s lattice (only if it is a cartesian or masked grid lattice). E.g. for a lattice `CartesianGrid(4,6)`, `(4,6)` is returned. """ -grid_size(lrs::LatticeReactionSystem) = grid_size(lrs.lattice) +grid_size(lrs::LatticeReactionSystem) = grid_size(lattice(lrs)) grid_size(lattice::CartesianGridRej{N,T}) where {N,T} = lattice.dims grid_size(lattice::Array{Bool, N}) where {N} = size(lattice) grid_size(lattice::Graph) = error("Grid size is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") @@ -290,7 +301,7 @@ grid_size(lattice::Graph) = error("Grid size is only defined for LatticeReaction Returns the number of dimensions of `lrs`'s lattice (only if it is a cartesian or masked grid lattice). The output is either `1`, `2`, or `3`. """ -grid_dims(lrs::LatticeReactionSystem) = grid_dims(lrs.lattice) +grid_dims(lrs::LatticeReactionSystem) = grid_dims(lattice(lrs)) grid_dims(lattice::GridLattice{N,T}) where {N,T} = return N grid_dims(lattice::Graph) = error("Grid dimensions is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") @@ -298,39 +309,36 @@ grid_dims(lattice::Graph) = error("Grid dimensions is only defined for LatticeRe ### Catalyst-based Getters ### # Get all species. -species(lrs::LatticeReactionSystem) = unique([species(lrs.rs); lrs.spat_species]) +species(lrs::LatticeReactionSystem) = unique([species(reactionsystem(lrs)); spatial_species(lrs)]) # Generic ones (simply forwards call to the non-spatial system). -reactions(lrs::LatticeReactionSystem) = reactions(lrs.rs) +reactions(lrs::LatticeReactionSystem) = reactions(reactionsystem(lrs)) ### ModelingToolkit-based Getters ### -# Get all parameters. -MT.parameters(lrs::LatticeReactionSystem) = lrs.parameters - -# Generic ones (simply forwards call to the non-spatial system). -MT.nameof(lrs::LatticeReactionSystem) = MT.nameof(lrs.rs) -MT.get_iv(lrs::LatticeReactionSystem) = MT.get_iv(lrs.rs) -MT.equations(lrs::LatticeReactionSystem) = MT.equations(lrs.rs) -MT.equations(lrs::LatticeReactionSystem) = MT.equations(lrs.rs) -MT.states(lrs::LatticeReactionSystem) = MT.states(lrs.rs) -#MT.unknowns(lrs::LatticeReactionSystem) = MT.unknowns(lrs.rs) -MT.parameters(lrs::LatticeReactionSystem) = MT.parameters(lrs.rs) -MT.get_metadata(lrs::LatticeReactionSystem) = MT.get_metadata(lrs.rs) +# Generic ones (simply forwards call to the non-spatial system) +# The `parameters` MTK getter have a specialised accessor for LatticeReactionSystems. +MT.nameof(lrs::LatticeReactionSystem) = MT.nameof(reactionsystem(lrs)) +MT.get_iv(lrs::LatticeReactionSystem) = MT.get_iv(reactionsystem(lrs)) +MT.equations(lrs::LatticeReactionSystem) = MT.equations(reactionsystem(lrs)) +MT.equations(lrs::LatticeReactionSystem) = MT.equations(reactionsystem(lrs)) +MT.states(lrs::LatticeReactionSystem) = MT.states(reactionsystem(lrs)) +#MT.unknowns(lrs::LatticeReactionSystem) = MT.unknowns(reactionsystem(lrs)) +MT.get_metadata(lrs::LatticeReactionSystem) = MT.get_metadata(reactionsystem(lrs)) # Lattice reaction systems should not be combined with compositional modelling. # Maybe these should be allowed anyway? Still feel a bit weird function MT.get_eqs(lrs::LatticeReactionSystem) error("The `get_eqs` function is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `equations` instead.") - # MT.get_eqs(lrs.rs) + # MT.get_eqs(reactionsystem(lrs)) end function MT.get_states(lrs::LatticeReactionSystem) error("The `get_unknowns` is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `unknowns` instead.") - # MT.get_states(lrs.rs) + # MT.get_states(reactionsystem(lrs)) end function MT.get_ps(lrs::LatticeReactionSystem) error("The `get_ps` function is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `parameters` instead.") - # MT.get_ps(lrs.rs) + # MT.get_ps(reactionsystem(lrs)) end # Technically should not be used, but has to be declared for the `show` function to work. @@ -393,11 +401,11 @@ function make_edge_p_values(lrs::LatticeReactionSystem, make_edge_p_value::Funct end # Makes the flat to index grid converts. Predeclared the edge parameter value sparse matrix. - flat_to_grid_idx = get_index_converters(lrs.lattice, lrs.num_verts)[1] - values = spzeros(lrs.num_verts,lrs.num_verts) + flat_to_grid_idx = get_index_converters(lattice(lrs), num_verts(lrs))[1] + values = spzeros(num_verts(lrs),num_verts(lrs)) # Loops through all edges, and applies the value function to these. - for e in lrs.edge_iterator + for e in edge_iterator(lrs) # This extra step is needed to ensure that `0` is stored if make_edge_p_value yields a 0. # If not, then the sparse matrix simply becomes empty in that position. values[e[1],e[2]] = eps() diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 98256e5e78..6d048c93d2 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -62,9 +62,9 @@ struct LatticeTransportODEf{S,T} vert_ps = [vp[2] for vp in vert_ps] # Computes the leaving rate matrix. - leaving_rates = zeros(length(transport_rates), lrs.num_verts) + leaving_rates = zeros(length(transport_rates), num_verts(lrs)) for (s_idx, tr_pair) in enumerate(transport_rates) - for e in lrs.edge_iterator + for e in Catalyst.edge_iterator(lrs) # Updates the exit rate for species s_idx from vertex e.src. leaving_rates[s_idx, e[1]] += get_transport_rate(tr_pair[2], e, t_rate_idx_types[s_idx]) end @@ -72,8 +72,8 @@ struct LatticeTransportODEf{S,T} # Declares `work_vert_ps` (used as storage during computation) and the edge iterator. work_vert_ps = zeros(length(vert_ps)) - edge_iterator = lrs.edge_iterator - new{S,T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, + edge_iterator = Catalyst.edge_iterator(lrs) + new{S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_ps, work_vert_ps, v_ps_idx_types, transport_rates, t_rate_idx_types, leaving_rates, edge_iterator) end end @@ -115,9 +115,9 @@ struct LatticeTransportODEjac{R,S,T} # its values only and put them into `vert_ps`. vert_ps = [vp[2] for vp in vert_ps] - work_vert_ps = zeros(lrs.num_verts) + work_vert_ps = zeros(num_verts(lrs)) v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) - new{R,S,typeof(jac_transport)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, + new{R,S,typeof(jac_transport)}(ofunc, num_verts(lrs), num_species(lrs) , vert_ps, work_vert_ps, v_ps_idx_types, sparse, jac_transport) end end @@ -129,7 +129,7 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; jac = false, sparse = false, name = nameof(lrs), include_zero_odes = true, - combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), + combinatoric_ratelaws = get_combinatoric_ratelaws(reactionsystem(lrs)), remove_conserved = false, checks = false, kwargs...) if !is_transport_system(lrs) error("Currently lattice ODE simulations are only supported when all spatial reactions are TransportReactions.") @@ -224,34 +224,34 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, # Prepares vectors to store i and j indices of Jacobian entries. idx = 1 - num_entries = lrs.num_verts * length(ns_i_idxs) + - lrs.num_edges * (length(trans_only_species) + length(trans_species)) + num_entries = num_verts(lrs) * length(ns_i_idxs) + + num_edges(lrs) * (length(trans_only_species) + length(trans_species)) i_idxs = Vector{Int}(undef, num_entries) j_idxs = Vector{Int}(undef, num_entries) # Indices of elements caused by non-spatial dynamics. - for vert in 1:lrs.num_verts + for vert in 1:num_verts(lrs) for n in 1:length(ns_i_idxs) - i_idxs[idx] = get_index(vert, ns_i_idxs[n], lrs.num_species) - j_idxs[idx] = get_index(vert, ns_j_idxs[n], lrs.num_species) + i_idxs[idx] = get_index(vert, ns_i_idxs[n], num_species(lrs)) + j_idxs[idx] = get_index(vert, ns_j_idxs[n], num_species(lrs)) idx += 1 end end # Indices of elements caused by spatial dynamics. - for e in lrs.edge_iterator + for e in edge_iterator(lrs) # Indexes due to terms for a species leaving its source vertex (but does not have # non-spatial dynamics). If the non-spatial Jacobian is fully dense, these would already # be accounted for. for s_idx in trans_only_species - i_idxs[idx] = get_index(e[1], s_idx, lrs.num_species) + i_idxs[idx] = get_index(e[1], s_idx, num_species(lrs)) j_idxs[idx] = i_idxs[idx] idx += 1 end # Indexes due to terms for species arriving into a destination vertex. for s_idx in trans_species - i_idxs[idx] = get_index(e[1], s_idx, lrs.num_species) - j_idxs[idx] = get_index(e[2], s_idx, lrs.num_species) + i_idxs[idx] = get_index(e[1], s_idx, num_species(lrs)) + j_idxs[idx] = get_index(e[2], s_idx, num_species(lrs)) idx += 1 end end @@ -261,9 +261,9 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, # Set element values. if set_nonzero - for (s, rates) in transport_rates, e in lrs.edge_iterator - idx_src = get_index(e[1], s, lrs.num_species) - idx_dst = get_index(e[2], s, lrs.num_species) + for (s, rates) in transport_rates, e in edge_iterator(lrs) + idx_src = get_index(e[1], s, num_species(lrs)) + idx_dst = get_index(e[2], s, num_species(lrs)) val = get_transport_rate(rates, e, size(rates)==(1,1)) # Term due to species leaving source vertex. diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index f5b68091ad..71caf6f55a 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -27,7 +27,7 @@ function lattice_process_u0(u0_in, u0_syms::Vector{BasicSymbolic{Real}}, lrs::La u0 = vertex_value_map(u0, lrs) # Converts the initial condition to a single Vector (with one value for each species and vertex). - return expand_component_values([entry[2] for entry in u0], lrs.num_verts) + return expand_component_values([entry[2] for entry in u0], num_verts(lrs)) end # From a parameter input, split it into vertex parameters and edge parameters. @@ -95,19 +95,19 @@ function vertex_value_form(values, lrs::LatticeReactionSystem, sym::BasicSymboli # For the case where we have a 1d (Cartesian or masked) grid, and the vector's values # correspond to individual grid points. if has_grid_lattice(lrs) && (size(values) == grid_size(lrs)) - return vertex_value_form(values, lrs.num_verts, lrs.lattice, sym) + return vertex_value_form(values, num_verts(lrs), lattice(lrs), sym) end # For the case where the i'th value of the vector corresponds to the value in the i'th vertex. # This is the only (non-uniform) case possible for graph grids. - if (length(values) != lrs.num_verts) - error("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertices ($(lrs.num_verts)).") + if (length(values) != num_verts(lrs)) + error("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertices ($(num_verts(lrs))).") end return values end # (2d and 3d) Cartesian and masked grids can take non-vector, non-scalar, values input. - return vertex_value_form(values, lrs.num_verts, lrs.lattice, sym) + return vertex_value_form(values, num_verts(lrs), lattice(lrs), sym) end # Converts values to the correct vector form for a Cartesian grid lattice. @@ -158,10 +158,10 @@ function edge_value_form(values, lrs::LatticeReactionSystem, sym) (values isa SparseMatrixCSC) || (return sparse([1], [1], [values])) # Error checks. - if nnz(values) != lrs.num_edges - error("You have provided ($(nnz(values))) values for $sym. This is not equal to the number of edges ($(lrs.num_edges)).") + if nnz(values) != num_edges(lrs) + error("You have provided ($(nnz(values))) values for $sym. This is not equal to the number of edges ($(num_edges(lrs))).") end - if !all(Base.isstored(values, e[1], e[2]) for e in lrs.edge_iterator) + if !all(Base.isstored(values, e[1], e[2]) for e in edge_iterator(lrs)) error("Values was not provided for some edges for edge parameter $sym.") end @@ -184,7 +184,7 @@ function make_sidxs_to_transrate_map(vert_ps::Vector{Pair{BasicSymbolic{Real},Ve # Next, convert to map from species index to values. transport_rates_speciesmap = compute_all_transport_rates(p_val_dict, lrs) return Pair{Int64,SparseMatrixCSC{T, Int64}}[ - speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] for spat_rates in transport_rates_speciesmap + speciesmap(reactionsystem(lrs))[spat_rates[1]] => spat_rates[2] for spat_rates in transport_rates_speciesmap ] end @@ -215,8 +215,8 @@ function compute_transport_rates(s::BasicSymbolic{Real}, p_val_dict, lrs::Lattic relevant_p_vals = [get_edge_value(p_val_dict[p], 1 => 1) for p in relevant_ps] return sparse([1],[1],rate_law_func(relevant_p_vals...)) else - transport_rates = spzeros(lrs.num_verts, lrs.num_verts) - for e in lrs.edge_iterator + transport_rates = spzeros(num_verts(lrs), num_verts(lrs)) + for e in edge_iterator(lrs) relevant_p_vals = [get_edge_value(p_val_dict[p], e) for p in relevant_ps] transport_rates[e...] = rate_law_func(relevant_p_vals...)[1] end @@ -228,7 +228,7 @@ end # (likely only a single parameter, such as `D`, but could be e.g. L*D, where L and D are parameters). # If there are several transportation reactions for the species, their sum is used. function get_transport_rate_law(s::BasicSymbolic{Real}, lrs::LatticeReactionSystem) - rates = filter(sr -> isequal(s, sr.species), lrs.spatial_reactions) + rates = filter(sr -> isequal(s, sr.species), spatial_reactions(lrs)) return sum(getfield.(rates, :rate)) end diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index a2052ffb5f..7c62bc7892 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -23,18 +23,18 @@ for grid in [small_2d_graph_grid, short_path, small_directed_cycle, for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) u0_1 = [:S => 999.0, :I => 1.0, :R => 0.0] - u0_2 = [:S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), :I => 1.0, :R => 0.0] + u0_2 = [:S => 500.0 .+ 500.0 * rand_v_vals(lattice(lrs)), :I => 1.0, :R => 0.0] u0_3 = [ - :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), + :S => 500.0 .+ 500.0 * rand_v_vals(lattice(lrs)), + :I => 50 * rand_v_vals(lattice(lrs)), + :R => 50 * rand_v_vals(lattice(lrs)), ] for u0 in [u0_1, u0_2, u0_3] p1 = [:α => 0.1 / 1000, :β => 0.01] - p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] + p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lattice(lrs))] p3 = [ - :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), - :β => 0.02 * rand_v_vals(lrs.lattice), + :α => 0.1 / 2000 * rand_v_vals(lattice(lrs)), + :β => 0.02 * rand_v_vals(lattice(lrs)), ] for pV in [p1, p2, p3] pE_1 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) @@ -57,14 +57,14 @@ for grid in [small_2d_graph_grid, short_path, small_directed_cycle, for srs in [Vector{TransportReaction}(), brusselator_srs_1, brusselator_srs_2] lrs = LatticeReactionSystem(brusselator_system, srs, grid) u0_1 = [:X => 1.0, :Y => 20.0] - u0_2 = [:X => rand_v_vals(lrs.lattice, 10.0), :Y => 2.0] - u0_3 = [:X => rand_v_vals(lrs.lattice, 20), :Y => rand_v_vals(lrs.lattice, 10)] + u0_2 = [:X => rand_v_vals(lattice(lrs), 10.0), :Y => 2.0] + u0_3 = [:X => rand_v_vals(lattice(lrs), 20), :Y => rand_v_vals(lattice(lrs), 10)] for u0 in [u0_1, u0_2, u0_3] p1 = [:A => 1.0, :B => 4.0] - p2 = [:A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :B => 4.0] + p2 = [:A => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), :B => 4.0] p3 = [ - :A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :B => 4.0 .+ rand_v_vals(lrs.lattice, 1.0), + :A => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :B => 4.0 .+ rand_v_vals(lattice(lrs), 1.0), ] for pV in [p1, p2, p3] pE_1 = map(sp -> sp => 0.2, spatial_param_syms(lrs)) @@ -194,8 +194,8 @@ end let lrs = LatticeReactionSystem(binding_system, binding_srs, undirected_cycle) u0 = [ - :X => 1.0 .+ rand_v_vals(lrs.lattice), - :Y => 2.0 * rand_v_vals(lrs.lattice), + :X => 1.0 .+ rand_v_vals(lattice(lrs)), + :Y => 2.0 * rand_v_vals(lattice(lrs)), :XY => 0.5 ] oprob = ODEProblem(lrs, u0, (0.0, 1000.0), binding_p; tstops = 0.1:0.1:1000.0) @@ -209,7 +209,7 @@ end # Checks that various combinations of jac and sparse gives the same result. let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_graph_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + u0 = [:X => rand_v_vals(lattice(lrs), 10), :Y => rand_v_vals(lattice(lrs), 10)] pV = brusselator_p pE = [:dX => 0.2] oprob = ODEProblem(lrs, u0, (0.0, 50.0), [pV; pE]; jac = false, sparse = false) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 8d08bfdaa2..b2823b3111 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -313,14 +313,14 @@ let random_1d_masked_grid, random_2d_masked_grid, random_3d_masked_grid] lrs1 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) lrs2 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice; diagonal_connections=true) - @test lrs1.num_edges == iterator_count(lrs1.edge_iterator) - @test lrs2.num_edges == iterator_count(lrs2.edge_iterator) + @test lrs1.num_edges == iterator_count(edge_iterator(lrs1)) + @test lrs2.num_edges == iterator_count(edge_iterator(lrs2)) end # Graph grids (cannot test diagonal connections). for lattice in [small_2d_graph_grid, small_3d_graph_grid, undirected_cycle, small_directed_cycle, unconnected_graph] lrs1 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) - @test lrs1.num_edges == iterator_count(lrs1.edge_iterator) + @test lrs1.num_edges == iterator_count(edge_iterator(lrs1)) end end @@ -342,10 +342,10 @@ let small_1d_masked_grid, small_2d_masked_grid, small_3d_masked_grid, random_1d_masked_grid, random_2d_masked_grid, random_3d_masked_grid] lrs = LatticeReactionSystem(rn, [tr], grid) - flat_to_grid_idx = Catalyst.get_index_converters(lrs.lattice, lrs.num_verts)[1] + flat_to_grid_idx = Catalyst.get_index_converters(lattice(lrs), num_verts(lrs))[1] edge_values = make_edge_p_values(lrs, make_edge_p_value) - for e in lrs.edge_iterator + for e in edge_iterator(lrs) @test edge_values[e[1], e[2]] == make_edge_p_value(flat_to_grid_idx[e[1]], flat_to_grid_idx[e[2]]) end end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl index 0f30979dfc..de77d54d5f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl @@ -94,26 +94,26 @@ let graph_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, graph_grid) # Check internal structures. - @test cartesian_lrs.rs == masked_lrs.rs == graph_lrs.rs - @test cartesian_lrs.spatial_reactions == masked_lrs.spatial_reactions == graph_lrs.spatial_reactions - @test cartesian_lrs.num_verts == masked_lrs.num_verts == graph_lrs.num_verts - @test cartesian_lrs.num_edges == masked_lrs.num_edges == graph_lrs.num_edges - @test cartesian_lrs.num_species == masked_lrs.num_species == graph_lrs.num_species - @test isequal(cartesian_lrs.spat_species, masked_lrs.spat_species) - @test isequal(masked_lrs.spat_species, graph_lrs.spat_species) - @test isequal(cartesian_lrs.parameters, masked_lrs.parameters) - @test isequal(masked_lrs.parameters, graph_lrs.parameters) - @test isequal(cartesian_lrs.vertex_parameters, masked_lrs.vertex_parameters) - @test isequal(masked_lrs.edge_parameters, graph_lrs.edge_parameters) - @test issetequal(cartesian_lrs.edge_iterator, masked_lrs.edge_iterator) - @test issetequal(masked_lrs.edge_iterator, graph_lrs.edge_iterator) + @test reactionsystem(cartesian_lrs) == reactionsystem(masked_lrs) == reactionsystem(graph_lrs) + @test spatial_reactions(cartesian_lrs) == spatial_reactions(masked_lrs) == spatial_reactions(graph_lrs) + @test num_verts(cartesian_lrs) == num_verts(masked_lrs) == num_verts(graph_lrs) + @test num_edges(cartesian_lrs) == num_edges(masked_lrs) == num_edges(graph_lrs) + @test num_species(cartesian_lrs) == num_species(masked_lrs) == num_species(graph_lrs) + @test isequal(spatial_species(cartesian_lrs), spatial_species(masked_lrs)) + @test isequal(spatial_species(masked_lrs), spatial_species(graph_lrs)) + @test isequal(parameters(cartesian_lrs), parameters(masked_lrs)) + @test isequal(parameters(masked_lrs), parameters(graph_lrs)) + @test isequal(vertex_parameters(cartesian_lrs), vertex_parameters(masked_lrs)) + @test isequal(edge_parameters(masked_lrs), edge_parameters(graph_lrs)) + @test issetequal(edge_iterator(cartesian_lrs), edge_iterator(masked_lrs)) + @test issetequal(edge_iterator(masked_lrs), edge_iterator(graph_lrs)) # Checks that simulations yields the same output. - X_vals = rand(cartesian_lrs.num_verts) + X_vals = rand(num_verts(cartesian_lrs)) u0_cartesian = [:X => reshape(X_vals, 5, 5), :Y => 2.0] u0_masked = [:X => reshape(X_vals, 5, 5), :Y => 2.0] u0_graph = [:X => X_vals, :Y => 2.0] - B_vals = rand(cartesian_lrs.num_verts) + B_vals = rand(num_verts(cartesian_lrs)) pV_cartesian = [:A => 0.5 .+ reshape(B_vals, 5, 5), :B => 4.0] pV_masked = [:A => 0.5 .+ reshape(B_vals, 5, 5), :B => 4.0] pV_graph = [:A => 0.5 .+ B_vals, :B => 4.0] @@ -148,9 +148,9 @@ let graph_lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, graph_grid) # Check internal structures. - @test masked_lrs.num_verts == graph_lrs.num_verts - @test masked_lrs.num_edges == graph_lrs.num_edges - @test issetequal(masked_lrs.edge_iterator, graph_lrs.edge_iterator) + @test num_verts(masked_lrs) == num_verts(graph_lrs) + @test num_edges(masked_lrs) == num_edges(graph_lrs) + @test issetequal(edge_iterator(masked_lrs), edge_iterator(graph_lrs)) # Checks that simulations yields the same output. u0_masked_grid = [:X => [1. 4. 6.; 2. 0. 7.; 3. 5. 8.], :Y => 2.0] @@ -230,14 +230,14 @@ let ps = [:α => α_vals[1:4], :β => β_val, :dS => dS_val] oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps) sol = solve(oprob, Tsit5(); saveat = 1.0, abstol = 1e-9, reltol = 1e-9) - @test sol[:,:] ≈ sol_base[1:12,:] + @test hcat(sol.u...) ≈ sol_base[1:12,:] # Checks where the values are arrays of size equal to the grid. u0 = [:S => reshape(S_vals[1:4], grid_size(lrs)), :I => I_val, :R => R_val] ps = [:α => reshape(α_vals[1:4], grid_size(lrs)), :β => β_val, :dS => dS_val] oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps) sol = solve(oprob, Tsit5(); saveat = 1.0, abstol = 1e-9, reltol = 1e-9) - @test sol[:,:] ≈ sol_base[1:12,:] + @test hcat(sol.u...) ≈ sol_base[1:12,:] end # Checks simulations for the second Cartesian grids (covering vertices 6 to 8). @@ -248,13 +248,13 @@ let ps = [:α => α_vals[6:8], :β => β_val, :dS => dS_val] oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps) sol = solve(oprob, Tsit5(); saveat = 1.0, abstol = 1e-9, reltol = 1e-9) - @test sol[:,:] ≈ sol_base[13:end,:] + @test hcat(sol.u...) ≈ sol_base[13:end,:] # Checks where the values are arrays of size equal to the grid. u0 = [:S => reshape(S_vals[6:8], grid_size(lrs)), :I => I_val, :R => R_val] ps = [:α => reshape(α_vals[6:8], grid_size(lrs)), :β => β_val, :dS => dS_val] oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps) sol = solve(oprob, Tsit5(); saveat = 1.0, abstol = 1e-9, reltol = 1e-9) - @test sol[:,:] ≈ sol_base[13:end,:] + @test hcat(sol.u...) ≈ sol_base[13:end,:] end end \ No newline at end of file diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index 3b87b6de60..bb106c9ee6 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -1,5 +1,7 @@ ### Fetch packages ### using Catalyst, Graphs +using Catalyst: reactionsystem, spatial_reactions, lattice, num_verts, num_edges, num_species, + spatial_species, vertex_parameters, edge_parameters, edge_iterator # Sets rnd number. using StableRNGs @@ -9,7 +11,7 @@ rng = StableRNG(12345) # Generates randomised initial condition or parameter values. rand_v_vals(grid, x::Number) = rand_v_vals(grid) * x -rand_v_vals(lrs::LatticeReactionSystem) = rand_v_vals(lrs.lattice) +rand_v_vals(lrs::LatticeReactionSystem) = rand_v_vals(lattice(lrs)) function rand_v_vals(grid::DiGraph) return rand(rng, nv(grid)) end @@ -22,8 +24,8 @@ end rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x function rand_e_vals(lrs::LatticeReactionSystem) - e_vals = spzeros(lrs.num_verts, lrs.num_verts) - for e in lrs.edge_iterator + e_vals = spzeros(num_verts(lrs), num_verts(lrs)) + for e in edge_iterator(lrs) e_vals[e[1], e[2]] = rand(rng) end return e_vals From 56df4cb17c208ceb5977662c5fa343da058f2b1d Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 31 May 2024 17:31:29 -0400 Subject: [PATCH 095/446] rebase fix --- .../catalyst_for_new_julia_users.md | 10 ---------- src/Catalyst.jl | 3 +-- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md index 3c3a529dfd..82c297b33d 100644 --- a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md +++ b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md @@ -54,19 +54,9 @@ To import a Julia package into a session, you can use the `using PackageName` co ```julia using Pkg Pkg.add("Catalyst") -using Catalyst ``` Here, the Julia package manager package (`Pkg`) is by default installed on your computer when Julia is installed, and can be activated directly. Next, we also wish to install the `DifferentialEquations` and `Plots` packages (for numeric simulation of models, and plotting, respectively). ```julia -using Pkg -Pkg.activate(".") -``` -Once a package has been installed through the `Pkg.add` command, this command does not have to be repeated if we restart our Julia session. We can now import all three packages into our current session with: -```@example ex2 -using Catalyst -``` -This will only make Catalyst available for the current Julia session. If you exit Julia, you will have to run `using Catalyst` again to use its features (however, `Pkg.add("Catalyst")` does not need to be rerun). Next, we wish to install the `DifferentialEquations` and `Plots` packages (for numeric simulation of models, and plotting, respectively): -```julia Pkg.add("DifferentialEquations") Pkg.add("Plots") ``` diff --git a/src/Catalyst.jl b/src/Catalyst.jl index e235f2b40c..39752d6901 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -8,8 +8,7 @@ using SparseArrays, DiffEqBase, Reexport, Setfield using LaTeXStrings, Latexify, Requires using JumpProcesses: JumpProcesses, JumpProblem, MassActionJump, ConstantRateJump, VariableRateJump, - SpatialMassActionJump, - CartesianGrid, CartesianGridRej + SpatialMassActionJump, CartesianGrid, CartesianGridRej # ModelingToolkit imports and convenience functions we use using ModelingToolkit From b7047cf99109e5dbe5b65d80017329e392e155c5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 31 May 2024 17:35:06 -0400 Subject: [PATCH 096/446] up --- .github/workflows/Documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 7e9af3e1dd..fc1871b19a 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: - version: '1' + version: '1.10.2' - name: Install dependencies run: julia --project=docs/ -e 'ENV["JULIA_PKG_SERVER"] = ""; using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy From d2e0527b07b25af51c43f9b0067229240bab3689 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 31 May 2024 17:44:15 -0400 Subject: [PATCH 097/446] states -> unknowns --- src/spatial_reaction_systems/lattice_jump_systems.jl | 2 +- .../lattice_reaction_systems.jl | 11 +++++------ src/spatial_reaction_systems/utility.jl | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 818da05d1a..78db1600bd 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -125,7 +125,7 @@ end # function make_majumps(non_spat_dprob, rs::ReactionSystem) # # Computes various required inputs for assembling the mass action jumps. # js = convert(JumpSystem, rs) -# statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(states(rs))) +# statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(unknowns(rs))) # eqs = equations(js) # invttype = non_spat_dprob.tspan[1] === nothing ? Float64 : typeof(1 / non_spat_dprob.tspan[2]) # diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index c7f7974478..a4bc32e4e1 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -56,7 +56,7 @@ struct LatticeReactionSystem{Q,R,S,T} <: MT.AbstractTimeDependentSystem if !isempty(MT.get_systems(rs)) error("A non-flattened (hierarchical) `ReactionSystem` was used as input. `LatticeReactionSystem`s can only be based on non-hierarchical `ReactionSystem`s.") end - if length(species(rs)) != length(states(rs)) + if length(species(rs)) != length(unknowns(rs)) error("The `ReactionSystem` used as input contain variable unknowns (in addition to species unknowns). This is not permitted (the input `ReactionSystem` must contain species unknowns only).") end if length(reactions(rs)) != length(equations(rs)) @@ -322,8 +322,7 @@ MT.nameof(lrs::LatticeReactionSystem) = MT.nameof(reactionsystem(lrs)) MT.get_iv(lrs::LatticeReactionSystem) = MT.get_iv(reactionsystem(lrs)) MT.equations(lrs::LatticeReactionSystem) = MT.equations(reactionsystem(lrs)) MT.equations(lrs::LatticeReactionSystem) = MT.equations(reactionsystem(lrs)) -MT.states(lrs::LatticeReactionSystem) = MT.states(reactionsystem(lrs)) -#MT.unknowns(lrs::LatticeReactionSystem) = MT.unknowns(reactionsystem(lrs)) +MT.unknowns(lrs::LatticeReactionSystem) = MT.unknowns(reactionsystem(lrs)) MT.get_metadata(lrs::LatticeReactionSystem) = MT.get_metadata(reactionsystem(lrs)) # Lattice reaction systems should not be combined with compositional modelling. @@ -332,9 +331,9 @@ function MT.get_eqs(lrs::LatticeReactionSystem) error("The `get_eqs` function is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `equations` instead.") # MT.get_eqs(reactionsystem(lrs)) end -function MT.get_states(lrs::LatticeReactionSystem) - error("The `get_unknowns` is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `unknowns` instead.") - # MT.get_states(reactionsystem(lrs)) +function MT.get_unknowns(lrs::LatticeReactionSystem) + error("The `get_unknowns` function is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `unknowns` instead.") + # MT.get_unknowns(reactionsystem(lrs)) end function MT.get_ps(lrs::LatticeReactionSystem) error("The `get_ps` function is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `parameters` instead.") diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 71caf6f55a..8aa5b14322 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -320,7 +320,7 @@ end # If at least one component is non-uniform, output is a vector of length equal to the number of vertexes. # If all components are uniform, the output is a length one vector. function compute_vertex_value(exp, lrs::LatticeReactionSystem; u=nothing, vert_ps=nothing) - # Finds the symbols in the expression. Checks that all correspond to states or vertex parameters. + # Finds the symbols in the expression. Checks that all correspond to unknowns or vertex parameters. relevant_syms = Symbolics.get_variables(exp) if any(any(isequal(sym) in edge_parameters(lrs)) for sym in relevant_syms) error("An edge parameter was encountered in expressions: $exp. Here, on vertex-based components are expected.") From 088e3129ca6dd491e2043dad0bb694f53e1f61b6 Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Sat, 1 Jun 2024 00:20:36 +0000 Subject: [PATCH 098/446] CompatHelper: add new compat entry for DynamicPolynomials at version 0.5, (keep existing compat) --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index 2b90ff7b2a..3c9572d1c4 100644 --- a/Project.toml +++ b/Project.toml @@ -40,6 +40,7 @@ BifurcationKit = "0.3" DataStructures = "0.18" DiffEqBase = "6.83.0" DocStringExtensions = "0.8, 0.9" +DynamicPolynomials = "0.5" DynamicQuantities = "0.13.2" Graphs = "1.4" HomotopyContinuation = "2.9" From 013d58d364de49f4142a98e5a15890fbc1899473 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sat, 1 Jun 2024 04:49:58 +0200 Subject: [PATCH 099/446] Update pages.jl comment out graphviz stuff --- docs/pages.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index 1a54135768..bd716f4327 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -14,7 +14,7 @@ pages = Any[ # Events. #"model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. # Loading and writing models to files. - "model_creation/model_visualisation.md", + #"model_creation/model_visualisation.md", #"model_creation/network_analysis.md", "model_creation/chemistry_related_functionality.md", "Model creation examples" => Any[ @@ -67,4 +67,4 @@ pages = Any[ # ], #"FAQs" => "faqs.md", #"API" => "api.md" -] \ No newline at end of file +] From c4264299868945eb756956a5e1f331e9df59ff86 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 1 Jun 2024 14:24:47 -0400 Subject: [PATCH 100/446] add back model visualization doc page --- docs/pages.jl | 10 +++++----- .../assets/network_graphs/brusselator_graph.png | Bin 0 -> 14630 bytes .../repressilator_complex_graph.png | Bin 0 -> 13569 bytes .../network_graphs/repressilator_graph.png | Bin 0 -> 12564 bytes docs/src/model_creation/model_visualisation.md | 7 +++++++ 5 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 docs/src/assets/network_graphs/brusselator_graph.png create mode 100644 docs/src/assets/network_graphs/repressilator_complex_graph.png create mode 100644 docs/src/assets/network_graphs/repressilator_graph.png diff --git a/docs/pages.jl b/docs/pages.jl index bd716f4327..65ea437951 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -14,11 +14,11 @@ pages = Any[ # Events. #"model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. # Loading and writing models to files. - #"model_creation/model_visualisation.md", + "model_creation/model_visualisation.md", #"model_creation/network_analysis.md", "model_creation/chemistry_related_functionality.md", "Model creation examples" => Any[ - #"model_creation/examples/basic_CRN_library.md", + "model_creation/examples/basic_CRN_library.md", "model_creation/examples/programmatic_generative_linear_pathway.md", #"model_creation/examples/hodgkin_huxley_equation.md", #"model_creation/examples/smoluchowski_coagulation_equation.md" @@ -28,7 +28,7 @@ pages = Any[ "model_simulation/simulation_introduction.md", "model_simulation/simulation_plotting.md", "model_simulation/simulation_structure_interfacing.md", - #"model_simulation/ensemble_simulations.md", + "model_simulation/ensemble_simulations.md", # Stochastic simulation statistical analysis. "model_simulation/ode_simulation_performance.md", # SDE Performance considerations/advice. @@ -37,7 +37,7 @@ pages = Any[ ], "Steady state analysis" => Any[ "steady_state_functionality/homotopy_continuation.md", - #"steady_state_functionality/nonlinear_solve.md", + "steady_state_functionality/nonlinear_solve.md", "steady_state_functionality/steady_state_stability_computation.md", "steady_state_functionality/bifurcation_diagrams.md", "steady_state_functionality/dynamical_systems.md" @@ -49,7 +49,7 @@ pages = Any[ # ODE parameter fitting using Turing. # SDE/Jump fitting. "inverse_problems/behaviour_optimisation.md", - #"inverse_problems/structural_identifiability.md", # Broken on Julia v1.10.3, requires v1.10.2 or 1.10.4. + "inverse_problems/structural_identifiability.md", # Broken on Julia v1.10.3, requires v1.10.2 or 1.10.4. # Practical identifiability. "inverse_problems/global_sensitivity_analysis.md", "Inverse problem examples" => Any[ diff --git a/docs/src/assets/network_graphs/brusselator_graph.png b/docs/src/assets/network_graphs/brusselator_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..599d23615817fc169112d0b0d9170ddc9499f83d GIT binary patch literal 14630 zcmZ`=WmHyMw8blmfS@3vl!$amgS2#aBO#KK(hVvCA|Tx*jdXWNOLv!aclTT08}IKM zjw@Wn^X;?uin->T+fQCr3F&~2`S#w^%qf9j~yQc@r>RA^LJQ8utwH(I&6`L{YP{z?D6(Z5TN zI?)NEUY6#)_+97vII^zdfR_)x%0CmPeMs~L@tv*k;4@@_ETRAJ%Qt+x)n20dn+vmx z<4s>Lue+n8qfXAwOdeNGA3l6|NR%j#@;!~=0oz)FhiBhki+U zvhmp@(T!C2W4?t@UtW{_3m;1{=Ii| zb35B_Csv?|@WGVGnh}=Kef%)JurN3{`0>@%m2lHD(*P6r@jF=~rcKSwOdK4>hbukN zF)?^tWXYC~>D>hwzeVYK#4Id)ctuLuMSi84r)hn#Bym@iV%QKxx|L4!Z1N#uF-3NjglcgWSl7${`ycr0g zg=2C2{!>ff@waMW_$QCQaS7Wkp$W3d=Zq|^u5R+FQQ?eFPU17Z5}+att$(TfHqwxp zg{7mjbD=GeV7k^h47Pjb^t>mIBg8OfdRnVglb$BxM@mWx3SW6(V4!7IMqM2*E-vny zH*cmLODZbb8yoM4iHXIsnTeJd^|bg2Wii7lhCkxr;b~Rbcy~oHY~D1S7D=WLUdN}W zlgrA=vYHNmhtFEv+6pKsVSzuO!a>{G+6pE^H8V5I&CO+GVWH(9Q%W5Oj*3E&-)RYs zi<44Nz?ztth?pHVE!(l)7|HRCizASUV^9A$lu=nzv$(PGqo#(N{#|*Rh(B(0T-;cH z4rTPqqJOdS99haV5J!CzCB}X~f38ed*=p6h#XuM!ZHyJj!FKK)9Hgk^s8$CNa9Bz& zwgqBLHuZLNkdTskm6o!?&PYf}k@E6tpOzgR+1c3GAj#82R900D6dOwM@bZ3~C{Ev5 zSzZ>Yb2)x^!h@XpGPGX)a=OknDmwakN@}Wo#o3wDuqoTg$%);@u;1s;NVRo!`v(V2 zU0vwUo;?%t)h4g1sv;vNU$<1t*COHMjGPfrg9F)=a3+TQVTYkRxz zXx_V_(vM1a`R^wCK-xst*VgiQTpz1p5^=T6H~abp29A)Qeg6E}hC@zHPRRG&;tzj+ z30c{d!E`AyGO|zWgX9Z~ixSe(WZc{YpM5Y0He``JJUq@WE*5rogM58`)vrq&uq1RD zdGFo3N671<9JQ31nmS-wR{C+sr{4V%`}uQq<0n^()y{_*JIKM&(c)TKg!}vZX=!OM zyO>o~RZA^rG##-kPd&RL>03L0Wk`^@H=g|-G5tx%*>rP#$?0~+m?@hen5Vk9x+*qT z?~aOw_KJ$?0VO4+kguj04h|0F&s!}mt*WV!95wh{b#?VMNnu!GbzD@`GYB~!Ox>(_ z@r$q4)wZjct|wc1mnYj>6D7X!@r1CgUF7E!GO^FOy1B&C6+_@rZf-o!7<7iOlj7r{ z4zx{A$3vb)&|;NY&fvqw=H}&L5Y@why_e3{sq2I{952#GaXsA;Nge3Vd&kk#)byO) z{CVEH3U;VI?CT3+(r;t5SrS($$Lj3p>l^C{sE5kB<8XdpxHDaK z_j5!roW3a4wdZAc<`t&cxI8hHy=xAr= z-LTF*FIpO!SJc#g-@YNwTzSFoueB=QN=Tq0l6QMDLZ{A!$!YIBm&2C&JGuX$b;!Mj zB4Jg3?z3Be`7<|{&gUUs)9`O4R8&-~)Yr&i(0;nBY**jdZ{R@KFZ}s(SF6E;2x8{` zqZi$K^UZjyCh`^=q>m+Zo!ox^{_S?qLFsmTeL6lh#bVfrlOdb%BQ7p3S?I|(8YslJ ztGz|8rO-c?d;QQ-TLm$f(A<(EAy~Iv$5BZ+h$6XdI|qlSKB)yXeA^i zKP>$Xsl7g#g4$JVIm0z$#MUNF=AIl64RxhAL29)(0ebE#)Oxyiq*V z9zXsAS+7xQg4J+yK?~TyIi(%jFV6-G3+rfYz-w)7%_c4rJ)>i0CIK3W_05&@^z`&| zW`hS__c0)zI{K4U#D|aWvEpN(YaF_xnY?i6 zH9uWn*h7y?e8p}-6Cqhq?gl;8w8zYrMNMQSp~hf2`!79RvT$BfP3 z*5a}ZzdDS-ZS$-MrKHcKVn6pbatcD>ZH0xAS&X_FGm5jG7i)~Ar=>9h@`AQ>el#TK zyj?bS#QdbFoPyn*WqC|AHKHmyvS(uH9a-9{yt?uaWU8mw;=dvcOEu-G$jIJ?k`2ce z$5sTU6}!tqoSub97qvyJDc|{HJ=CtycB6UQ;o+(_gJFA(mx=; zU}N|KhhJVs^q`qi%4p%L7QU}X-`mJ8&5Mf^<#287lLK2l@y`UKxMi7(x{-$U zR|ir?J+UH5eD1J*Vw360qU0tli8=XF&6t2KLCdAAznv-y^2(CdzkL(~aS6~_=(;1u z$12i_^4MgEHrMm+*};K{2ECB6vty-I&3!*zY3<@Xjlz`PqBYe_()_%Spj$%}nHak~k zYuua2+a<+N*YZoi>+GP5!5OyGj0baIu;32fg!dMAQtx?AMX(`WO!2py%5EHN)4Tv7 zcd3gqGs#Cu6wz_Klf*56PVo3WcmxCla^F?ti=HO3MU3W>JU>L7!@*4b8*FCVkYn{u zXJtw6Z?38LYq1xMm6sDC^7l_0ZWET4mMAakI*Pl+h}{ukJCrH6s(X8(Cey2rwVbdK zo}k;3@uWjbyhAiO_(=1viuij&JyEA9+#{2jZzvd*=ZH0L=2b-VI5Pv{EZFx`YzrSB z=ETD>zWP~C^~lhjk}l3s@T0zQp;N$m*?CC{Hinerwd}>VU|qcxag-v-{B(aTN7JO@ zTxVhPi?Gm8r^6l&2?>e$ApV4yi9nLWA-!7;q9>`Cq?c*JgdcVHICNqqUD z={a_sYJ|{9y?0iq+*dLZ>hNDGUStXl%`D?5Zdei;hZ3#Nb>bQn%}VKJ?7!q_oSR@o z@9iqkkZEmg?w=NxtH;t};ELII^KyFL@Bj*6H6IU7^1O-3kco@Dy!r8z_WO??9{?e# zaP=mAPb;uG&sJ0=lKy97^qTFsiRS4`3A8rk@+IosVud>*A|leUtbP_#wVKj*6VJ<`Zk` zr`UWl+fIhT4UH7`Dr3`OfJE+N5(1_`1xS?+g%JtS;UM5qK0|ATnx5lECvDNlrmx$+ ztm=Vh@(%3IRMv97xFzO9Hxei$t zv3i>jr=iz`5?qGAqr>@w=3H2%*^Nv=K|wt98yTFE!LI=myCl!k8Amp3A;X{KT9jPp zkqYE)R0RExlRSLAiq`M@Cs+Hh)l^mOL&y1GY%ErwQ}^A^Pj56=qdSq8KqHcgg+;$3 zl-z|oQKIzdSX(Lw6R!eY1N)e})ihAR>{lpq2a0N#(+Xpa!#05Wu9w!rA|hD0xT^D~ z_17owU{-Bycc}%m1~noO2Ed)2ol=V_Tz-Cj%?iuB8kO>&GAoX$131%e{}!dt$Vcg2 z=ieN8wg1RruZmD>`6@GD!e$f5uKB`OFf^Jo<*p3T1|I9rjY`3;o*r^m)^I=_`C3(q zmaVcxqcijI!ePJDT~lXhXQ=C!18S4^`Wn&7d~6x1$gj zsLFrZOb%i&V>2)&Q*5Yw4xd}sY9aY5i1JIo0EHWpMCkeY7;Wvf-KA!QLr_^68_aiv z{2o{JRHI}@}rbnH$tX`2qfDM(H&uyY~TdMWl=>!?ibDeZWzk&jW)xIRp zwZ2)Y6EyFyFmyb|TIqdEoP&aC7J(z#=6jmjZj1H5LtUm^5(PayMxf|j=weS`KF!@* zSy)JkTD?evp)?34X$XiQ_c>l;r4XD@fwo0{zLsTO{aiHQluV0}x= ze5s8H07GKGm6^>(=@7k6LYnNnAx|WdEn-$|)N~&MUxYH~QcaMKo}QeZJ`f7AO$Ait z5SZ@-1n$VIIs)MNSe5gQUzc9lanW!nL&oi5JySY?D*{m7G{;o|vzpbCEgNlvrv4Bm zUS&kitJl|g1P7S^V(RyEr5iXS&^gK;^Jr>0WThDPj6R9^YM5-_t@S9ZvORTX{-49BC>0G%7TNUr2LVKK2M&z@yoR%T^Au(h=XlIy{P2Y=?~h@pH`J8XBROGT~C)SS8prOQ(V z8^!>;aC5!usa9!Or5e$da5u^Jv~!?%y_+h;0D}r=c=wF1e1?*g`q*>+-2^IvYv8{* z6B^Sy9rO6cKL=p+#m6A8lkKS|1Oz#kfM5yX$hsIBy5l)*X{2vV10x;Bu0)Hf4|0Bq z*}p$-Zy}x%+1&AZ;4xo!S+5&y8-{_dW3aSkW%6%4t+Sm)N!*Qfp4y9UKwjIKijObx z`}YS>kG{slOt^QW+6l)CGR)!3%Qg+ahVpmOez(ETZ+H(qm#SayPe-HRi-dD zC4fRxR8q2+xYh}kla;;S8JdukM11GYooccrM=zubVnNrIHync z_Vi=F-FCnI+u6zA95)*;Bso7njrzC~Q8Vkbhyg40uDiaKe5IDZd|)`d>*CBO;$4;N z?#TmR0-TJg^L4hn+&%j^u-?>MK4x6wtRsAcnt{2On&%qf%fwf{(JKTq;0Nd`gnVw# zSy)PJ+M%(KLcUSRB(Akkhu(yZyR%0m^`*xM)84*dN&gy@xT%?!mjcuVOri4m%N{3N z8o*pa*gLtpZffqp7TE1fpBHiS$FW;T8W~Z*-vD94=(wW=3l7xe=vD**EizIyU+KKB z!nI?jsp-E*j~>Cfj#pUC-6cNMN&0J6P}c_hNl{sup_(0yYhPySz((esCyuN?yv+Do z8%MmDu;xD!}xtv!qrqpEWyx7${;yqo0sdu{C-v7?s z`^wb`3&8atVEG$>C6ejbfudejRZuFr^?=hf@jsxFLbdfbY~><>#N-~O5x4=vA;9E3cI}L<}%*M zsdwKEq}q=#YbrqJDN47BV8n=B17>jl%a| zNH|g%v*5sOD8DFW$`+Z8<%?ApKx1ZSXRke9iDNbA{+=`8a`i6MkewmEw~V%7Xffua z9U7i*M(H>EL&^cG1-e)l$Nsq@B#zblwTE_>zPG=yG>bmmV`#WyE-x<+AmI2}q~AUW zQ|6_^XsBTfu$22dHM<*c_Dz_lVt)9S@5&I2>O=fIW=H{?ey(x67S>`Sc0lMBz6k;@p<*rfkXfP%=N< zK>R&br<(R^T^*elMMXu+n!7OnF2X!r-P_R+Y0Sf$Q?Oj-d}sva!(?n{KT*u0!sFTn zD(C6=lZD*PsX^zz!jnhN_~}(17jBluWF_;>9aKF%cpRSe)?z7t`|5S{5*o7UdLrmt z>T70+D=2c+i@vJqx~q)2FwoPdE2N4du%q>2+d#6=`I9uwJfq`{k=omvOQ1Uv`r6O; z&7&>}ctZytitviX2E6;yv{64lnZYDG7u}LMH;`whtHO>!w8ndaapH0M=x463pzU1m zI4fO_9PV7tYJZUb1Luz(5l{HS%W!Z0c4lS?dqs$}hH(Sf=_;FM(5V#%ny;8n-^@%3 z5@GN3)EKw{WMt(2-%6C`=H@hRxf=)FEPcak`_dwcirBy+K8mlUkidq>EYl1mlK?JP zZzRnetDtsjtkCMec-)Q=Ixj8@en_!FzY;B*y;-ar zL$NPtW~Zg~hl3CGMt?R4jBujs4kv7E<~v*S&Twj;sR|w`x zobCExy3KM7G|w3)TVS33`|rQ!oHl9sJixx5ij#^XH;YS3(l{RH$WwJt6{pUTRlk3fjUCuQas4AaJRHfe zVaW65FsVC+GBNt z2eKW3!j`+|4~`R9<*7o*?iGs$HWl+Z2zaI@?jl|DPvINDi2^4Di+bPm4VC9|tKc`l1x74O*MNV53I-l`X%5_igK3N$A!9wvH}nvMMgDrxO_|6X$pJYu)umM?+Z zv905G6DCixZ1wF%ZBts3`+KHh)w>b0@1i<5YjNUKzj*TcM-ys9+HYoWO;rTdxt^vd z)8H|_M?#Q{u!`oUl-ziG+Jdaf1(O+f#;88RS5S>uk9XdZIuh3r3q>Xyj(z#&>o|C+ zua=R0>4z4yvJ4{E*5z}XXEg4=jaOSz%J}Ey=Z<`~@}YpN5A7CI-A>doT>uiRKKQ9J z?Si&=`c|Z*>sGipJE-a6CT8~Zsn>AT7*(D1`W@7U0Jqp|kdP3uRoo6+zy4V&l)YXx9m~q~Bv~AE&QIjmq;HpxP8 zoi;9g_QdJk?Hx8!MJT9aDn8*+XO9G(9Ez^!Y}PZMa{WO=N1rIvy99kql)gH^f@2#@i_NN;dWnY(m6rYj~$aauwAm%lFGoIt)KcMJKYS&`+7&)1(FY4B4YDF+|*5XSN=78d8~ z&31ESt@1CSCT7p8l$Xu&uB@y8wu2J=oX_n|DrA4U{j*WR`jk`e@E}VqNoWF<=lu8H z&aKYs&D8plC0^kb^j@0``_tt!A z%Dxl%+JdpfbC2=plKHcS&@(!M%g;yumR?eK7oBXKoAmVxQ$drAz6ujqGJd6TYneP!K!MUMzIPR`++sq5c4U+-8?sK@T4 z7yj~`%`c}Y^BGS1Sjs&6+DC&3>5##= ziYrDMwYKaOiX6BteQBfK&k)pxQN+s9^HZKhA|(Qq^12hD5x}fqyEV=P(lCO7J#tyr z(i`M(v=m8IkdnXg{4EyR9UN|GOzLefM2OOpp%a(vg5LBPHZYWIAXV&9B>g)$6prya zR}RR=oKFFKpM~6MyKyeod(=5?k>_sQ8ShPKTsiJscb}=gZ%JH2q6-DU< zlYO$!m7DCjqKR_WIWw0^pDQW--8ZL7_ODzy){lWAPp&cLSntC`?V56V&R#;g z=zLFgdfUFQCMofut1SthcprpfQhxrV5K^&)wKZ`_BeT)mf$a{OVHr}_ za^cVb*M)RCsetW~&MbYm;N#6P1Q7+{HeT<}9TE}(%+#34!&mG4UuHe>sidjaq2t$G zYZaC=HV+JhfJ|1b-;SBDUDI~3+;s=R4Cm+PSMSce8@iML!jnV-JCqEb@% zQLcVFRo*=wU!AE~Ov-@N-AoGm;pXIjYQ8_j#_&pjxO$9@-2{t%fR2t3fg^S*-pxo` z)(GqG&jFv+w`Hh?{XUUfgxVH7fJ z6D5?O@1;sby#@d--{5gANxTTMz}94$e^wTi!|rU)OpW9EcoAuGax$y~gf;mmaw1uJ zBINjyJJoNizxYzplA{`Po6SD3kaDNJwxm(k&ev_|g#~ETIM56EVj2Rdt*qHJzSZLe zc`3Voj#$oIvRt9``;8>2WFz8;%d(h z)CkE|7*PFw{1`k&MX-jlNbVO$zwPF!8jr%W%G@gW6I+f5OYe(6ffGqpxU2JG$r!tf z612lYyLCmrt34re3k!{615{w#eK0o*fc?(~F(JvEQw#`0YjO|N#O)mkVox>M_%{L| zE7?iZzcMm1^i(!2!$(I)2N2p00<`u=DV@}TPYW%mlEt`}3?hwV z&MgcEZRP5C|1W-N1&=BY{MAd@?yLCW=V6g~pgu623dfq^vD1B5VuS(5<&t(ui-QJg zxu2h(z;Cxwd{aTe|E4Oej8AuF;@?<*1u+#kY8jy3*XJ7eLDzX|*?V0Hrzitx5Mu^U zbrqDtLg49fvLv(1tW)Jb2YPeryOyPBqqm!K|Evd_#Qw>Y6%4{J1cihUTlV6`i$@Kq z^B_hrfT~32cagB57%1@WDim3}4xgpIV$7Wd*SI&n1JcXhFtL+ZXaN{n5a%!bKFI%8 zCggSqNEHjRftKTOzWfa2R47_$#(jy%_wR$%L9p2;sDZ2{oLWh}%7*+2h4lORChwqO zCi*ooKh3V$!;ggDd)|(AnjA%Q?ew^=54A;*Buoc*+|GjEX&An*>Q_($ZJ*s~?kDv))f{rLasUEV(cC~dyspmn zp^z$C=@}Xu+n(*|VG_Ri1C>xhT>Q`AU?_~^Lj}70FmJ3+mTi)1V_u$}Asji~-Q9)7 z#S39_eDe`%I?hY9*vT7cKO{NbNWyd(bc9s0MM)&&jy%-F(F`=&2DJnp%~2d#O@;eB zdai2z5cnXeNV@sdAifm6W*?Bv5WQx$Vk!dbiG=PPJVwKvDx~Eo9-m-1RXJS71%E2% zsdn9q>4ToWzDxk9KvAnipKXrhwEtGh+&etX$^X>x_wPS&DiFT;^tsvhQCMv358y_K zxa@wxK37laULvERp%s>u(L1Kw6yTJBi0yLZR7%vg6k22Q<3M3@eC)?leGHM*2tT=+ z=82pNaq&J9nD3M2eupEWYD5m08sea#`W64K$CsJd%}ef(RZgfInL-H zwG2LJC-y{){09dIofeM^lxf!clkbAvWM#&2Zh6=9)-|y9cLB757#3qY@?=mK!L{(! z-yi+UmoL?!@-Sz@LDucw{4ZbC3~l-xj1#o<=hHH8-p;PuhtJ;J4GtkA$Ca7-Fs08N znQxJODlvMe;^4>MS|xGSpFgH+$f0-uHvpns5v$qoJic}$GnuPajg?MiT zMy`jEpCpf%PSiTHAeQKPwT}YqID)x@Qi;!1nvE^dK%rG*8wE_I_00x-DU#;7pCll! zZ=$q|{eOPkY{w4HWHPO&RMOpCY!=vGqKSusV~Nsf<9Bh-1Pcp`-}mqDN3vC*KNLFc ze{eb8IBsdrMhg^eh9R{Vau>8WYDarTEEuSP=LL@^1`q)McG8=tXRm`$q*KCzaj<15 za$VARMZG7baz={vn(wdlCCLG`11bOV)vKm)u_YeNkR!9uY+;0(q2s)_Uk^GefoRE7?Lud${S#oxe7u{%;}l%g6mWmrS8vSN zWtpFJSW`0(U~W0WIs+BzIky8HLjQ$<*9TK(PI!<5Cr_2)P?m(lMj4zPRPqsuxuf#g zqaQ5veA2zKeJ>(e%gY)^8Z#zE*&u}4f{6Rzz`axTmlsn`w0OtLcDlWGxu8WZoFR!tTkcYA#nGmi~gLF@(Q@Z+KvK`EMdDn|vLEoy5qK;{%A3N! zA#d-#x3CBV^CvjtN=--5!09;C)7*Sls_W8`t&3{>Tlop!Z=AmUHva@L2VQFZmkfEBgY=OpkD)O z0&?EQ_4L0TotvknPlGPL6f5wQ*Xbk+v>Rt*XT1Lg*3MgyTr|qf@fxlU&;fd^Sb1I& zJfW0L6S#YQ92D~a{KVP$m6g$8X=L%dakHGO?@5ygi{^IBAE2vLjZNqbB-Rxi&8FSy zR-sZDwf$o_h)>4yWcpS;OCH)W!tM)fs)3>50CW!>&ue>Vr6JPpYR8md2>itl&Rm26 zt&i_A0CrW!Wu1K1Ws^QtU8HY#1I7}IuZM+lEuVTc|G_MX zK!*{uYBoSOP0!3Y-<+>H&wD=$Sa3H%1~1u>9PAA!wZ)L7K(*rWy0C(i=odh+`+-vL z!o}vjwQ(|&#I&OWVN%ZJO=`+GMj}feNBN_?ys}bkF~ttX=7|a`V$esiNJaOW&>8yS z1(Y&1(od3kxsLaYeq_J~}hp~ua+G2i7D3s_)aiu2?y6-PA zFe6?UH2M1&_+%$~`LQMj0*t>vv~q@7&1QGD&hYKY{(cBQyf!B(X*0OX{J~_57wqNb zwG2f|BVDlrj4KCVW;J~4ak*WALG&T8#(p!h)^vo%8FI@8{6Pj1p-Zo|tN(%_Em&V) zAA?B8!dA5O=w2!o0YMkSAPIi`4wy2@$i7L0Q6Tmm%$do}L;hghm@K!D0=I7!ua}gh zB<@E5Ov-dVaWSZEK5|t`Pp(U%y_?q?l#AhItf(yZPNMpAS7<{iDK`T7GW?WBiJcY(SVm! zRDa1h#QM6r7Ek9qjZd~F42+D5?Kf3{NuJ3gC@CqKC^LJ8kmvgnd2w)Y2LX$KrB)RD zhH#O9!|uC%0vMM+R)3oR?_IgM9$0`{+S*{~{R=ej12DM!`Fw|h3E2+JfdJ!>9^>G& zgpx}=EEkYb(W-M%Q7h-PS$Y7u1aGFDVl^sFXQd|$St>mTH5D-d0I$8cHjoNlcQDp~ zVTR((n`nqR{Oy(m2%ONcFoZ$s9ME_S6rLU7_qkBtxwtf=*5L4AEY;up*Z7ctJpl0P zP@$fXkT10|!cXZyO+&K)akMqpkOVgoY(at~lK{*1cjF}YNEq7rKKU{V;WK?eM$|w= zwl_95`e0_pLw7-V(gC3$9M#Y(!J-dsLKZdyW-YA6RT(ew3&Cv%xaNUC$5_$a{B=0afHVnK41gez5OQ<22sl1 zSxc{m;Pwh^X_i9jb2d{PaIwQ)GCCg^SkBgVK)112+v}|2VCU!No_fy|&+nOt7)l}W z023CLmZs@8cpyF>5|7EF9v5u6SuAixps(TPm;u3npb)$L0iDy#&HM~3dx&H=8=Gx- zwhPGq&Kg+k16!5A@(Mc$3w%XG^8;=VXlZNXaoJHJ_;=X8_O`Z+wgs&0BzX4(7BFJ2 zbjLJ&Srf{)D@%+mms5U^f#bH6~hAz$j?@^p7B!UAL# zX%iDFP=W1Tu9GHZhmX9uRzc#l+noh3odKx%rbpw>&d&eG0RWpV4jM72UuS1$ds`)a z2e7ZuN%Kw@b!wdyAik^@kl}9;qZB@V=XWepKsi;jLDbL55tli5p+YWYCV9M~r-z!T zOb{Wdl5ZZCnD`UCGl*H<$LB|}%nUh~jd%4jka~2w_3_|*#^=JR16!14RdJwXdJ3qm z(lN~5pvJhHBOF>9Hd#&{kaw-$dJ`g;T;Bg1zZgnxjQ3)Y&v^$ps{|4Hg zwY@!?UG!ILEPD6koio7ah>Y`nM9e<%Eh#Fh9h%1kaEV~vr2tzt^m=ByHF<;^7zWs! zbh)=#ypS*(-#`dqwq23tyE)T`VGM()S0C<87(&u+&DO=~xBmq63mgMFtk!U?KRLym zlm{1DxM`iP$GJD6>Kk=G2eIq+Iaj=IXI~$~SS%orcCa0QO$XushS@35ff}`O&2NAk zA{+*MC^HD^1-$VblgFn}*(M+-z@kRTY4Z)bAlS*pKYe0^T?8-jU2j3XHO6~G2(!Oy z0tuhUX0z*C4djuU=lL=;+O@t*!lfXk|f& z+&Y4IXEN!33GWDCzC>ZG-)?_FM7v`JQ6)x9tL@gC{xy2x@i;NUhT!HoCs%{*ewYt( zN&jy!3C!Gaz+2&&hwXDa?tEJ7Cpp=f>4Hd?kdbNa=|AFYKvV<<7Ga==&wizO_>~0lfiK|vj9{OE z4JrbW-gLKg#Do?^F!~00o(a*mF$V?)jyA`F z5OgD0+Eu40)}iB%lAi-e2}wwhK`>Ht4Gpgc{9C(_Nh#o-#|40JFwAg3)|gL~%M_3& zz@48@|Nh+3PLY$5N%y$E0B81kXl+A7Lo^|IG#YgEp-8xVO1s)l4?zpwzdu;xXa!|QSEldnW79}LsT4V-r8vgM#y97E zp3G1_QEtrj^hm6&t)YQn6A|^m=nfDH)G!=JP!);0U`${8)6RF-Iaqj2$y1T7MHxMrQz;b znHOEx(()hH^B-)oz~ha_|M!LE-_K;P$H)SYO@(i6 a@9^xLhq~`uABnc5&;e5gOU;hW(JB5q@ literal 0 HcmV?d00001 diff --git a/docs/src/assets/network_graphs/repressilator_complex_graph.png b/docs/src/assets/network_graphs/repressilator_complex_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..a5302659175f874a2bc1df82f6a790ee86c96cea GIT binary patch literal 13569 zcmcheWl)??@aJ*Y;O_1O_uz{qxVuYmcPF?7w*(LFt|7R)FCn-tPVoPe-_=#!tGjns zR4r`nGS5g)PxtrJ6a7g=4jqLU1quoZ9V9QU1_cEz5BxofgaEvLNUVGWej%7C%1J}L z{rmaWU6u+3MF|Cx{-EKVd$#H0ud&$wb~)YdW!1ot8h%Qmk7SBUS&qcmP|m1lk5{6% zVNda7b5_*ct5wj_TlTWLRHL_ER^4*0aiZ7U_C1(VxB*B0XMRB5C|}mZM0c-vq%a2PDgJ=;fNSH@#`vSjrK%>aqLi#+@u&V%~#CSxyxW z=Hiv(@bFN`hGC1qk}KINp87pgB)H6~%gww@HA%n$r7P91S3|8NeR&Uaa zvEFS2JGiuY+s|XSq%Bh8Vt>tH51|aF+$0`sXn%-Ph(+~up#>3V{)Q{A8CYu2Gg@~> z%Fst$Exj>eWUwE4KUC_vS*7|*cB~VeV&NQuIWnZh5Tvg;5#6EnQyl-_G7T2Ho;<_Q z@6#f53Z&59veiMiKk2}7)G!h8H4pV5K_AwAW6Y_v-u*se`XR+aIs{o>dEO^z7+Gw^ zg*2o!n$5@Qj|H8K+d-jZ%Fb>TDTtf!4bdWYV6XtZC1?LUBsf->#8~h#PPE z?;!!Bz0kT_bNv+H^{~zDpF3QyCJ+;BZ>K*SzP8ZwvWZCl#&mUMETP5Qd>s`8`M!BX zzVs3wMip$&K|1(6s@u;VLkoIE8EIe^4tjMKXHTThSq!5Mj*wY3aC;pwA&^Ol6mFef z4r8rsxL$bjo8{r`qs>byg01w_gc?cuT1{`vV~;N_5;G)h{|da=e;3-SZW zQnz2JwJb{i=_q)j0qxI zsOO{-Xi?la{$PH{EA3BDA#VA)gz&Fjhg@|DyN;-rnq;0e8`1lex>9xSK=|%Z*+O*Sc7oxoo|>g*CE;6|xn*JYXjX7?K;1$1}zv z$AYNl3RB)u$Yk@zEQB)_vWgXGQxR=^oAw44S6Dyc@jK(3gf~y#oSm>SX^nM3k10GDc1QGB554J|E~{cst7Fsi34E|NW!FmUU@UcGG+K?Y4pIHxS5 zd3)YMWUD49E}n)uRFa(Y?xc}hF_OD&kQqah|N2*Fr{(+_l!lD+=r5%n-|cb}fL21@SxHj&$CrAZr+6PvVBe_j?shRq)@xx&_7@kyYJweJ)^p~%&{bq z$h%F20CxwN8`WW7OM@PG#TzbaKECDz`hgR&HW1+83&TH2rkJgnpUBB|5xf!56k;vL zp5816tBThj$Ms`_n?rm>2DwA8GYrpOQP5yM)azCCH&YChp|ztApTG|Ya3|;AoIc+)1qkKS7kaqd=I4n{xS9jjMB4_R4fyB7( zUdOyHmIl2)80PAQY zA!BiNtN9P@7KY_Q_7dck_Iu7AjigKF&IMvMblgu667MXB9n~pZ916sM^nUI*)1n6?gtl{zn?Ff?X(43w5&(? zs;7&OgeV}aWm+{Sw%!2<7Pcj1GUyhn#oykMPb%wJ; z4AwO$LK)nR@qrCPTc=v!bvps4pFPyrCR+tm5-+tl33PoS(FnF-aJ!Hy?sX&*M+QmZ zr$t+0_2g)J%359b_wUT1n4Tj&@-!#Bl#0xs4^3yN#8{C9bWBk_nf%Re^A&#j1mGr? zdeyp75~e?F^^tw3t|Izq%JyJ#<=wYugK z^$Om*g+M{Jlo7=_#p| zqCyHS6|Z>h-^u>7XiH$@X6179Y}UrJ?b);Mc;LzVglP2%hhgu1Vu0#;Sx00Puj_tlU+98K7rTmkL*e+gv`@*xG!!&^K|?0+$@F@& zP0q0Fc*aPf#eX9DMc>x8s{b8c7%y8Ch`?B}A3pZ*_!MoZT|@H&C?ft+cZn*1V)WkE zq5ae1!jyS!(DprS(AD!a)2lz-js6 z#3&0Kn7rIX7`vJbJWcd$ukTJ91k-V6ubCt2UeDFqRtQf0ae9fHRX_0T?dfh$#uGPE zk;}=G@p|QlCou3_yBBV;ra3VRNXZGjVWn5x6-=}Jrd07e#_Tde@`ro;LlYTcvRTVtz^`5hMm0xWwIt) z>=U@GZi>q;^YC@OmdzWHkQMwI9Pfec>(I_G?tU0&3YxmZ*Xn(?$KQDUvNeD$G5>7{ zzeD+SfOnEntOr!i$gI6_Elz8l_<>jd%g0fR{?FpH-1H%vm0Nu~uMLa1ac;V^E96%% zL)+zY5-kD-;K`_GR^SVSYJDE~ZSpyUcM~D&O6Gg2rEDFekiEn4=e@0iIsKmH zolERhbFM6!x@Aq*eS-;?0}eBdSyG|3(<;Nu_48rzr(p;YzZ(^iIsHTT#_znbrIll} zk@`n+c5vULATd%U}^L^HTHP%VRHs2VzL=*kqx+1?Ew#}1kD_fbRs$I4jdK;A0 zj*uU^{W$T}LBGHx5QUjLa>aC@K%mIvd}8JvQYo#!LK4<~!GTJwOl^MqE1I$KOc}FQ z6w7mHFom(f&H&OaI{cgj?$RfxrWQ(&GOj9dcp}Dt7|3l#tFiwbGlXWdwwJqTHJS0q z`zZDh-U;c+Sy<3WXVv{#S=$pcFdKvg#1!9cUu%^G*Y)d{^C{{0DHJuPU)O^36`^^# zDbdR^g3(NPjgj^@pRr;J8*aO;0rNIpA0rqgd>>uqq7JK_@PTo?hd4M(n;mldkQr{V z5B8>jve$l22X@miV9g(RvtYGkgIh3mSp7;*gdAIQFvb1`*9N_u=u~Uf^uGFn1MUrl zg#8eF;wQJ4*{pNf>v2DNI zn!a9ss{I{543S|((FQ*ej_MXY`3F!=TXxQSA2_s~%tm^2h#W$nvxvPN3o~xsA!}N8 zwp*-vMaL+TEK+xW_?)j=KuId!FpUTr|CbwjC@ z8(KNz>S%J_2@$Wy)VD-ITv-b5Q&%6Sn5LGNtK-Vrt3RB9*)j(gKp0zx{h1Kws5|<{Wt=jvyJNbqRX@O`PpRCP>+rk zjP*}aa8X;Iu5LL;I=%UPP8^ODnmEv4w?>3KzkW#JmTfppki%AZ>uyRb=@e)(XLCyj z4?hOIhAeWt#7wxzh&sOeJ1-vSOVHcfC}1*75>sR4X=q3WSQsr-G?5RGyR{N(?mls< zDOIllFY8*7{z742$jMdI>hjir z@_{wNsdu-nqvL)}CxWNcfCJx-Yx~ILBwfH5%#}4eB^S-~G+nr=x|N&vur=vs_jfr7 zgewDPNfSQ!}}J!?F$9w^p+m0M$e%&8yj zX{;KFsNlG`xL6H4zIuUuq8=Z8U#~KHIjyHi!?C0@{Yh{Odhg`KUe`bR1)Q&dm7$8` zSw+q+B?=U&;FAAsb~$vlz!)DqC+`9_h!|?0@pM6=}zZgm9K*W2daXrY^K~rW6V zlzgL{m5_~BbI4k&?)8;ZM{nSc3xwgOJIKgQFO_cwBaaj!fHf`VPxhJ5$?d9oYh$|n zhg;h*xBwO`-cm-Pv+=U_2OXl`wH>g}-kn$&p%|clsQyb9!E?jXScx776`%EYy1wH! zi}3@Bi;Hz{dvX@;K=dP@h!`=e*!A(c{KQC+!;@W%_#6M{I|STCX)|B;#+uHkLU?`? zZodI0oddObcQu^C$Qx1Gt&@74!GFoKfqD$NV-77k4NPWk#>8d@UjA+K>%!_P$j$9N zjcgp@!jsshVZX3`B5`1OO6|)t2?61Lm@36lnM!a!0xp)NoA*&`RCj9|IN-$N_Y zTga>7(mC)2Ws3`rdy#k%C1HjB%0!~yqXiOQH431;n_o6&yGdb5yR%JoM;8=1$$jMu zZ5P!t$3ogF)970T9B4PhNic*N$&?=rkhpVjUDYLfqc``)h*(hLRGHnb;=0EysG04dRr8;a1C;jx2)k{p_Bw&CQSW7t zhN8BelS$Z#akR{-HDx|!rj8VgtlrzW12MOPKW!F>3wpCamY7(1K1+yOaAokK{nrXo zO{X(1H5$7tRbLhJ29Nax%YkCZGQ__|7v5HEtIzNX6KJ-WAqn@ObMiFa;yfI!G)@WS zCD4T|-B^z@gAWxO$U%I})pp1JP)Q=Pvm!w--f}obhlz1!yL&9ku_ZZ!6!=mt=Y=e8 z{7MF8vWN;kqrZGSGnG&^Q6S{2{fKAD}j>8{hR<$#2KjxzU_ zxzpJ|udg*E4M9#5PR55Oa*)FpYtYmauK0x9d9LK8bqIoA521b)Em(LzAbUP6T^ zOUb`U7hJ`~nON#JS{!FWKx5L7V*dA>SN%%@CsAN#92Qu3X-iLJ_QEpF7s%!+VoYWi z9B{LeB8!nCp5x$T%LW0N0y-^?bU4Lxmv`P^Yt6QIUbd~@|XQ>`!d(rzc=5~ zWr#b-b3AVcfh6;AT>sD+-b%{r=oHR*8V@InG2;ZW4-6g!GRV5>DhwGUh*BNYUP%Qb z5_D`#L%YA8%1oHv74m&B>^qZ5umX6)TL&Ya2TBurw`AzDZ&4j>p^YbUj#>B(>=_5- zRpl`kCkQ4L)L1ZpP*+#Sm=kUjyOlr(zwsMJZQdjBa!AGQ^vpHiGl@y^BJ89r*%AG$ z8t?f?Uip2&`41CFy{rh7kl1Q{Ba8b^A*%t8SiB;=)jI;Z3`8`kFjVZyca1&EieC=scqSL}~IJvSMvZ#|)pQaE%&|+_0+JqC+KT|_;x&|_L9z(#K`onXj zOB2ca+mtFvmkGfL6iHAlOKJ5b#a1h4h7NF&68>K!C^iLPV?h{XM6Am5f?CTzFFUmC zPBDPQ|A&+3%+c(TyPvZk4=a5dQmSOQE*%Y5WYaIkx$4Tg+PU|E`QuQg6I&QyA#1S_s9h1aVdpsW% zLvn(aL(6*55>Agp@z61Vy#F6GEiLfZ$L6+lq9qhb7;eY!N;}qBj8A< zW%}knXGD@=yO#jJvzKBcR>~N9RHWV}%ih2Y`mo)IQO!h?sw?CdHz7{}bml{P+TQih z=oTQYMSt{rKEN$?8@w@CT_C5{#I~RxbOxqr$nfM-N+fi_*1PVjE(3gg%h4|)axoW? zJ3&T*fCz>TXpy!$!ev=Ju=So!bhS=Gr!SHTj1*W@aOUs-v>!AeOJ`aND5xdnt!oT~ ztm$cuE7~i}&hCBv)rbXS6$jeeSG?M5Qv-1$ydlu`z2Yc~G*%EMmX6cS`O-)n{&Nt< z?Iy*i*-5lJ7eNv*3oE1|*DhSmo`~613ZCkCA%#diV*CX}=6eEiAnwdQINIRxqRYGe zqN;W6Xm-N8my`->`GnYAV5%%=$7G!7MuTApk_-V=@M`Rln@5=<{RD&p-GM=XSL*gs zHR~$;3B+B~he#`tx7jbgl%c9;Dc4um9#Vw#!Qg8a$w6G2Vmc`05}tvw)8C~nf0>`O zaPtV`WUaUY$=%TUgDRi4(s&w6h@zSelA{ESdxxlhS7m6TC#f9eMYyU5;K4dd-!qHm z=j_L&1;nS|>V;Ay8QPs;h^^^PEzRkLV|nwit;h&j$6(oXs5mN+YUgLL+*hX;&sgLJ zpU$n!^;c}-IS71;$Drc%Y_J!e0UQ$k$7wq8_f#t~0L0iJ;-Kv0Y4vLGG^QiLT$=lH zc=LpCaiEZEz@(%3&1M&AUwO>J!pUqB zSiwmzn*Z}ux-_2rgPB9PaUu&yZ6;hxBLBzI`M0LIseTlaD7$Q1TgZ*d47Q1o?f{9B zBZ`D(ck_JBy^NlQYl=D3NCP+f`;e}$0`0OzwCI@M$&##H+j)W60k-M!DTbW`2C<$n zAezoDZTIh824kqSBBnBb!h_eumeyDLC7TtU*pOrwPt8Vky!$4HgJX+wmq-Uva}L** zP)MPDNYYn10{G5x7@9mNrC4kv`0M}wH)yeJPo#N{z>xIF*mE7x0l$Wn8Z%goMP_Oy z?N7Tz*xuVr3o1h$53aJaJ1~iehs9GSt^Qn6J>ao+UGZo0>2cwVbdo+I?iV3SWA&~z zEhirhwB~sb8)!gp#o=k~k8^!VQzSZ~U3s&xFM59?c5orHw+JzCfE*l@xte}p2BMf( z<`!De5Z>dH8Gp0jgM~Ky38qE}2Kl6ycmhb(*`63)R|_yHOC{f~>7G^#wExAv*b-EP z42jtjWs7A7&D20K@qNT3Ar5kI`4~LS?lTKZpt5x-xiZ8?P`{bl7hoAuu<*X%8yy1q z&%ADHZ%oiW1kk+w$0w6=@pnilUCK4I(f}* z6$FFuENF-Rai%s_mL2YQpDdPRr+s+x!tnSMn9oS7YzoJg5{BpReXu1>;_E*p*;*QC z!Qi;1d`Yr6EHZ{<{NJj>}6@wl9GNJsty)E=M~4 zFvomc>B2-o@HlgCrZ1#XGT>y7k7cmnq!ybKhbNc)w=dU?2h&pkInPlkrcd|OeO`_G}8BJ{4- zTS9;yhGW@?wQVQ^Gca&7OVeg2OU}g2h*&+e|9fjaq2byFHU>+~7MfY|!!j>-idmbl zEP3ixO5`J(6ayu%;X&GM)QWo@G0j&UO*ooAMQxA@5UsR+s3GYQn#UwrS3}R&ITT@} zS*Q1p1|GK|hGJ|{GBT@~8iG}S9Sj`fF+Fol0D=>KaV8qZZb(KMAsD?O;D-isIe3P~ zCx$+nb${)t$n0jwo)Hm}J>u2I``}QV1!2unQjpXD1o6D4lBRU+r3^q#ImdZBF)~7Y0Zh&NvB>N=n+#fzqk{SqZvP~~fg3pUtJ?$q5T?9g)Pb1j2;f~l*sI+G zVOqT^evjAZX` zvmXmOS;Fr#m~wm1x<8F6CIpr`bMAS)L9W38AJFBX$ds3TT$GW8F_sbQNr&p2tJ3ib z(tO_{!@TVP=f6CQ0OUv9<$i>b=p+At#)(3hVI1^4Pac^ZiB4-T^NI|SCx9O_0~oSl z*4$4rFs5t8!`fbJdK%m+Z>4s;1D_FB}6DF+=_p-0L@LFZRmcpnBj#;;I}`2b&Z$T*2XB_ z{>lx-5e+PA8&5`}lW=A!Ei8{?yf&`-6z9(=beg^)bs~`&ZFv)xjpLja!bx||PwM9+ zz}-ep{Nr)m!z8Ogg+I2IiI^!pei!92py{(wFaj~*C&!|~@72o(l@%3#M?VHiYH8u$ zZv}}DCy>GR?YUBgfBXKODrpd1Q0M~OvV-RNNuTr3o4~=y*nkXp$gj14GAhgT%3I=yO9^2E3}TIMlf^TtVwzUCDa-e)7-Qq4%)YJBE7(+*~wOWi*~0qE%pZ({d84msxj4C!0OsJPEDJ zUlt~0rELAW;K{*;e+4J;yQXwr#^<+rs76c!alM@RSDHNV5bk#cwE zxxBojrKL^!`c>M@j2Z+2mAwdaaV3X`hX*FsI|kuNVjoJisc1c02*#(CVcw4vv{pBM^ zkH?yMdO2aw!!N{5V+wG4LR!$H>HRVd!?f9GKw`l)m!`~xkBepjv~6YHId#JF0gwm{}h)a2!~mWFgjn!Aa(SOAt{W*k;u3xFlB?e;**Ry29SIc`uoytO zg!jwsa+E=Wn`OODosRDm%SWY~Y0{mcp`m%0#+sUhwKcu5@$nzk)q8y*uvLbg=`k@V z)P-g`ca6*bt{aDcY1xhL$i#g2gLLl4Qg?BuD22eC(&>%e5sMse*?>*;N@2)dOx*z+ zd|+>hGv{Y**4d%Ey4L1ts;*OGZEpsRu=qjW5N0N)HFDO2Oqpo!`62_8o_E;CpV)oX zC%75wnPMH*XJAg=5x}4RT~03&b25S$y&&ujuD74oP~7@{Z(SwPx!iF|N=g~jrg((i zN7|}6%g7;}d=|;mtkkM^-BS>Idv*i#c=}Z2*7eO3KUREH6>IOC!N;{Naq#&0PEWVr z3q<@in5J{-z11Tf>+8}q)Zf2O;&ekop|L%86k@^{eL%kTV|qUq`)fu9Lqwmx`D0;s!a5xJl5vZsz;|}m?c(VJW;9R_PuV^{hxkp7^xFc{gs5jP9 zyL~a`v5?2O7h1cqH@}mK+yDJIBry>^5)yddxx2f2aafet;I!2P-3xJ6eqVv%#b96Y=@x)$`Dujm*!W4(=J?07#z0t;`^`SCGL#lvLhNvY>Xh}zuO zu|?CX>nlHbZ^r0#&sc8BJ(uT@|6R4;aM>`TbBAb|GJm69wSnJRv&ij&I;oKR7aRta zi;4Hsb?!%MDXFQQ#tg%fce}3kD`Y*jy2G@z@9&46pmaL_v;r_p7$5wKR55xwzvInQ z?szQN&}RdfqR;c?a2Aj4yNHO0XF!8KC68Ikh#BJB0HWv!tWY0*@$kZOhX9DK9+iGQ5*Y{8XA9HBVNA&1IKXQ`qdGlN0rQVQ z>9(P9{PC~#nqKwh?19_r>gtB)`sc3atMP@!#d^PcM_>lGzE`-eHoafu6@KPx^?I%v z+*QtxC@Mj=x5EeYW-;-|1QktnnGGy8t7E3r`cwaIDbLREFz5Sj=2?@lS2n_dhY0G$4?!LJI~t z&zk#juC-j)PW-sy%3A;Fd{L3Vzr!pnExGKMSYIEPd*?Pz&$us^r#)U5y|8k=Drl}c zS^@M&)#X(`uB|!aWE{(@-jICqVA4LA@C0csC+ACy;;$Xg`p3axKxn^chP-iL&z50^ z)GvND7o|}Oko?Q=97R*M9@INLHfKn>R-Z1Y2mvJhx4z?yK$-nEYQRkWeM$AzX==Oo zb2=|xXLujJ@psgCCH`7zute2waoFy8IyWCpp%a+5P7*$nEbvmy{67Fw*|0Kh!SNYn z0SkI{>$O!XIa|Lk`p;+SVTSsvAPOn~-)V2XvEY_iUpL>`aQ$fSaneGDV>iB=L^CH( zOz69x_y51qAM)Fy`eT;F`kTNlFwuQU4M~)BkuBK1qp>lSf6E_7aUlwKswYNw8GM#% zs@gxZzV(ADdj(%S9yQ1R$m8_&#>41sFNo7~sAQt^q2FkrzPUaf;HQ=oH;VqoNQMk^ ztWP)rK&tl4-R=Bh|1nuzAPfLdP|ineTLbTi1_0>e>VA(`NLa|Siqxh z5Q3&=r<@V1QCH?cK~R2Y&@~4@9ylxpuql6TypKY|P8u6UPxPPEatlpZvMSuEQp)Tu zUS4+hY-&7iAI|jHZ2b~<78JAIJ`;jOlqBjtNk)Md^h^#Q>YqSmK~}&^YbQmlvV!Fu zI=EK5_l70+tXfRisN4uu5{NNmkoqUG-apJ2yh=++!Ln%ASUgWTSXdKafWvnVyYZ>by!BKRCe6VDIVSwQ5?#8d{c z%q1j=u0Scc(G`EX@Mv1?s-*9}&3OXYD~9&wlUpB2?nm*uwx{Mn5^$L}P;X)AO99~K z{C}|$6%7{vO?bXs?hH5jKe(o|=?{%0(>w)ywld>c6eAz+@;Y0u0_qDkje9TKTD5{8 z1SBg9>70F|iF7D{KddorICs=baGON#5lf?+vs1 z-=3YbJn^Te4mDj@3i#{>Bcbr9@(K!n2%OFj-SU)+)baYYfl@{~ocCddU&TakW~8L0 zrT@jD^vc;hzwfKpG^hO56L*kjNm6Lkcf2oHWUR#;atE2O)XW}OCw^iZ(y$5fa1aJ5 zDk`4bUQSJM=wt8g?TI{{Hj2F5%p*}{0^Yl*%m9f7MV{n6k(;#5;sj7z)uyyFHGuH< zwl*k01u{OZ)YnMIQ|qCr$~bWz;9p`2(?B^-%#HdDLqmO0mAvghi@ZgLkjo5?iHXU= z!XhapMN&Zl<;Ra7wDk0zwIMdmdH@+ce66*oS_3qzC0ToWmg?&2g@pyQLs7%`LhgnT z@C>WhA9y8x-DmE-i2^g~$>OL_IDVY2vcKNhY8@fz%OmB_);;ELr!14tzx?Lj0TVI9pHPfv&E`_&cneCrF_9yrL4UIu2^y{Ryck1B) z)4$6GsE0?5ZBRuE%sUj?Sa4%{tD}Qs?eu1qt=(aS0fa{3lT+3KG+c2_4|QSjAoD2W zbct(z8KfG)AX?PYk>DGCpr}yW>Abk59X8Rbi_06vN%{WDRlKisCVttFcX%GJGMf#prtWJTMbENkrr^0Y%L5efOQHpsc4z7<5`&fIcFj17Dz4My zpuC1mQbAgurFxXH0E~g4Xof12OXL#?KFmbSPs3OgL=8SE1X}MWgt@BO$S3%FncQc{ z9Xvdh`o41#P%)o}I<#ZxGfxOKf=e<=c8!Woun_`uqZ1!51?w}{#Fo_Z)*(R1F}!E7 z1`vutnr-&m-Wrq^W}$F2~v7%U|+-q7`Zf^cbN{*5$^wZH2XumXnp!59QMl1T;R(|NHd z(FufL-eGW)fB`^jGK+ZGpt~O`m_r$3qk-Xb({~@-O#e_=?7t? zV{y7U5c3<0EL{vJN2c!IsQ@A^WmH8@|MWn*rp&h!u-Nr@>HY}jXrN8U)RQsk!2sDF zBTR2#Ffv@n4AV%H>q!axL5GdUAslUAh%3p*+O@>>>%Gzs{gmNA!7zYn(Av$;{(=Dl{(Fp zq;h2>&{D*|s+eJbixfHUhSLuAxTzRa110id$yWTTlGD}`)xk4sFMguhG1N1>UlMp+ zbXED>=!?y{(0vv!XHey#TIF_LI1(D|wBaSc1f#vfXSgR}8)<29m|6)ua8elBxw1JG zTpB4&A;w5wpK%8~l#S2}d)3UMN1?6IEH=GWg0dyB<9$qfE~F{Rsy~}5+Oh#v(a!E2 zotzY?a28e-=NUg57KoEHa35!j{FvfAuS@Cy9qV&MlT% z#3%f=Cx|bVUm}(=D%0O__IMvs{Js92f{D_4`bckl`sk}J`}$Aaki=9GXK0@#=UQ?+*2 z6R~=8AR}9YUfr|#j0)5W=K>-qF4auacdrRAct-_ET!5vS4gvU+Mm8lf;{s`f9T4>zIipMI1pi$ZE- z9J+CF5w>LtkC%0XTIz9`monmc}=sAH+tC$SItDWt~}8hER2H z3w;n+5ICr)U(j7_XsHLs!c~2Jxh)A!S+3iJu9%o=Do27Cd2HsAx38Zh76rL^;Lby6 z_U88X3UZSWKmQH?j#v<8CmU*y7h(4-oH2fgNq^@aQ4d?GO!xomL>ilv^bQxd-zMx@8SvBYi|5sY@Z?dKGI_?CW=+DR2 zx{R$#C23_uShMPDmr*8~QX{<~=_?)-@`hGz)|FYPb5OJIMeaunbzWDt5zX0&P5LZB zm0LwEE#4#Q%~cYA3w1fGSSP}5POT9~hH@rN+mOnIx+?3N^*pD3|L)wmXleA*u>Q+i z1OMpB6j*^xob~t42+kkLsIPL?Y3FrFYQ(@s-mhP=RP%QZnBCvLeN#v&NAz+*d)X78 z;x-W&U)IL^_ga>0F59EZ?SmZns5A+8L$ukSR@;~7Gf&t}gsJ3Yg<_8>f})|wExn>< zcg9tQ@E%#lxLT{F#mRJ%YEpsvxigdUQPE@MI;h=AJ?ky}_osX!`slTRxK@^y+rQ)z z7CtT2+b;&ZdP91@a(;gP`}c>yCt5`sSWl1H%kxtpGKRx^VNxP*=8Lk97nZ6n00c5R z$$dX27RLJ1945#Wz05f+P1;qQ+S ziF)A{d2+x_{wJ_+S8f%Or?%G>i0pB)0zLf0E|P7et`4IKe2W;Dk|fC!?L6Q@zlw0Hs*v-87wsuB=$v@zg76F@L}~wl|jTBoPkY0}okY zzy>8);`4fMxUl#2;c|;snI;AShxtOSjUvrOAD+>#y%q%e*8yUXGQ+m}Opg_cu&m3} zper0csF`JM?(Rm@M;RAJYeH3nk%gl;#IKly#M{Eu?0<&3+EMh9T-@BAUS76!pm`F8TwPt4>g?tkKW}^@;hwbvmoQc4vpp<^){%R7 z#^95KM>G|Aq_`pxE(hJTSax6%!RW%?-rmu}dm5#*#H1uCOi2xm>8}XL0fB+`SghBD zpr3NSllW_`+;^gN0vK$C-zQ^xzR=#RhI z$Tc8Fi&5ngOpl``yXh zvYP7Dh`g_eDg7|kwy}<~&&0hV+fDT8>pwY<$T$YZb#CP==Yl6aV_h|Uv&H64pm5Dxw^PeP*xW4 zeY{=oi;RqjKtVy7tuTsl;k9foEiJ9AWJBsP)NQ-QMxLdd=3#Kq;QG_5S)@}m#8O0r zzyC_NuFm6li5@#VCnpD&S(gj+CNMVWg@y|U$riU7Fqajk%*f)nDKXSVPwiS`!^cx) z{6Tdr&t;vU|9&zxF)=pZB_CKXFvZaXUkZV)SSmK@K7*j>e^tV@S!OSW; z(aIB!0y%#A@h&t6BU)E6foAbE?YH6{@LzYzwMuLqkLPQw=E@BuetyWVCLM7T4V` zEV>O2%fElVzd2gOBPM1_8L`KH@7;LZa#QKL%nSdw?kFWKY2mp2toLQ5?K2DGe>VEV zt#i7LDZCLLZEeO2X%z-S&BD^#=RX};$?hpAD8!8_mJ3v}k4fQb+BsA&K-(`BT9ZQp znt5xEdnka{00k8}KJK2NrN6(`&{+f0ysfL$j??|IFcZ||qul1n(HPHq6ho8i!vboO zzQEPinD3LRUOm~JSEkm7FWrsv(!}Z%W$BnZyxi22VK=6;b98fuEgv!rbXBlpb7Ch) zJmV3AfM0#czxwYvWr>!WYf3Jo91V1n%ZoI9H>@va3?jNW5?fcW3kF9=gD@o^CB?;b z5mTbi+v$$Yx7Y7O(G6O^R550+Hx%dfM}2X1awfXJlmI%1vPJWYTIQZI0~F%-t8b@M zrHL_tzoQrNoK5|0cs11{4N{IrcMrNvh`SGRij5aald>|ol=JT#Pwon;R}2N%KfStv z>YL(ERJK0Lwp<38`_bxHMh2@6Y0hvnv()m6}PO<(|g zdbl~Uo)BEOG(Pmq2~leNEK-tB$GUTSJkUR$Q=6tPC_*B<^8%R~WQ@&Ml~YQHn>3!y z$=%Jgiez2IhBv;fGXs=zky7t-pZH;+!KmPztO~ly93#aNHCsLM&adhHfgd)KHgFQa zWc5Ml0eUOSVQEhU)zI+pFowu2xqyHG2giBa%?cwct7C^=lB75h34ahJrzRGnC-udH zW%%hyX|~V%oL45F^MjbGqG zmHc~Yk5#>dkk=-!r9;U+K$_uvc->909P7M3g_&vP({l@EJcIrjpPtZ2)eANXl=r#VdRoVB*LCNXNE zAO`&U^{Y&;K|Vc=tJytUdO4BLroBe6BY$_{$7>f7gD?4^#nayj)hY@qzO;B<6-P!! z&M3=IS}#@+&0sf4fm4k>mdAV6|+XU*lQ-7r3 z{DmK`7ahw)X}26x z$ZNNLxE#irJ}p7ZN}pYQ>#m${3{FEk9C$=bGi@5LmhEK=a`zAD<78=Z)J)Ho{rXIg zG6fT5C1>VfV`Bi26Z6I{|7#T$W_J~3LRRMe&lv>XL(!Kz6+=VAha*gh0CG%;@bGXj z1*WsRY&S2@9Uf*eIvKCM`O{9v-JGqmiu4(&LZ&LZj~}|vs(GA8qeML z0&CaY9Eu8k?6>C6EQzC|qjCu}bwV?KUswKji6`iB+}QMQxa}#1B@?gGu?_|xB1{SZ z{+d2|?nkjMmy#>rEdY_OkwgM=Q#Oa%+4e?} zHx*M6kyjo550EJe^60l&rhT1c(fKw`tadVzlDls@0|NpU4!76#KiI1vP_Ivrs1ZL0 zMMs!RNmf!)>X?`Wy*n87Dnn>**-xquizfVl^dXPDABC*Z*g9z5jJ6*Km@KYq#34PV+-3wTt2 zP%TO}wV0fSt6=ED5+{9{h%;#D-8QS;F z{lA9zATg_!!zcigo4z7xZUD*q4^xj1sQ&Le;(bm@XK|`4_ zZ%oSr!MxBKZF0=-#L4rJpnL84`OA31F+G{EZe|KH-Fqm6YEQtecp}9J{@hnSCrxU< zSYLipR=~6JX+xH_rG?XX_csMAYXw+Di(ZE84?XU$vLx$1B`2C71|dA0Ws^)|008FH zN>eU9lLj8HEy|mwiyc8!!s%z`=3)7O0m_VwQj!vA<)t}^e3Zl3wE~XC#z>$)+xL@d zpL1X_WO5#%Q{FtSm|nv0Ru18LB@<=Wo6}v zw|K@D0F+>!o}RppE02dY6Oe#iGD1Ek;9K-2+G}>MYz*-a)y=a+qXL?vmQp*(t-ZEu6ulJq^>l*<#7uWRcY=mTvUW0>Mdx3QBSIj9c z1Nu@A^evfNLI)LcLab6`!|uHY6HK)Xk>%4Ft#i?5Ws z*Aq1nF9!mG&e~dOCEK9yEt6Rq5icNat)Nh1Ip$2v(RJs0b^vkDHqttmUY%KWb@3O8)nRP^De! zXoLXB##2E8j*ao_+l6?^$&LFW-d*HJB^(=0uhC=mok!WW!4;gKQ$*!^PLK|5kEQ7~ zWb}VK6Ls=W$Up_eZ0k0cJ&Hn*006ObBS}lh(06{eOIOYm|K*Fr){_7ckvKq|Dl$PV zS1lnSVCz9y*X+3}qNIui1~j?qfIywNxYAQ6ca6jmbrk}iNCwsli+sebRlij^xzDM5 zlP*tQ`CWNb*7BbZepTOQ)K|qD3)Vd4WaiMa>7)6K@Wj!-bL@C|euyR!oPxn%Q&ZnZ z$CDWu-*O*(y9bNb=K&r)hH|5!Cp*?5<9y*8fgv{Ms9IB#w^EeqxE3E7lF-o5K%>me z%rIl9KAsdef6h3Zn&3M}9D@XvCRDnml{(H}2>IkdNhnQs_5~-=2q@y?6F0lnUs;?BT z$b3G$bRh#2&)z4WU!ENGq^#fSMdK`$+SZyN5KH`BA6$qP8AM^Eq0Cg)(kTL?$7(Bh zoL*{`r zZ{a(MYP#u*YmU7c7r}V`jervX%FD|+t;RbiEnzqEn#Kd;8?E(MY-Q9qI zfaC&28ubEL6cMij2-mzPD~-=Fck4D#u$Dr|(OovDk)4`{dG}jR?R||P%xD)3)L_^( z{)+_pXkx&VBo`WQfu%VigYy0;^i|kdjooFQ3`5ALv>MS$d}P2aiSt>C<36(lq{Wlc786e zqLR&Hzi_(NL-yxF|Lm??7Y4hDtC)LdAWRR5@w4rtQQxgoyv9l+!D_4dVGzDh0FAO6XeiGc_3 zQIW(kFLox3nw%|UN^D@ZI3*(S<6oti{Ni4sa<63>F_XbXhz5u;;rd<8Xk|mMQ^m-?gFsBH=S6o< z4;+BTk+e7Wel3_%N=v1hf4t~oHjR%bfc$Tc=U!gJ8}X8Q&X|G7=xAjnC3_o@hr7GG zhld(%Y2~h2H~6mr(7END!eY>DK7C0b)C(NaB_JFqVI0tj` zg21_-#%sRaT`VCn^$yU60b@2-0RwGqZ7(l@Ezw4-R2&y|=+QXuOP=Zw47U|4*?Pay z9O<>0P+@IQJ6IzgvMjmR*H*OOqflH{_&trZa)t>vmpT4|4S{Hc&vVoL0(Zf*#<9!9 z{GzdJ%hs^T80q{;%SN!K2x|}<^Y|*d3ojt1t{sMoz6}qsrD6im2EL&UI39649tqm` zQ?^)56g%^;(bu;K^#x>bt_QPaysJKMCFmf2pCs>{vm6(jaGT*N?r%LD?KI9SdvRdk z^8jFX3vctpabuwi_YEFPZLi5#Lc2{+F*S-wt_0Ob9>zLClDx^uXeOj|ai`2bD?-uP zf}uO+4-;a-x-5>*S92j_>D+mF%0)$B9^Yhh`cW3RZ?b;>MTGRoBmc*OtuP%`P?t~2 zSquToc@6)^5pDhWAjF?4v#~uUp}|!f>wi>LOT!@U8aR8$hJLs9ePta2x@;<)W+#uU zJl$1xFe05l!Ef+H?lu#Y-HqAsaioN=|Cqs@nJrO<5UA#s-As^mXsnLAx$ne$ubXP5 zto@2fZiDV&Alo)g~?ck#b>8Xdci%7z{b|yFj0|lF_ z=F`+AZDDsOvC^-^T0RrUx+;BlplxaqhR%WkC5zomI(_{uFM1^T53Cfpycy!BYo)ZQ zzJl>f3`ad@m)nv$5$MA}L7vA=C0CR_@wZZf6Y=J!8}g6VzAf74Dsm)2%g^?E={8Cz zV_hR#GS5X{{-ba*_Y-AoD9C6L>O;Q%pct?HD0vy-*HR`c6HG2UN9Xgr=a$AI|g zrUHv2b+jGY(#W~?cb(k?y4*Df1eD2Gi9ty2(cQ!S`Z?GMRs1632R1Cs&3_dXfLLN( z_VTxrZCOCp!!Iht#k=EEA^i>JCCT~pg=GXDSoQ*$IM5ryW!Y7Tf6biTwGm4meb9$c zqtfO8Y1zCybbW@F4|Exv3{0Ic{nhWf**#8B7E41axEF2#iCDeVI}d)u&I}h_aR3V+ zzdKt*D5!7uV~2d`kFP5PdgkO0W+~iq1&R$@^A+w zyCbE-s_^r4tO(^j(JnO?x>`xP--FWmUV(^_&suD(ae0qvCS$q%g70~FjQ~yI9Wd8==aFEWV&{mwnmG-L%z1L(B(`+X!DH< zA1qHgO5^AJms4GFslfRTau`IFYr*^HW`TNx(ursuRs21MC=&sjSffvqnHu)IxV;Zp zlW1#y-=i$5oR#p=pHo)(&c+rtM*jM3S}YaZYe>R>KC=_Ve_H-U9BkC7`4JKDs&jd{ zjj@Rc0LzKX>hy7x_@KDw<@BiPP_Hj2ZxxcViHAWfKeQFh90mgy$N(De4cezmNz@yp%$7C0n+{@yICs+Wb&@4KUwxyxWFSHvq(UioK9fb{usalK0l9bh(@nh@> z#W8S^9Zw|Db8Fs~i9wKqy~aohW|tq!=vi2ZID+UgPjQ)@q1QSAu*okxY~Z@WAt054 z{U{&bpB%;*V=f8a62cmV{cnKn z`hX|S^B=D^6%0ehcf5MF5tlz95L#4J#mG06Ca9V)o%TiVpS@||!uVtI#Y1U(2e@0) z-xTrH>B*0iUnukWyT!Fc2DU^ZRKm{Uzt4a3W;%`RxvP)*iYD%y_%#}4Sk%K>?74+q zGYZTbV0Mx{w-!y|<0nY-a@(Mk2~Qw}m>A%!O`x4SQ`l=g?EcUke=@IHPl##Z@X%&8 z=7ZX~ULL#j@en*pQU3aH;M<|Q;+0$8tlR2fAgB1vX@-HQH^@YLZd4Kd=-I1p8;-*1 z^VX@=Sgg;z&0QE)1gyXwC+rGedDYLb#8%Vx6L%6*)bFAI!Z?lBzz3ra7X9{CE{dNv zHx4W7eZGqgi!?N-JO$34gF7!LpV|ze+_#UO?vj#Q2`ZX-1$K-KpD@Gg z@MTvCNh~h5tY1UMUtE9KF4UfHn$K*FtPt^DkBwa1cDztF#YpnghFs|TFr%X-|6DeSTBtV=k>ZX~1npO|r? z{{Dm#+SL5r$qpRPa#KRK5u(p@d^;t#^;VwisGvRTk zbHv>Y&X*KB?%=+-6z}fK1yJ?9Ymk|M_U-0kVJ1de1WMdVll_bee@mNo4g;ZY->0iN z_^-zQpl=KhLLs^QI#4SRGbcJ$J@4!8El`Prs`(L^)tdQ`6cWeiNg^ZjM^>hL~q#sH@8{k2YEb za_RIL09?0th{VM}jygcZCuN?pdnJS4NjoTp{_SvsNbSLBYF7%Yph~LRL-(6^3A7~@ zM!Y>HWxHQ5%cj2goxP_hkOZbk0;A&!OCby=)I_o1LC=v zJl`G_AECvNJsjZmX67H-I9rACo(^JBrMyXD;XdhNKKk0-WoL1<7`@DP7B?YXdPXLGS-OO_ z@XK*S-bq)m#mYr&v$OL#5Dv(-?05u`J6_i3Nyoptf8pLo2^u?@FCm{WbFnkH=zv@) zNOt~Ts5`$){`znEvOi8r*g>&tlLOsxruefD!l%oo*-ce4FfK-h)eme@apQU>6Y z$s_Q2-NWXcE99YkzY#;SI$WKHtoAKMuVFyC{sW`A-}B}3_a-}S^czOH2U-=BFk(8# zQo)u7PRi)laHrezTEa9+u)l`0=olEQl_u|PY-}7Hj;Hbzpgumj?k=QkseLEN;qGLi zhB7$^FO6?-g){IULe-pZB+#9T3ghkBpoicd0(%^I(eraA@;B-#=Il6J-|HX_^`yo z)xAjCpG1y`E%8qlgvvQ8cCUTN7-`8bgJMDF%b@2-;^`>n8ZC{O%AQW z-~0{-&&Z0ij1(oN+bX5m)C=Az8nnOSS@7x9c&I;0Q;%T&bg*-h9Uf06xy`Cuzd-NtCnAxbe?p0ml`nMUBaq=zTc9Gac`uRQbj|vR*8}GQ4my3l<5<_cJhX<8N1fM&40nCQlkp8ph2I z4bq&AV7Xf4*C{p;~WR5&@L5tL(jtU^=TZ{EjK zy{4Gna|N?#chKv%w91M~N^VYUzRr@MA?oc|CBIHRIcHSHW zc{JgtOITTfO&=R-jmp%~Vz5F=0P+R1IU9t9cXyf|v^v(nq1rtX0J4Ec1r9q&afBTEAR*F&|dkOG00W#=!bB^;b4JOrQyj{aDHNnC4~Sqd!fdY z)1|p@R7wv(LS*hL@iB@ZiSi)sGzp^K8YS5oRB}c}s|RxxEsc%%So|R--?@vbYb=m4 z7%B`uT3Asw5o047bHwRJ0oFFnpdv7+ebx7X!=ifgR@ze>mkR%%y5JHcBbWBPpUJK! zpd5r%RLb(4P_alq*7?@v?8)G8E1CwFWVU4`oGOMyXO?;O(J2XLGf^Qq$plrWE zYC061WI+J>7IAHB>uQv9;_(I~Hb7mEz!z}#jIiXyLT8tc^Yg_eS~E>ewGwcz;VZRe z^WZThkOFU>CGswb#f~(kLxK)|=s3LJ5v?3!I>?!X%y7{KMaiq?R_o-c+Sx=!uUF!K zq8W0t^ae$EO)j85Y6HF9_vyZ2#Y@3RMmByE)KG$28^bSauL8(d{9c~;UADiIih}m2 zqxeP7qPeqKSY4B(RbF^;5hwpDGxhM5$?v3jUk}+>--#1yV*L}xsssyWL2=dTN|T=F zyB)Hi-~Mn2AYR&Q%tlB=wEATuYAl^MhM3=ZB$?U7#H1em?E!Ulb#*eco~D{wPK#D{ zq{*z9s?vamtUDAH^FK2*rieEbYa+fR1*>7n%-cm2Rdn%GpeAU@wCcMexIY95P>}%t zku8UX+P%%8gu1%Av$M04wwtVeV)ahrm}*ceI+Q?Lp;IFb3V;M~4=3Np{~q~@bz;E= zR{6j`#toTAo02Kv6*98pV!b^qU+L-o%C1eApPye)@L@*FXc zkOSNO{Zp&Lu+O89oe^fuB@<1|?<&wzV{1c*#Z)k>=Owlyr=_8xQL0n3onqumAmVe| zA4LS}!K@F$DzjvYG(a*L9(a6uva_@Myz|=9Mn3O+Fpl!!`eND!3?$j&09aj0MD+^C1e&3U~w>!tNcuV%~!; zYvj3=w6L&XF_K)S-!!${78p&1dH^^;&5~oYv;Dy7{!{8SyN36fMiKL$gW}H&4h#8% z$&JKPLW!7REB_lE9a#kuQ1nF|LF|){r_W$<3rXQ#iJL!07m(B&D^^|0gtzKI@ zJ8dw*2f(N^>Vt#Qw+AbzsjlYW$*off7K&P z4Yp+HEXq&WPD(z>#tq9I9{G-K+uNWJg4c1WQMF97<90ogQM0sDj`#ot6ltoH2Z8iN zG^w!HO4L;BB z@oWIp;RWLvYFC*C7Zenf1$G~T+PLpnlK)LsnGIa(fYRsLkr75PmV()hnRyJP$gr&r zpTH^8BtSuN37Owxl_Jf^;NX|FU_4VZGjNqG3;rq3WdDl;*KK>cNS#(CYjkMHgd-KS z>`cMo?d>fKZ*A0N;V(zwCjIA)7*Su)d3YU`@Yzj!BU#(|EQWEO_{dT1zJmI`*KxA0 z_m`mMRI&x!7(_+M0L-^>e_m9VeTLpd#m6E80>r>8Y&V4c5=v{{o=Q~7PCe(?}B*e})_cfqIk2I?fJ z6^)LMw>wW9EjD0Dz`?S7E3^F7ax)>a?zR#1cjF2QGK zXF_h<>9;CE>|lx=9F+ZYl|%!kS0XnzH&7V`4p6Z-Wp%W_ZwvT?n(I%WKI!Xcv?q~Z zW1Akxwt$52IOywOJlh1hHE{1*+uDK~@1H!u+1Xh_LIV77Ny$ME##fW??^JObk5K7GchrNK>-{*JUjvd3li_bQF%yob&{_Ty8js5*%qtJwrt@USI)B~0%>D-s}DbXKv^4ZlyL_~T8Og!mF zM@OKd^wy_cjQsTC;!XR-p1%ih|hJt^4drxRsQ`1S6*?fd_JC9qFD!VUg-K-l(wO6h~R d|Nrz%#w&8d**>3*2$D{KoRqR;xrA}R{{lSst@Ho@ literal 0 HcmV?d00001 diff --git a/docs/src/model_creation/model_visualisation.md b/docs/src/model_creation/model_visualisation.md index 6efb71b4bb..80e33d9aef 100644 --- a/docs/src/model_creation/model_visualisation.md +++ b/docs/src/model_creation/model_visualisation.md @@ -45,7 +45,10 @@ brusselator = @reaction_network begin 1, X --> ∅ end Graph(brusselator) +nothing # hide ``` +!["Brusselator Graph"](../assets/network_graphs/brusselator_graph.png) + The network graph represents species as blue nodes and reactions as orange dots. Black arrows from species to reactions indicate substrates, and are labelled with their respective stoichiometries. Similarly, black arrows from reactions to species indicate products (also labelled with their respective stoichiometries). If there are any reactions where a species affect the rate, but does not participate as a reactant, this is displayed with a dashed red arrow. This can be seen in the following [Repressilator model](@ref basic_CRN_library_repressilator): ```@example visualisation_graphs repressilator = @reaction_network begin @@ -55,7 +58,9 @@ repressilator = @reaction_network begin d, (X, Y, Z) --> ∅ end Graph(repressilator) +nothing # hide ``` +!["Repressilator Graph"](../assets/network_graphs/repressilator_graph.png) A generated graph can be saved using the `savegraph` function: ```@example visualisation_graphs @@ -67,5 +72,7 @@ rm("repressilator_graph.png") # hide Finally, a [network's reaction complexes](@ref network_analysis_reaction_complexes) (and the reactions in between these) can be displayed using the `complexgraph(brusselator)` function: ```@example visualisation_graphs complexgraph(brusselator) +nothing # hide ``` +!["Repressilator Complex Graph"](../assets/network_graphs/repressilator_complex_graph.png) Here, reaction complexes are displayed as blue nodes, and reactions in between these as black arrows. \ No newline at end of file From a1e5bfa23ade462ed4e4f91f57ed40689b8fcbfe Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sat, 1 Jun 2024 21:38:24 +0200 Subject: [PATCH 101/446] don't attempt to save graph (documenter cannot handle) --- docs/src/model_creation/model_visualisation.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/src/model_creation/model_visualisation.md b/docs/src/model_creation/model_visualisation.md index 80e33d9aef..a13f348749 100644 --- a/docs/src/model_creation/model_visualisation.md +++ b/docs/src/model_creation/model_visualisation.md @@ -63,10 +63,9 @@ nothing # hide !["Repressilator Graph"](../assets/network_graphs/repressilator_graph.png) A generated graph can be saved using the `savegraph` function: -```@example visualisation_graphs +```julia repressilator_graph = Graph(repressilator) savegraph(repressilator_graph, "repressilator_graph.png") -rm("repressilator_graph.png") # hide ``` Finally, a [network's reaction complexes](@ref network_analysis_reaction_complexes) (and the reactions in between these) can be displayed using the `complexgraph(brusselator)` function: @@ -75,4 +74,4 @@ complexgraph(brusselator) nothing # hide ``` !["Repressilator Complex Graph"](../assets/network_graphs/repressilator_complex_graph.png) -Here, reaction complexes are displayed as blue nodes, and reactions in between these as black arrows. \ No newline at end of file +Here, reaction complexes are displayed as blue nodes, and reactions in between these as black arrows. From 6d02cfe51f26b012d93a9f2e83ef536c6e3467fb Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 12 Feb 2024 19:21:39 -0500 Subject: [PATCH 102/446] init --- src/reactionsystem.jl | 6 ++++-- test/extensions/structural_identifiability.jl | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 3be6b6c06c..a5182438dc 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -1338,7 +1338,8 @@ function MT.flatten(rs::ReactionSystem; name = nameof(rs)) balanced_bc_check = false, spatial_ivs = get_sivs(rs), continuous_events = MT.continuous_events(rs), - discrete_events = MT.discrete_events(rs)) + discrete_events = MT.discrete_events(rs), + complete = false) end """ @@ -1395,7 +1396,8 @@ function ModelingToolkit.extend(sys::MT.AbstractSystem, rs::ReactionSystem; balanced_bc_check = false, spatial_ivs = sivs, continuous_events, - discrete_events) + discrete_events, + complete = false) end ### Units Handling ### diff --git a/test/extensions/structural_identifiability.jl b/test/extensions/structural_identifiability.jl index 8ef35c293b..4fe1bad359 100644 --- a/test/extensions/structural_identifiability.jl +++ b/test/extensions/structural_identifiability.jl @@ -191,7 +191,6 @@ let si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[pₑ, pₚ], known_p=[pₑ]) # Tests using model.component style (have to make system complete first). - gw_osc_complt = complete(goodwind_oscillator_catalyst) @test make_si_ode(gw_osc_complt; measured_quantities=[gw_osc_complt.M]) isa ODE @test make_si_ode(gw_osc_complt; known_p=[gw_osc_complt.pₑ]) isa ODE @test make_si_ode(gw_osc_complt; measured_quantities=[gw_osc_complt.M], known_p=[gw_osc_complt.pₑ]) isa ODE From 3f65047e7fe446af722fd37edd05aebc393e45f2 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 12 Feb 2024 19:21:46 -0500 Subject: [PATCH 103/446] up --- test/extensions/structural_identifiability.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/extensions/structural_identifiability.jl b/test/extensions/structural_identifiability.jl index 4fe1bad359..8ef35c293b 100644 --- a/test/extensions/structural_identifiability.jl +++ b/test/extensions/structural_identifiability.jl @@ -191,6 +191,7 @@ let si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[pₑ, pₚ], known_p=[pₑ]) # Tests using model.component style (have to make system complete first). + gw_osc_complt = complete(goodwind_oscillator_catalyst) @test make_si_ode(gw_osc_complt; measured_quantities=[gw_osc_complt.M]) isa ODE @test make_si_ode(gw_osc_complt; known_p=[gw_osc_complt.pₑ]) isa ODE @test make_si_ode(gw_osc_complt; measured_quantities=[gw_osc_complt.M], known_p=[gw_osc_complt.pₑ]) isa ODE From e912c133fe26ab5dfc7f6cb8c3906d4d1f9cf7d0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 18 Mar 2024 19:06:43 -0400 Subject: [PATCH 104/446] up --- src/reactionsystem.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index a5182438dc..39dde75b9c 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -437,6 +437,7 @@ end # Two-argument constructor (reactions/equations and time variable). # Calls the `make_ReactionSystem_internal`, which in turn calls the four-argument constructor. function ReactionSystem(rxs::Vector, iv = Catalyst.DEFAULT_IV; kwargs...) + println("Complete 3: ", complete) make_ReactionSystem_internal(rxs, iv, Vector{Num}(), Vector{Num}(); kwargs...) end @@ -1320,7 +1321,7 @@ Notes: - The default value of `combinatoric_ratelaws` will be the logical or of all `ReactionSystem`s. """ -function MT.flatten(rs::ReactionSystem; name = nameof(rs)) +function MT.flatten(rs::ReactionSystem; name = nameof(rs), complete = false) isempty(get_systems(rs)) && return rs # right now only NonlinearSystems and ODESystems can be handled as subsystems @@ -1339,7 +1340,7 @@ function MT.flatten(rs::ReactionSystem; name = nameof(rs)) spatial_ivs = get_sivs(rs), continuous_events = MT.continuous_events(rs), discrete_events = MT.discrete_events(rs), - complete = false) + complete = complete) end """ From ea448a1728d49b5d50d21161617d282bb0ad83c3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 18 Mar 2024 20:15:51 -0400 Subject: [PATCH 105/446] tests shoudl start passing --- src/reactionsystem.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 39dde75b9c..f2ac871bc9 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -437,7 +437,6 @@ end # Two-argument constructor (reactions/equations and time variable). # Calls the `make_ReactionSystem_internal`, which in turn calls the four-argument constructor. function ReactionSystem(rxs::Vector, iv = Catalyst.DEFAULT_IV; kwargs...) - println("Complete 3: ", complete) make_ReactionSystem_internal(rxs, iv, Vector{Num}(), Vector{Num}(); kwargs...) end From ef94f700fe0b37ed259e4083e27eb2b112792883 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 25 Mar 2024 18:23:10 -0400 Subject: [PATCH 106/446] rewors --- test/dsl/dsl_options.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/dsl/dsl_options.jl b/test/dsl/dsl_options.jl index 93ce90b541..f88c366fa2 100644 --- a/test/dsl/dsl_options.jl +++ b/test/dsl/dsl_options.jl @@ -16,7 +16,7 @@ t = default_t() ### Tests `@parameters`, `@species`, and `@variables` Options ### -# Test creating networks with/without options. +# Test creating networks with/without options. Compares they are all identical. let @reaction_network begin (k1, k2), A <--> B end @reaction_network begin @@ -155,7 +155,6 @@ end # Test inferring with stoichiometry symbols and interpolation. let @parameters k g h gg X y [isconstantspecies = true] - t = Catalyst.DEFAULT_IV @species A(t) B(t) BB(t) C(t) rni = @reaction_network inferred begin @@ -496,7 +495,7 @@ let @test_broken false # plot(sol; idxs=[:X, :Y]).series_list[2].plotattributes[:y][end] ≈ 3.0 end -# Compares programmatic and DSL system with observables. +# Compares programmatic and DSL systems with observables. let # Model declarations. rn_dsl = @reaction_network begin From 21b87b81017671d6bff58574d7d7f9f13606df57 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 25 Mar 2024 18:48:31 -0400 Subject: [PATCH 107/446] up --- test/dsl/dsl_options.jl | 777 +++++++++++++++++----------------------- 1 file changed, 325 insertions(+), 452 deletions(-) diff --git a/test/dsl/dsl_options.jl b/test/dsl/dsl_options.jl index f88c366fa2..bdd71e2cfc 100644 --- a/test/dsl/dsl_options.jl +++ b/test/dsl/dsl_options.jl @@ -1,4 +1,4 @@ -#! format: off +### Prepares Tests ### ### Prepares Tests ### @@ -16,334 +16,214 @@ t = default_t() ### Tests `@parameters`, `@species`, and `@variables` Options ### -# Test creating networks with/without options. Compares they are all identical. -let - @reaction_network begin (k1, k2), A <--> B end - @reaction_network begin - @parameters k1 k2 - (k1, k2), A <--> B - end - @reaction_network begin - @parameters k1 k2 - @species A(t) B(t) - (k1, k2), A <--> B - end - @reaction_network begin - @species A(t) B(t) - (k1, k2), A <--> B - end - - @reaction_network begin - @parameters begin - k1 - k2 - end - (k1, k2), A <--> B - end - @reaction_network begin - @species begin - A(t) - B(t) - end - (k1, k2), A <--> B - end - @reaction_network begin - @parameters begin - k1 - k2 - end - @species begin - A(t) - B(t) - end - (k1, k2), A <--> B - end - - n1 = @reaction_network rnname begin (k1, k2), A <--> B end - n2 = @reaction_network rnname begin - @parameters k1 k2 - (k1, k2), A <--> B - end - n3 = @reaction_network rnname begin - @species A(t) B(t) - (k1, k2), A <--> B - end - n4 = @reaction_network rnname begin - @parameters k1 k2 - @species A(t) B(t) - (k1, k2), A <--> B - end - n5 = @reaction_network rnname begin - (k1, k2), A <--> B - @parameters k1 k2 - end - n6 = @reaction_network rnname begin - (k1, k2), A <--> B - @species A(t) B(t) - end - n7 = @reaction_network rnname begin - (k1, k2), A <--> B - @parameters k1 k2 - @species A(t) B(t) - end - n8 = @reaction_network rnname begin - @parameters begin - k1 - k2 - end - (k1, k2), A <--> B - end - n9 = @reaction_network rnname begin - @species begin - A(t) - B(t) - end - (k1, k2), A <--> B - end - n10 = @reaction_network rnname begin - @parameters begin - k1 - k2 - end - @species begin - A(t) - B(t) - end - (k1, k2), A <--> B - end - @test all(==(n1), (n2, n3, n4, n5, n6, n7, n8, n9, n10)) -end - -# Tests that when either @species or @parameters is given, the other is inferred properly. -let - rn1 = @reaction_network begin - k*X, A + B --> 0 - end - @test issetequal(species(rn1), @species A(t) B(t)) - @test issetequal(parameters(rn1), @parameters k X) - - rn2 = @reaction_network begin - @species A(t) B(t) X(t) - k*X, A + B --> 0 - end - @test issetequal(species(rn2), @species A(t) B(t) X(t)) - @test issetequal(parameters(rn2), @parameters k) +# Sets the default `t` to use. +t = default_t() - rn3 = @reaction_network begin - @parameters k - k*X, A + B --> 0 - end - @test issetequal(species(rn3), @species A(t) B(t)) - @test issetequal(parameters(rn3), @parameters k X) +# Fetch test networks and functions. +include("../test_networks.jl") +include("../test_functions.jl") - rn4 = @reaction_network begin - @species A(t) B(t) X(t) - @parameters k - k*X, A + B --> 0 - end - @test issetequal(species(rn4), @species A(t) B(t) X(t)) - @test issetequal(parameters(rn4), @parameters k) +### Declares Testing Functions ### - rn5 = @reaction_network begin - @parameters k B [isconstantspecies=true] - k*X, A + B --> 0 - end - @test issetequal(species(rn5), @species A(t)) - @test issetequal(parameters(rn5), @parameters k B X) +function unpacksys(sys) + get_eqs(sys), get_iv(sys), get_unknowns(sys), get_ps(sys), nameof(sys), get_systems(sys) end -# Test inferring with stoichiometry symbols and interpolation. -let - @parameters k g h gg X y [isconstantspecies = true] - @species A(t) B(t) BB(t) C(t) - - rni = @reaction_network inferred begin - $k*X, $y + g*A + h*($gg)*B + $BB * C --> k*C - end - @test issetequal(species(rni), [A, B, BB, C]) - @test issetequal(parameters(rni), [k, g, h, gg, X, y]) +opname(x) = istree(x) ? nameof(operation(x)) : nameof(x) +alleq(xs, ys) = all(isequal(x, y) for (x, y) in zip(xs, ys)) - rnii = @reaction_network inferred begin - @species BB(t) - @parameters y [isconstantspecies = true] - k*X, y + g*A + h*($gg)*B + BB * C --> k*C +# Gets all the reactants in a set of equations. +function all_reactants(eqs) + all_reactants = [] + for eq in eqs + append!(all_reactants, opname.(eq.substrates)) + append!(all_reactants, opname.(eq.products)) end - @test rnii == rni + return Set{Symbol}(unique(all_reactants)) end -# Tests that when some species or parameters are left out, the others are set properly. -let - rn6 = @reaction_network begin - @species A(t) - k*X, A + B --> 0 - end - @test issetequal(species(rn6), @species A(t) B(t)) - @test issetequal(parameters(rn6), @parameters k X) - - rn7 = @reaction_network begin - @species A(t) X(t) - k*X, A + B --> 0 - end - @test issetequal(species(rn7), @species A(t) X(t) B(t)) - @test issetequal(parameters(rn7), @parameters k) - - rn7 = @reaction_network begin - @parameters B [isconstantspecies=true] - k*X, A + B --> 0 - end - @test issetequal(species(rn7), @species A(t)) - @test issetequal(parameters(rn7), @parameters B k X) - - rn8 = @reaction_network begin - @parameters B [isconstantspecies=true] k - k*X, A + B --> 0 - end - @test issetequal(species(rn8), @species A(t)) - @test issetequal(parameters(rn8), @parameters B k X) +# Gets all parameters (where every reaction rate is constant) +function all_parameters(eqs) + return Set(unique(map(eq -> opname(eq.rate), eqs))) +end - rn9 = @reaction_network begin - @parameters k1 X1 - @species A1(t) B1(t) - k1*X1, A1 + B1 --> 0 - k2*X2, A2 + B2 --> 0 - end - @test issetequal(species(rn9), @species A1(t) B1(t) A2(t) B2(t)) - @test issetequal(parameters(rn9), @parameters k1 X1 k2 X2) +# Perform basic tests. +function basic_test(rn, N, unknowns_syms, p_syms) + eqs, iv, unknowns, ps, name, systems = unpacksys(rn) + @test length(eqs) == N + @test opname(iv) == :t + @test length(unknowns) == length(unknowns_syms) + @test issetequal(map(opname, unknowns), unknowns_syms) + @test all_reactants(eqs) == Set(unknowns_syms) + @test length(ps) == length(p_syms) + @test issetequal(map(opname, ps), p_syms) +end - rn10 = @reaction_network begin - @parameters k1 X2 B2 [isconstantspecies=true] - @species A1(t) X1(t) - k1*X1, A1 + B1 --> 0 - k2*X2, A2 + B2 --> 0 - end - @test issetequal(species(rn10), @species A1(t) X1(t) B1(t) A2(t)) - @test issetequal(parameters(rn10), @parameters k1 X2 B2 k2) +### Basic Tests ### - rn11 = @reaction_network begin - @parameters k1 k2 - @species X1(t) - k1*X1, A1 + B1 --> 0 - k2*X2, A2 + B2 --> 0 - end - @test issetequal(species(rn11), @species X1(t) A1(t) A2(t) B1(t) B2(t)) - @test issetequal(parameters(rn11), @parameters k1 k2 X2) +# Test basic properties of networks. +let + basic_test(reaction_networks_standard[1], 10, [:X1, :X2, :X3], + [:p1, :p2, :p3, :k1, :k2, :k3, :k4, :d1, :d2, :d3]) + @test all_parameters(get_eqs(reaction_networks_standard[1])) == + Set([:p1, :p2, :p3, :k1, :k2, :k3, :k4, :d1, :d2, :d3]) + basic_test(reaction_networks_standard[2], 3, [:X1, :X2], [:v1, :K1, :v2, :K2, :d]) + basic_test(reaction_networks_standard[3], 10, [:X1, :X2, :X3, :X4], + [:v1, :K1, :v2, :K2, :k1, :k2, :k3, :k4, :d]) + basic_test(reaction_networks_standard[4], 8, [:X1, :X2, :X3, :X4], + [:v1, :K1, :v2, :K2, :v3, :K3, :v4, :K4, :d1, :d2, :d3, :d4]) + basic_test(reaction_networks_standard[5], 8, [:X1, :X2, :X3, :X4], + [:p, :k1, :k2, :k3, :k4, :k5, :k6, :d]) + @test all_parameters(get_eqs(reaction_networks_standard[5])) == + Set([:p, :k1, :k2, :k3, :k4, :k5, :k6, :d]) + basic_test(reaction_networks_hill[1], 4, [:X1, :X2], + [:v1, :v2, :K1, :K2, :n1, :n2, :d1, :d2]) + basic_test(reaction_networks_constraint[1], 6, [:X1, :X2, :X3], + [:k1, :k2, :k3, :k4, :k5, :k6]) + basic_test(reaction_networks_real[1], 4, [:X, :Y], [:A, :B]) + basic_test(reaction_networks_weird[1], 2, [:X], [:p, :d]) + basic_test(reaction_networks_weird[2], 4, [:X, :Y, :Z], [:k1, :k2, :k3, :k4]) end -# Checks that some created networks are identical. +# Compares networks to networks created using different arrow types. let - rn12 = @reaction_network rnname begin (k1, k2), A <--> B end - rn13 = @reaction_network rnname begin - @parameters k1 k2 - (k1, k2), A <--> B - end - rn14 = @reaction_network rnname begin - @species A(t) B(t) - (k1, k2), A <--> B - end - rn15 = @reaction_network rnname begin - @parameters k1 k2 - @species A(t) B(t) - (k1, k2), A <--> B + networks_1 = [] + networks_2 = [] + + different_arrow_1 = @reaction_network begin + (p1, p2, p3), ∅ > (X1, X2, X3) + (k1, k2), X2 ↔ X1 + 2X3 + (k3, k4), X1 ⟷ X3 + (d1, d2, d3), (X1, X2, X3) → ∅ + end + push!(networks_1, reaction_networks_standard[1]) + push!(networks_2, different_arrow_1) + + different_arrow_2 = @reaction_network begin + mmr(X2, v1, K1), ∅ → X1 + mm(X1, v2, K2), ∅ ↣ X2 + d, X1 + X2 ↦ ∅ + end + push!(networks_1, reaction_networks_standard[2]) + push!(networks_2, different_arrow_2) + + different_arrow_3 = @reaction_network begin + mm(X2, v1, K1), ∅ ⇾ X1 + mm(X3, v2, K2), ∅ ⟶ X2 + (k1, k2), X1 ⇄ X3 + (k3, k4), X3 + X2 ⇆ X4 + X1 + d, (X1, X2, X3, X4) ⟼ ∅ + end + push!(networks_1, reaction_networks_standard[3]) + push!(networks_2, different_arrow_3) + + different_arrow_4 = @reaction_network begin + mmr(X4, v1, K1), ∅ ⥟ X1 + mmr(X1, v2, K2), ∅ ⥟ X2 + mmr(X2, v3, K3), ∅ ⇀ X3 + mmr(X3, v4, K4), ∅ ⇁ X4 + (d1, d2, d3, d4), (X1, X2, X3, X4) --> ∅ + end + push!(networks_1, reaction_networks_standard[4]) + push!(networks_2, different_arrow_4) + + # Yes the name is different, I wanted one with several single direction arrows. + different_arrow_8 = @reaction_network begin + p, 2X1 < ∅ + k1, X2 ← X1 + (k2, k3), X3 ⟻ X2 + d, ∅ ↼ X3 + end + push!(networks_1, reaction_networks_standard[8]) + push!(networks_2, different_arrow_8) + + for (rn_1, rn_2) in zip(networks_1, networks_2) + for factor in [1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3] + u0 = rnd_u0(rn_1, rng; factor) + p = rnd_ps(rn_1, rng; factor) + t = rand(rng) + + @test f_eval(rn_1, u0, p, t) ≈ f_eval(rn_2, u0, p, t) + @test jac_eval(rn_1, u0, p, t) ≈ jac_eval(rn_2, u0, p, t) + @test g_eval(rn_1, u0, p, t) ≈ g_eval(rn_2, u0, p, t) + end end - @test all(==(rn12), (rn13, rn14, rn15)) end -# Checks that the rights things are put in vectors. +# Checks that some created networks are identical. let - rn18 = @reaction_network rnname begin - @parameters p d1 d2 - @species A(t) B(t) - p, 0 --> A - 1, A --> B - (d1, d2), (A, B) --> 0 - end - rn19 = @reaction_network rnname begin - p, 0 --> A - 1, A --> B - (d1, d2), (A, B) --> 0 - end - @test rn18 == rn19 - - @parameters p d1 d2 - @species A(t) B(t) - @test isequal(parameters(rn18)[1], p) - @test isequal(parameters(rn18)[2], d1) - @test isequal(parameters(rn18)[3], d2) - @test isequal(species(rn18)[1], A) - @test isequal(species(rn18)[2], B) - - rn20 = @reaction_network rnname begin - @species X(t) - @parameters S - mm(X,v,K), 0 --> Y - (k1,k2), 2Y <--> Y2 - d*Y, S*(Y2+Y) --> 0 - end - rn21 = @reaction_network rnname begin - @species X(t) Y(t) Y2(t) - @parameters v K k1 k2 d S - mm(X,v,K), 0 --> Y - (k1,k2), 2Y <--> Y2 - d*Y, S*(Y2+Y) --> 0 - end - rn22 = @reaction_network rnname begin - @species X(t) Y2(t) - @parameters d k1 - mm(X,v,K), 0 --> Y - (k1,k2), 2Y <--> Y2 - d*Y, S*(Y2+Y) --> 0 - end - @test all(==(rn20), (rn21, rn22)) - @parameters v K k1 k2 d S - @species X(t) Y(t) Y2(t) - @test issetequal(parameters(rn22),[v K k1 k2 d S]) - @test issetequal(species(rn22), [X Y Y2]) + # Declares network. + differently_written_5 = @reaction_network begin + q, ∅ → Y1 + (l1, l2), Y1 ⟷ Y2 + (l3, l4), Y2 ⟷ Y3 + (l5, l6), Y3 ⟷ Y4 + c, Y4 → ∅ + end + + # Computes initial conditions/parameter values. + u0_vals = rand(rng, length(species(differently_written_5))) + ps_vals = rand(rng, length(parameters(differently_written_5))) + u0_1 = [:X1 => u0_vals[1], :X2 => u0_vals[2], :X3 => u0_vals[3], :X4 => u0_vals[4]] + u0_2 = [:Y1 => u0_vals[1], :Y2 => u0_vals[2], :Y3 => u0_vals[3], :Y4 => u0_vals[4]] + ps_1 = [:p => ps_vals[1], :k1 => ps_vals[2], :k2 => ps_vals[3], :k3 => ps_vals[4], + :k4 => ps_vals[5], :k5 => ps_vals[6], :k6 => ps_vals[7], :d => ps_vals[8]] + ps_2 = [:q => ps_vals[1], :l1 => ps_vals[2], :l2 => ps_vals[3], :l3 => ps_vals[4], + :l4 => ps_vals[5], :l5 => ps_vals[6], :l6 => ps_vals[7], :c => ps_vals[8]] + t = rand(rng) + + # Checks equivalence. + rn_1 = reaction_networks_standard[5] + rn_2 = differently_written_5 + @test f_eval(rn_1, u0_1, ps_1, t) ≈ f_eval(rn_2, u0_2, ps_2, t) + @test jac_eval(rn_1, u0_1, ps_1, t) ≈ jac_eval(rn_2, u0_2, ps_2, t) + @test g_eval(rn_1, u0_1, ps_1, t) ≈ g_eval(rn_2, u0_2, ps_2, t) end -# Tests that defaults work. +# Compares networks to networks written in different ways. let - rn26 = @reaction_network rnname begin - @parameters p=1.0 d1 d2=5 - @species A(t) B(t)=4 - p, 0 --> A - 1, A --> B - (d1, d2), (A, B) --> 0 - end - - rn27 = @reaction_network rnname begin - @parameters p1=1.0 p2=2.0 k1=4.0 k2=5.0 v=8.0 K=9.0 n=3 d=10.0 - @species X(t)=4.0 Y(t)=3.0 X2Y(t)=2.0 Z(t)=1.0 - (p1,p2), 0 --> (X,Y) - (k1,k2), 2X + Y --> X2Y - hill(X2Y,v,K,n), 0 --> Z - d, (X,Y,X2Y,Z) --> 0 - end - u0_27 = [] - p_27 = [] - - rn28 = @reaction_network rnname begin - @parameters p1=1.0 p2 k1=4.0 k2 v=8.0 K n=3 d - @species X(t)=4.0 Y(t) X2Y(t) Z(t)=1.0 - (p1,p2), 0 --> (X,Y) - (k1,k2), 2X + Y --> X2Y - hill(X2Y,v,K,n), 0 --> Z - d, (X,Y,X2Y,Z) --> 0 - end - u0_28 = symmap_to_varmap(rn28, [:p2=>2.0, :k2=>5.0, :K=>9.0, :d=>10.0]) - p_28 = symmap_to_varmap(rn28, [:Y=>3.0, :X2Y=>2.0]) - defs28 = Dict(Iterators.flatten((u0_28, p_28))) - - rn29 = @reaction_network rnname begin - @parameters p1 p2 k1 k2 v K n d - @species X(t) Y(t) X2Y(t) Z(t) - (p1,p2), 0 --> (X,Y) - (k1,k2), 2X + Y --> X2Y - hill(X2Y,v,K,n), 0 --> Z - d, (X,Y,X2Y,Z) --> 0 - end - u0_29 = symmap_to_varmap(rn29, [:p1=>1.0, :p2=>2.0, :k1=>4.0, :k2=>5.0, :v=>8.0, :K=>9.0, :n=>3, :d=>10.0]) - p_29 = symmap_to_varmap(rn29, [:X=>4.0, :Y=>3.0, :X2Y=>2.0, :Z=>1.0]) - defs29 = Dict(Iterators.flatten((u0_29, p_29))) + networks_1 = [] + networks_2 = [] + + # Unfold reactions. + differently_written_6 = @reaction_network begin + p1, ∅ → X1 + p2, ∅ → X2 + k1, 2X1 → X3 + k2, X3 → 2X1 + k3, X2 + X3 → 4X4 + k4, 4X4 → X2 + X3 + k5, X4 + X1 → 2X3 + k6, 2X3 → X4 + X1 + d, X1 → ∅ + d, X2 → ∅ + d, X3 → ∅ + d, X4 → ∅ + end + push!(networks_1, reaction_networks_standard[6]) + push!(networks_2, differently_written_6) + + # Ignore mass action. + differently_written_7 = @reaction_network begin + @parameters p1 p2 p3 k1 k2 k3 v1 K1 d1 d2 d3 d4 d5 + (p1, p2, p3), ∅ ⇒ (X1, X2, X3) + (k1 * X1 * X2^2 / 2, k2 * X4), X1 + 2X2 ⟺ X4 + (mm(X3, v1, K1) * X4, k3 * X5), X4 ⇔ X5 + (d1 * X1, d2 * X2, d3 * X3, d4 * X4, d5 * X5), ∅ ⟽ (X1, X2, X3, X4, X5) + end + push!(networks_1, reaction_networks_standard[7]) + push!(networks_2, differently_written_7) + + # Ignore mass action new arrows. + differently_written_8 = @reaction_network begin + @parameters p1 p2 p3 k1 k2 k3 v1 K1 d1 d2 d3 d4 d5 + (p1, p2, p3), ∅ => (X1, X2, X3) + (k1 * X1 * X2^2 / 2, k2 * X4), X1 + 2X2 ⟺ X4 + (mm(X3, v1, K1) * X4, k3 * X5), X4 ⇔ X5 + (d1 * X1, d2 * X2, d3 * X3, d4 * X4, d5 * X5), ∅ <= (X1, X2, X3, X4, X5) + end + push!(networks_1, reaction_networks_standard[7]) + push!(networks_2, differently_written_8) @test ModelingToolkit.defaults(rn27) == defs29 @test merge(ModelingToolkit.defaults(rn28), defs28) == ModelingToolkit.defaults(rn27) @@ -458,9 +338,6 @@ let X ~ Xi + Xa Y ~ Y1 + Y2 end - (p,d), 0 <--> Xi - (k1,k2), Xi <--> Xa - (k3,k4), Y1 <--> Y2 end @unpack X, Xi, Xa, Y, Y1, Y2, p, d, k1, k2, k3, k4 = rn @@ -495,18 +372,24 @@ let @test_broken false # plot(sol; idxs=[:X, :Y]).series_list[2].plotattributes[:y][end] ≈ 3.0 end -# Compares programmatic and DSL systems with observables. +# Compares networks to networks written without parameters, let - # Model declarations. - rn_dsl = @reaction_network begin - @observables begin - X ~ x + 2x2y - Y ~ y + x2y - end - k, 0 --> (x, y) - (kB, kD), 2x + y <--> x2y - d, (x,y,x2y) --> 0 - end + networks_1 = [] + networks_2 = [] + parameter_sets = [] + + # Different parameter and variable names. + no_parameters_9 = @reaction_network begin + (1.5, 1, 2), ∅ ⟶ (X1, X2, X3) + (0.01, 2.3, 1001), (X1, X2, X3) ⟶ ∅ + (π, 42), X1 + X2 ⟷ X3 + (19.9, 999.99), X3 ⟷ X4 + (sqrt(3.7), exp(1.9)), X4 ⟷ X1 + X2 + end + push!(networks_1, reaction_networks_standard[9]) + push!(networks_2, no_parameters_9) + push!(parameter_sets, [:p1 => 1.5, :p2 => 1, :p3 => 2, :d1 => 0.01, :d2 => 2.3, :d3 => 1001, + :k1 => π, :k2 => 42, :k3 => 19.9, :k4 => 999.99, :k5 => sqrt(3.7), :k6 => exp(1.9)]) @variables X(t) Y(t) @species x(t), y(t), x2y(t) @@ -522,21 +405,17 @@ let @named rn_prog = ReactionSystem([r1, r2, r3, r4, r5, r6, r7], t, [x, y, x2y], [k, kB, kD, d]; observed = obs_eqs) rn_prog = complete(rn_prog) - # Make simulations. - u0 = [x => 1.0, y => 0.5, x2y => 0.0] - tspan = (0.0, 15.0) - ps = [k => 1.0, kD => 0.1, kB => 0.5, d => 5.0] - oprob_dsl = ODEProblem(rn_dsl, u0, tspan, ps) - oprob_prog = ODEProblem(rn_prog, u0, tspan, ps) - - sol_dsl = solve(oprob_dsl, Tsit5(); saveat=0.1) - sol_prog = solve(oprob_prog, Tsit5(); saveat=0.1) - - # Tests observables equal in both cases. - @test oprob_dsl[:X] == oprob_prog[:X] - @test oprob_dsl[:Y] == oprob_prog[:Y] - @test sol_dsl[:X] == sol_prog[:X] - @test sol_dsl[:Y] == sol_prog[:Y] + + for (rn_1, rn_2, p_1) in zip(networks_1, networks_2, parameter_sets) + for factor in [1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3] + u0 = rnd_u0(rn_1, rng; factor) + t = rand(rng) + + @test f_eval(rn_1, u0, p_1, t) ≈ f_eval(rn_2, u0, [], t) + @test jac_eval(rn_1, u0, p_1, t) ≈ jac_eval(rn_2, u0, [], t) + @test g_eval(rn_1, u0, p_1, t) ≈ g_eval(rn_2, u0, [], t) + end + end end # Tests for complicated observable formula. @@ -556,32 +435,51 @@ let u0 = Dict([:X1 => 1.0, :X2 => 2.0, :X3 => 3.0, :X4 => 4.0]) ps = Dict([:p => 1.0, :d => 0.2, :k1 => 1.5, :k2 => 1.5, :k3 => 5.0, :k4 => 5.0, :op_1 => 1.5, :op_2 => 1.5]) - oprob = ODEProblem(rn, u0, (0.0, 1000.0), ps) - sol = solve(oprob, Tsit5()) + rxs_1 = [Reaction(p, nothing, [X1], nothing, [2]), + Reaction(k1, [X1], [X2], [1], [1]), + Reaction(k2, [X2], [X3], [1], [1]), + Reaction(k3, [X2], [X3], [1], [1]), + Reaction(d, [X3], nothing, [1], nothing)] + @named rs_1 = ReactionSystem(rxs_1, t, [X1, X2, X3], [p, k1, k2, k3, d]) + push!(identical_networks_4, reaction_networks_standard[8] => rs_1) @test sol[:X][1] == u0[:X1]^2 + ps[:op_1]*(u0[:X2] + 2*u0[:X3]) + u0[:X1]*u0[:X4]/ps[:op_2] + ps[:p] end -# Checks that ivs are correctly found. -let - rn = @reaction_network begin - @ivs t x y - @species V1(t) V2(t,x) V3(t, y) W1(t) W2(t, y) - @observables begin - V ~ V1 + 2V2 + 3V3 - W ~ W1 + W2 + rxs_3 = [Reaction(k1, [X1], [X2], [1], [1]), + Reaction(0, [X2], [X3], [1], [1]), + Reaction(k2, [X3], [X4], [1], [1]), + Reaction(k3, [X4], [X5], [1], [1])] + @named rs_3 = ReactionSystem(rxs_3, t, [X1, X2, X3, X4, X5], [k1, k2, k3]) + push!(identical_networks_4, reaction_networks_weird[7] => rs_3) + + for networks in identical_networks_4 + @test isequal(get_iv(networks[1]), get_iv(networks[2])) + @test alleq(get_unknowns(networks[1]), get_unknowns(networks[2])) + @test alleq(get_ps(networks[1]), get_ps(networks[2])) + @test ModelingToolkit.get_systems(networks[1]) == + ModelingToolkit.get_systems(networks[2]) + @test length(get_eqs(networks[1])) == length(get_eqs(networks[2])) + for (e1, e2) in zip(get_eqs(networks[1]), get_eqs(networks[2])) + @test isequal(e1.rate, e2.rate) + @test isequal(e1.substrates, e2.substrates) + @test isequal(e1.products, e2.products) + @test isequal(e1.substoich, e2.substoich) + @test isequal(e1.prodstoich, e2.prodstoich) + @test isequal(e1.netstoich, e2.netstoich) + @test isequal(e1.only_use_rate, e2.only_use_rate) end end - V,W = getfield.(observed(rn), :lhs) - @test isequal(arguments(ModelingToolkit.unwrap(V)), Any[rn.iv, rn.sivs[1], rn.sivs[2]]) - @test isequal(arguments(ModelingToolkit.unwrap(W)), Any[rn.iv, rn.sivs[2]]) end -# Checks that metadata is written properly. +### Tests Usage of Various Symbols ### + +# Tests that time is handled properly. let - rn = @reaction_network rn_observed begin - @observables (X, [description="my_description"]) ~ X1 + X2 - k, 0 --> X1 + X2 + time_network = @reaction_network begin + (t, k2), X1 ↔ X2 + (k3, t), X2 ↔ X3 + (t, k6), X3 ↔ X1 end @test ModelingToolkit.getdescription(observed(rn)[1].lhs) == "my_description" end @@ -604,64 +502,62 @@ let @test isequal(observed(rn1)[1].lhs.metadata, observed(rn2)[1].lhs.metadata) @test isequal(unknowns(rn1), unknowns(rn2)) - # Case with metadata. - rn3 = @reaction_network rn_observed begin - @observables (X, [description="description"]) ~ X1 + X2 - k, 0 --> X1 + X2 - end - rn4 = @reaction_network rn_observed begin - @variables X(t) [description="description"] - @observables X ~ X1 + X2 - k, 0 --> X1 + X2 + @test f_eval(reaction_networks_constraint[1], u, p_1, τ) ≈ f_eval(time_network, u, p_2, τ) + @test jac_eval(reaction_networks_constraint[1], u, p_1, τ) ≈ jac_eval(time_network, u, p_2, τ) + @test g_eval(reaction_networks_constraint[1], u, p_1, τ) ≈ g_eval(time_network, u, p_2, τ) end - @test isequal(observed(rn3)[1].rhs, observed(rn4)[1].rhs) - @test isequal(observed(rn3)[1].lhs.metadata, observed(rn4)[1].lhs.metadata) - @test isequal(unknowns(rn3), unknowns(rn4)) end -# Tests for interpolation into the observables option. +# Check that various symbols can be used as species/parameter names. let - # Interpolation into lhs. - @species X [description="An observable"] - rn1 = @reaction_network begin - @observables $X ~ X1 + X2 - (k1, k2), X1 <--> X2 + @reaction_network begin + (a, A), n ⟷ N + (b, B), o ⟷ O + (c, C), p ⟷ P + (d, D), q ⟷ Q + (e, E), r ⟷ R + (f, F), s ⟷ S + (g, G), u ⟷ U + (h, H), v ⟷ V + (j, J), w ⟷ W + (k, K), x ⟷ X + (l, L), y ⟷ Y + (m, M), z ⟷ Z end @test isequal(observed(rn1)[1].lhs, X) @test ModelingToolkit.getdescription(rn1.X) == "An observable" @test isspecies(rn1.X) @test length(unknowns(rn1)) == 2 - # Interpolation into rhs. - @parameters n [description="A parameter"] - @species S(t) - rn2 = @reaction_network begin - @observables Stot ~ $S + $n*Sn - (kB, kD), $n*S <--> Sn - end - @unpack Stot, Sn, kD, kB = rn2 + @reaction_network begin (1.0, 1.0), i ⟷ T end - u0 = Dict([S => 5.0, Sn => 1.0]) - ps = Dict([n => 2, kB => 1.0, kD => 1.0]) - oprob = ODEProblem(rn2, u0, (0.0, 1.0), ps) + @reaction_network begin + (å, Å), ü ⟷ Ü + (ä, Ä), ñ ⟷ Ñ + (ö, Ö), æ ⟷ Æ + end - @test issetequal(Symbolics.get_variables(observed(rn2)[1].rhs), [S, n, Sn]) - @test oprob[Stot] == u0[S] + ps[n]*u0[Sn] - @test length(unknowns(rn2)) == 2 + @reaction_network begin + (α, Α), ν ⟷ Ν + (β, Β), ξ ⟷ Ξ + (γ, γ), ο ⟷ Ο + (δ, Δ), Π ⟷ Π + (ϵ, Ε), ρ ⟷ Ρ + (ζ, Ζ), σ ⟷ Σ + (η, Η), τ ⟷ Τ + (θ, Θ), υ ⟷ Υ + (ι, Ι), ϕ ⟷ Φ + (κ, κ), χ ⟷ Χ + (λ, Λ), ψ ↔ Ψ + (μ, Μ), ω ⟷ Ω + end end # Tests specific declaration of observables as species/variables. let rn = @reaction_network begin - @species X(t) - @variables Y(t) - @observables begin - X ~ X + 2X2 - Y ~ Y1 + Y2 - Z ~ X + Y - end - (kB,kD), 2X <--> X2 - (k1,k2), Y1 <--> Y2 + k1, S + I --> 2I + k2, I --> R end @test isspecies(rn.X) @@ -677,69 +573,46 @@ let k, 0 --> X1 + X2 end - # System with observable in observable formula. - @test_throws Exception @eval @reaction_network begin - @observables begin - X ~ X1 + X2 - X2 ~ 2X - end - (p,d), 0 <--> X1 + X2 - end - - # Multiple @observables options - @test_throws Exception @eval @reaction_network begin - @observables X ~ X1 + X2 - @observables Y ~ Y1 + Y2 - k, 0 --> X1 + X2 - k, 0 --> Y1 + Y2 - end - @test_throws Exception @eval @reaction_network begin - @observables begin - X ~ X1 + X2 - end - @observables begin - X ~ 2(X1 + X2) - end - (p,d), 0 <--> X1 + X2 - end - - # Default value for compound. - @test_throws Exception @eval @reaction_network begin - @observables (X = 1.0) ~ X1 + X2 - k, 0 --> X1 + X2 - end - - # Forbidden symbols as observable names. - @test_throws Exception @eval @reaction_network begin - @observables t ~ t1 + t2 - k, 0 --> t1 + t2 - end - @test_throws Exception @eval @reaction_network begin - @observables im ~ i + m - k, 0 --> i + m - end - - # Non-trivial observables expression. - @test_throws Exception @eval @reaction_network begin - @observables X - X1 ~ X2 - k, 0 --> X1 + X2 + rn2 = @reaction_network arrowtest begin + a1, C --> 0 + a2, 0 --> C + k1, A + B --> C + k2, C --> A + B + b1, B --> 0 end - # Occurrence of undeclared dependants. - @test_throws Exception @eval @reaction_network begin - @observables X ~ X1 + X2 - k, 0 --> X1 - end + @test rn1 == rn2 +end - # Interpolation and explicit declaration of an observable. - @variables X(t) - @test_throws Exception @eval @reaction_network begin - @variables X(t) - @observables $X ~ X1 + X2 - (k1,k2), X1 <--> X2 - end +# Tests arrow variants in "@reaction" macro . +let + @test isequal((@reaction k, 0 --> X), (@reaction k, X <-- 0)) + @test isequal((@reaction k, 0 --> X), (@reaction k, X ⟻ 0)) + @test isequal((@reaction k, 0 --> X), (@reaction k, 0 → X)) + @test isequal((@reaction k, 0 --> X), (@reaction k, 0 ⥟ X)) end +# Test that symbols with special mean, or that are forbidden, are handled properly. +let + test_network = @reaction_network begin t * k, X --> ∅ end + @test length(species(test_network)) == 1 + @test length(parameters(test_network)) == 1 + + test_network = @reaction_network begin π, X --> ∅ end + @test length(species(test_network)) == 1 + @test length(parameters(test_network)) == 0 + @test reactions(test_network)[1].rate == π + + test_network = @reaction_network begin pi, X --> ∅ end + @test length(species(test_network)) == 1 + @test length(parameters(test_network)) == 0 + @test reactions(test_network)[1].rate == pi + + test_network = @reaction_network begin ℯ, X --> ∅ end + @test length(species(test_network)) == 1 + @test length(parameters(test_network)) == 0 + @test reactions(test_network)[1].rate == ℯ + ### Test `@equations` Option for Coupled CRN/Equations Models ### From 33b68076cc44a821095ad6a6be1ae90836b504b5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 28 Mar 2024 14:20:34 -0400 Subject: [PATCH 108/446] init --- src/Catalyst.jl | 5 + .../serialisation_support.jl | 232 ++++++++++++++++++ .../serialise_reactionsystem.jl | 75 ++++++ 3 files changed, 312 insertions(+) create mode 100644 src/model_serialisation/serialisation_support.jl create mode 100644 src/model_serialisation/serialise_reactionsystem.jl diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 42433bcd2f..fd2f10e2d6 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -153,6 +153,11 @@ export balance_reaction, balance_system include("steady_state_stability.jl") export steady_state_stability, steady_state_jac +# ReactionSystem serialisation. +include("model_serialisation/serialise_reactionsystem.jl") +include("model_serialisation/serialisation_support.jl") +export save_reaction_network + ### Extensions ### # HomotopyContinuation diff --git a/src/model_serialisation/serialisation_support.jl b/src/model_serialisation/serialisation_support.jl new file mode 100644 index 0000000000..43805ce126 --- /dev/null +++ b/src/model_serialisation/serialisation_support.jl @@ -0,0 +1,232 @@ +### Generic Functions ### + +# Function which handles the addition of a single component to the file string. +function push_component(file_text::String, rn::ReactionSystem, annotate::Bool, comp_funcs::Tuple) + has_component, get_comp_string, get_comp_annotation = comp_funcs + has_component(rn) || (return (file_text, false)) + write_string = "\n" * get_comp_string(rn) + annotate && (write_string = "\n\n# " * get_comp_annotation(rn) * write_string) + return (file_text * write_string, true) +end + +# Converts a Num to a String. +function wrapped_num_2_string(num) + return String(Symbol(num)) +end + +# Converts a vector of Nums to a single string with all (e.g. [X(t), Y(t)] becomes " X(t) Y(t)"). +function wrapped_nums_2_string(nums) + return prod([" " * wrapped_num_2_string(num) for num in nums]) +end + +# Converts a vector of Symbolics to a (uncalled) vector (e.g. [X(t), Y(t), p] becomes " [X, Y, p]"). +function nums_2_uncalled_strin_vec(nums) + return "$(convert(Vector{Any}, uncall.(nums)))"[4:end] +end + +# Function for converting a Symbolics of the form `X(t)` to the form `X`. +function uncall(var) + istree(var) ? Sym{Real}(Symbolics.getname(var)) : var +end + +function nums_2_uncalled_strin_vec(nums) + "$(convert(Vector{Any}, uncall.(nums)))"[4:end] +end + +# Converts a numeric expression to a string. `uncall_dict` is required to convert stuff like +# `p1*X(t) + p2` to `p1*X + p2`. +function num_2_string(num; uncall_dict = make_uncall_dict(Symbolics.get_variables(num))) + uncalled_num = substitute(num, uncall_dict) + return repr(uncalled_num) +end + +# For a list of symbolics, makes an "uncall dict". It permits the conversion of e.g. `X(t)` to `X`. +function make_uncall_dict(syms) + return Dict([sym => uncall(Symbolics.unwrap(sym)) for sym in syms]) +end + +### Generic Unsupported Component FUnctions ### + +# Generic function for creating an string for an unsupported argument. +function get_unsupported_comp_string(component::String) + @warn "Writing ReactionSystem models with $(component) is currently not supported. This field is not written to the file." + return "" +end + +# Generic function for creating the annotation string for an unsupported argument. +function get_unsupported_comp_annotation(component::String) + return "$(component): (OBS: Currently not supported, and hence empty)" +end + + +### Handles Independent Variables ### + +# Checks if the reaction system have any independent variable. True for all valid reaction systems. +function has_iv(rn::ReactionSystem) + return true +end + +# Extract a string which declares the system's independent variable. +function get_iv_string(rn::ReactionSystem) + return "@variables $(num_2_string(ModelingToolkit.get_iv(rn)))" +end + +# Creates an annotation for the system's independent variable. +function get_iv_annotation(rn::ReactionSystem) + return "Independent variable:" +end + +# Combines the 3 independent variable-related functions in a constant tuple. +IV_FS = (has_iv, get_iv_string, get_iv_annotation) + + +### Handles Spatial Independent Variables ### + +# Checks if the reaction system have any spatial independent variables. +function has_sivs(rn::ReactionSystem) + return length(rn.sivs) != 0 +end + +# Extract a string which declares the system's spatial independent variables. +function get_sivs_string(rn::ReactionSystem) + return "sivs = @variables$(wrapped_nums_2_string(rn.sivs))" +end + +# Creates an annotation for the system's spatial independent variables. +function get_sivs_annotation(rn::ReactionSystem) + return "Spatial independent variables:" +end + +# Combines the 3 independent variables-related functions in a constant tuple. +SIVS_FS = (has_sivs, get_sivs_string, get_sivs_annotation) + + +### Handles Species ### + +# Checks if the reaction system have any species. +function has_species(rn::ReactionSystem) + return length(species(rn)) != 0 +end + +# Extract a string which declares the system's species. +function get_species_string(rn::ReactionSystem) + return "sps = @species$(wrapped_nums_2_string(species(rn)))" +end + +# Creates an annotation for the system's species. +function get_species_annotation(rn::ReactionSystem) + return "Species:" +end + +# Combines the 3 species-related functions in a constant tuple. +SPECIES_FS = (has_species, get_species_string, get_species_annotation) + + +### Handles Variables ### + +# Checks if the reaction system have any variables. +function has_variables(rn::ReactionSystem) + return length(unknowns(rn)) > length(species(rn)) +end + +# Extract a string which declares the system's variables. +function get_variables_string(rn::ReactionSystem) + variables = filter(!isspecies, unknowns(rn)) + return "vars = @variables$(wrapped_nums_2_string(variables))" +end + +# Creates an annotation for the system's . +function get_variables_annotation(rn::ReactionSystem) + return "Variables:" +end + +# Combines the 3 variables-related functions in a constant tuple. +VARIABLES_FS = (has_variables, get_variables_string, get_variables_annotation) + + +### Handles Parameters ### + +# Checks if the reaction system have any parameters. +function has_parameters(rn::ReactionSystem) + return length(parameters(rn)) != 0 +end + +# Extract a string which declares the system's parameters. +function get_parameters_string(rn::ReactionSystem) + return "ps = @parameters$(wrapped_nums_2_string(parameters(rn)))" +end + +# Creates an annotation for the system's parameters. +function get_parameters_annotation(rn::ReactionSystem) + return "Parameters:" +end + +# Combines the 3 parameters-related functions in a constant tuple. +PARAMETERS_FS = (has_parameters, get_parameters_string, get_parameters_annotation) + + +### Handles Reactions ### + +# Checks if the reaction system have any reactions. +function has_reactions(rn::ReactionSystem) + return length(reactions(rn)) != 0 +end + +# Extract a string which declares the system's reactions. +function get_reactions_string(rn::ReactionSystem) + uncall_dict = make_uncall_dict(unknowns(rn)) + rxs_string = "rxs = [" + for rx in reactions(rn) + rxs_string = rxs_string * "\n\t" * reaction_string(rx, uncall_dict) * "," + end + + # Updates the string (including removing the last `,`) and returns in. + return rxs_string[1:end-1] * "\n]" +end + + +# Creates a string that corresponds to the declaration of a single `Reaction`. +function reaction_string(rx::Reaction, uncall_dict) + # Prepares the `Reaction` declaration components. + rate = num_2_string(rx.rate; uncall_dict) + substrates = isempty(rx.substrates) ? "nothing" : nums_2_uncalled_strin_vec(rx.substrates) + products = isempty(rx.products) ? "nothing" : nums_2_uncalled_strin_vec(rx.products) + substoich = isempty(rx.substoich) ? "nothing" : nums_2_uncalled_strin_vec(rx.substoich) + prodstoich = isempty(rx.prodstoich) ? "nothing" : nums_2_uncalled_strin_vec(rx.prodstoich) + + # Creates the full expression, including adding kwargs (`only_use_rate` and `metadata`). + rx_string = "Reaction($rate, $(substrates), $(products), $(substoich), $(prodstoich)" + if rx.only_use_rate + rx_string = rx_string * "; only_use_rate = true" + end + return rx_string * ")" +end + +# Creates an annotation for the system's reactions. +function get_reactions_annotation(rn::ReactionSystem) + return "Reactions:" +end + +# Combines the 3 reactions-related functions in a constant tuple. +REACTIONS_FS = (has_reactions, get_reactions_string, get_reactions_annotation) + + +### Handles Equations ### + +# Checks if the reaction system have any equations. +function has_equations(rn::ReactionSystem) + return length(equations(rn)) > length(reactions(rn)) +end + +# Extract a string which declares the system's equations. +function get_equations_string(rn::ReactionSystem) + get_unsupported_comp_string("equations") +end + +# Creates an annotation for the system's equations. +function get_equations_annotation(rn::ReactionSystem) + get_unsupported_comp_annotation("Equations") +end + +# Combines the 3 equations-related functions in a constant tuple. +EQUATIONS_FS = (has_equations, get_equations_string, get_equations_annotation) \ No newline at end of file diff --git a/src/model_serialisation/serialise_reactionsystem.jl b/src/model_serialisation/serialise_reactionsystem.jl new file mode 100644 index 0000000000..b42d34bbe5 --- /dev/null +++ b/src/model_serialisation/serialise_reactionsystem.jl @@ -0,0 +1,75 @@ +function save_reaction_network(filename::String, rn::ReactionSystem; annotate = true) + # Initiates the file string. + file_text = "" + + # Goes through each type of system component, potentially adding it to the string. + file_text, has_iv = push_component(file_text, rn, annotate, IV_FS) + file_text, has_sivs = push_component(file_text, rn, annotate, SIVS_FS) + file_text, has_species = push_component(file_text, rn, annotate, SPECIES_FS) + file_text, has_variables = push_component(file_text, rn, annotate, VARIABLES_FS) + file_text, has_parameters = push_component(file_text, rn, annotate, PARAMETERS_FS) + file_text, has_reactions = push_component(file_text, rn, annotate, REACTIONS_FS) + file_text, has_equations = push_component(file_text, rn, annotate, EQUATIONS_FS) + + # Finalises the system. Creates the final `ReactionSystem` call. + rs_creation_code = make_reaction_system_call(rn, file_text, annotate, + has_sivs, has_species, has_variables, + has_parameters, has_reactions, has_equations) + annotate || (file_text = "\n" * file_text) + file_text = "let" * file_text * "\n\n" * rs_creation_code * "\n\nend" + + # Writes the model to a file. Then, returns nothing. + open(filename, "w") do file + write(file, file_text) + end + return nothing +end + +# Takes the actual text which creates the model, and wraps it in a `let ... end` statement and A +# ReactionSystem call, to create the final file text. +function make_reaction_system_call(rs::ReactionSystem, file_text, annotate, has_sivs, has_species, has_variables, has_parameters, has_reactions, has_equations) + # Creates base call. + iv = Catalyst.get_iv(rs) + if has_reactions && has_equations + eqs = "[rxs; eqs]" + elseif has_reactions + eqs = "rxs" + elseif has_equations + eqs = "eqs" + else + eqs = "[]" + end + if has_species && has_variables + unknowns = "[sps; vars]" + elseif has_species + unknowns = "sps" + elseif has_variables + unknowns = "vars" + else + unknowns = "[]" + end + if has_parameters + ps = "ps" + else + ps = "[]" + end + reaction_system_string = "ReactionSystem($eqs, $iv, $unknowns, $ps" + + # Appends additional (optional) arguments. + if Base.isidentifier(Catalyst.getname(rs)) + rs_name = ":$(Catalyst.getname(rs))" + else + rs_name = "Symbol(\"$(Catalyst.getname(rs))\")" + end + reaction_system_string = reaction_system_string * "; name = $(rs_name)" + reaction_system_string = reaction_system_string * ")" + + # Returns the full call. + if !ModelingToolkit.iscomplete(rs) + reaction_system_string = "rs = $(reaction_system_string)\ncomplete(rs)" + end + if annotate + reaction_system_string = "# Declares ReactionSystem model:\n" * reaction_system_string + end + return reaction_system_string +end \ No newline at end of file From 0c5ac53ea91086fc17c4dbe1234cfc504cdf9e84 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 28 Mar 2024 15:28:36 -0400 Subject: [PATCH 109/446] up --- .../serialisation_support.jl | 218 ++---------------- src/model_serialisation/serialise_fields.jl | 171 ++++++++++++++ 2 files changed, 193 insertions(+), 196 deletions(-) create mode 100644 src/model_serialisation/serialise_fields.jl diff --git a/src/model_serialisation/serialisation_support.jl b/src/model_serialisation/serialisation_support.jl index 43805ce126..43e8f4c033 100644 --- a/src/model_serialisation/serialisation_support.jl +++ b/src/model_serialisation/serialisation_support.jl @@ -1,4 +1,4 @@ -### Generic Functions ### +### Field Serialisation Support Functions ### # Function which handles the addition of a single component to the file string. function push_component(file_text::String, rn::ReactionSystem, annotate::Bool, comp_funcs::Tuple) @@ -9,6 +9,20 @@ function push_component(file_text::String, rn::ReactionSystem, annotate::Bool, c return (file_text * write_string, true) end +# Generic function for creating an string for an unsupported argument. +function get_unsupported_comp_string(component::String) + @warn "Writing ReactionSystem models with $(component) is currently not supported. This field is not written to the file." + return "" +end + +# Generic function for creating the annotation string for an unsupported argument. +function get_unsupported_comp_annotation(component::String) + return "$(component): (OBS: Currently not supported, and hence empty)" +end + + +### Symbolics String Conversions ### + # Converts a Num to a String. function wrapped_num_2_string(num) return String(Symbol(num)) @@ -24,15 +38,6 @@ function nums_2_uncalled_strin_vec(nums) return "$(convert(Vector{Any}, uncall.(nums)))"[4:end] end -# Function for converting a Symbolics of the form `X(t)` to the form `X`. -function uncall(var) - istree(var) ? Sym{Real}(Symbolics.getname(var)) : var -end - -function nums_2_uncalled_strin_vec(nums) - "$(convert(Vector{Any}, uncall.(nums)))"[4:end] -end - # Converts a numeric expression to a string. `uncall_dict` is required to convert stuff like # `p1*X(t) + p2` to `p1*X + p2`. function num_2_string(num; uncall_dict = make_uncall_dict(Symbolics.get_variables(num))) @@ -40,193 +45,14 @@ function num_2_string(num; uncall_dict = make_uncall_dict(Symbolics.get_variable return repr(uncalled_num) end -# For a list of symbolics, makes an "uncall dict". It permits the conversion of e.g. `X(t)` to `X`. -function make_uncall_dict(syms) - return Dict([sym => uncall(Symbolics.unwrap(sym)) for sym in syms]) -end - -### Generic Unsupported Component FUnctions ### - -# Generic function for creating an string for an unsupported argument. -function get_unsupported_comp_string(component::String) - @warn "Writing ReactionSystem models with $(component) is currently not supported. This field is not written to the file." - return "" -end - -# Generic function for creating the annotation string for an unsupported argument. -function get_unsupported_comp_annotation(component::String) - return "$(component): (OBS: Currently not supported, and hence empty)" -end - - -### Handles Independent Variables ### - -# Checks if the reaction system have any independent variable. True for all valid reaction systems. -function has_iv(rn::ReactionSystem) - return true -end - -# Extract a string which declares the system's independent variable. -function get_iv_string(rn::ReactionSystem) - return "@variables $(num_2_string(ModelingToolkit.get_iv(rn)))" -end - -# Creates an annotation for the system's independent variable. -function get_iv_annotation(rn::ReactionSystem) - return "Independent variable:" -end - -# Combines the 3 independent variable-related functions in a constant tuple. -IV_FS = (has_iv, get_iv_string, get_iv_annotation) - - -### Handles Spatial Independent Variables ### - -# Checks if the reaction system have any spatial independent variables. -function has_sivs(rn::ReactionSystem) - return length(rn.sivs) != 0 -end - -# Extract a string which declares the system's spatial independent variables. -function get_sivs_string(rn::ReactionSystem) - return "sivs = @variables$(wrapped_nums_2_string(rn.sivs))" -end - -# Creates an annotation for the system's spatial independent variables. -function get_sivs_annotation(rn::ReactionSystem) - return "Spatial independent variables:" -end +### Generic Expression Handling ### -# Combines the 3 independent variables-related functions in a constant tuple. -SIVS_FS = (has_sivs, get_sivs_string, get_sivs_annotation) - - -### Handles Species ### - -# Checks if the reaction system have any species. -function has_species(rn::ReactionSystem) - return length(species(rn)) != 0 -end - -# Extract a string which declares the system's species. -function get_species_string(rn::ReactionSystem) - return "sps = @species$(wrapped_nums_2_string(species(rn)))" -end - -# Creates an annotation for the system's species. -function get_species_annotation(rn::ReactionSystem) - return "Species:" -end - -# Combines the 3 species-related functions in a constant tuple. -SPECIES_FS = (has_species, get_species_string, get_species_annotation) - - -### Handles Variables ### - -# Checks if the reaction system have any variables. -function has_variables(rn::ReactionSystem) - return length(unknowns(rn)) > length(species(rn)) -end - -# Extract a string which declares the system's variables. -function get_variables_string(rn::ReactionSystem) - variables = filter(!isspecies, unknowns(rn)) - return "vars = @variables$(wrapped_nums_2_string(variables))" -end - -# Creates an annotation for the system's . -function get_variables_annotation(rn::ReactionSystem) - return "Variables:" -end - -# Combines the 3 variables-related functions in a constant tuple. -VARIABLES_FS = (has_variables, get_variables_string, get_variables_annotation) - - -### Handles Parameters ### - -# Checks if the reaction system have any parameters. -function has_parameters(rn::ReactionSystem) - return length(parameters(rn)) != 0 -end - -# Extract a string which declares the system's parameters. -function get_parameters_string(rn::ReactionSystem) - return "ps = @parameters$(wrapped_nums_2_string(parameters(rn)))" -end - -# Creates an annotation for the system's parameters. -function get_parameters_annotation(rn::ReactionSystem) - return "Parameters:" -end - -# Combines the 3 parameters-related functions in a constant tuple. -PARAMETERS_FS = (has_parameters, get_parameters_string, get_parameters_annotation) - - -### Handles Reactions ### - -# Checks if the reaction system have any reactions. -function has_reactions(rn::ReactionSystem) - return length(reactions(rn)) != 0 -end - -# Extract a string which declares the system's reactions. -function get_reactions_string(rn::ReactionSystem) - uncall_dict = make_uncall_dict(unknowns(rn)) - rxs_string = "rxs = [" - for rx in reactions(rn) - rxs_string = rxs_string * "\n\t" * reaction_string(rx, uncall_dict) * "," - end - - # Updates the string (including removing the last `,`) and returns in. - return rxs_string[1:end-1] * "\n]" -end - - -# Creates a string that corresponds to the declaration of a single `Reaction`. -function reaction_string(rx::Reaction, uncall_dict) - # Prepares the `Reaction` declaration components. - rate = num_2_string(rx.rate; uncall_dict) - substrates = isempty(rx.substrates) ? "nothing" : nums_2_uncalled_strin_vec(rx.substrates) - products = isempty(rx.products) ? "nothing" : nums_2_uncalled_strin_vec(rx.products) - substoich = isempty(rx.substoich) ? "nothing" : nums_2_uncalled_strin_vec(rx.substoich) - prodstoich = isempty(rx.prodstoich) ? "nothing" : nums_2_uncalled_strin_vec(rx.prodstoich) - - # Creates the full expression, including adding kwargs (`only_use_rate` and `metadata`). - rx_string = "Reaction($rate, $(substrates), $(products), $(substoich), $(prodstoich)" - if rx.only_use_rate - rx_string = rx_string * "; only_use_rate = true" - end - return rx_string * ")" -end - -# Creates an annotation for the system's reactions. -function get_reactions_annotation(rn::ReactionSystem) - return "Reactions:" -end - -# Combines the 3 reactions-related functions in a constant tuple. -REACTIONS_FS = (has_reactions, get_reactions_string, get_reactions_annotation) - - -### Handles Equations ### - -# Checks if the reaction system have any equations. -function has_equations(rn::ReactionSystem) - return length(equations(rn)) > length(reactions(rn)) -end - -# Extract a string which declares the system's equations. -function get_equations_string(rn::ReactionSystem) - get_unsupported_comp_string("equations") +# Function for converting a Symbolics of the form `X(t)` to the form `X`. +function uncall(var) + istree(var) ? Sym{Real}(Symbolics.getname(var)) : var end -# Creates an annotation for the system's equations. -function get_equations_annotation(rn::ReactionSystem) - get_unsupported_comp_annotation("Equations") +# For a list of symbolics, makes an "uncall dict". It permits the conversion of e.g. `X(t)` to `X`. +function make_uncall_dict(syms) + return Dict([sym => uncall(Symbolics.unwrap(sym)) for sym in syms]) end - -# Combines the 3 equations-related functions in a constant tuple. -EQUATIONS_FS = (has_equations, get_equations_string, get_equations_annotation) \ No newline at end of file diff --git a/src/model_serialisation/serialise_fields.jl b/src/model_serialisation/serialise_fields.jl new file mode 100644 index 0000000000..195c52e769 --- /dev/null +++ b/src/model_serialisation/serialise_fields.jl @@ -0,0 +1,171 @@ +### Handles Independent Variables ### + +# Checks if the reaction system have any independent variable. True for all valid reaction systems. +function has_iv(rn::ReactionSystem) + return true +end + +# Extract a string which declares the system's independent variable. +function get_iv_string(rn::ReactionSystem) + return "@variables $(num_2_string(ModelingToolkit.get_iv(rn)))" +end + +# Creates an annotation for the system's independent variable. +function get_iv_annotation(rn::ReactionSystem) + return "Independent variable:" +end + +# Combines the 3 independent variable-related functions in a constant tuple. +IV_FS = (has_iv, get_iv_string, get_iv_annotation) + + +### Handles Spatial Independent Variables ### + +# Checks if the reaction system have any spatial independent variables. +function has_sivs(rn::ReactionSystem) + return length(rn.sivs) != 0 +end + +# Extract a string which declares the system's spatial independent variables. +function get_sivs_string(rn::ReactionSystem) + return "sivs = @variables$(wrapped_nums_2_string(rn.sivs))" +end + +# Creates an annotation for the system's spatial independent variables. +function get_sivs_annotation(rn::ReactionSystem) + return "Spatial independent variables:" +end + +# Combines the 3 independent variables-related functions in a constant tuple. +SIVS_FS = (has_sivs, get_sivs_string, get_sivs_annotation) + + +### Handles Species ### + +# Checks if the reaction system have any species. +function has_species(rn::ReactionSystem) + return length(species(rn)) != 0 +end + +# Extract a string which declares the system's species. +function get_species_string(rn::ReactionSystem) + return "sps = @species$(wrapped_nums_2_string(species(rn)))" +end + +# Creates an annotation for the system's species. +function get_species_annotation(rn::ReactionSystem) + return "Species:" +end + +# Combines the 3 species-related functions in a constant tuple. +SPECIES_FS = (has_species, get_species_string, get_species_annotation) + + +### Handles Variables ### + +# Checks if the reaction system have any variables. +function has_variables(rn::ReactionSystem) + return length(unknowns(rn)) > length(species(rn)) +end + +# Extract a string which declares the system's variables. +function get_variables_string(rn::ReactionSystem) + variables = filter(!isspecies, unknowns(rn)) + return "vars = @variables$(wrapped_nums_2_string(variables))" +end + +# Creates an annotation for the system's . +function get_variables_annotation(rn::ReactionSystem) + return "Variables:" +end + +# Combines the 3 variables-related functions in a constant tuple. +VARIABLES_FS = (has_variables, get_variables_string, get_variables_annotation) + + +### Handles Parameters ### + +# Checks if the reaction system have any parameters. +function has_parameters(rn::ReactionSystem) + return length(parameters(rn)) != 0 +end + +# Extract a string which declares the system's parameters. +function get_parameters_string(rn::ReactionSystem) + return "ps = @parameters$(wrapped_nums_2_string(parameters(rn)))" +end + +# Creates an annotation for the system's parameters. +function get_parameters_annotation(rn::ReactionSystem) + return "Parameters:" +end + +# Combines the 3 parameters-related functions in a constant tuple. +PARAMETERS_FS = (has_parameters, get_parameters_string, get_parameters_annotation) + + +### Handles Reactions ### + +# Checks if the reaction system have any reactions. +function has_reactions(rn::ReactionSystem) + return length(reactions(rn)) != 0 +end + +# Extract a string which declares the system's reactions. +function get_reactions_string(rn::ReactionSystem) + uncall_dict = make_uncall_dict(unknowns(rn)) + rxs_string = "rxs = [" + for rx in reactions(rn) + rxs_string = rxs_string * "\n\t" * reaction_string(rx, uncall_dict) * "," + end + + # Updates the string (including removing the last `,`) and returns in. + return rxs_string[1:end-1] * "\n]" +end + + +# Creates a string that corresponds to the declaration of a single `Reaction`. +function reaction_string(rx::Reaction, uncall_dict) + # Prepares the `Reaction` declaration components. + rate = num_2_string(rx.rate; uncall_dict) + substrates = isempty(rx.substrates) ? "nothing" : nums_2_uncalled_strin_vec(rx.substrates) + products = isempty(rx.products) ? "nothing" : nums_2_uncalled_strin_vec(rx.products) + substoich = isempty(rx.substoich) ? "nothing" : nums_2_uncalled_strin_vec(rx.substoich) + prodstoich = isempty(rx.prodstoich) ? "nothing" : nums_2_uncalled_strin_vec(rx.prodstoich) + + # Creates the full expression, including adding kwargs (`only_use_rate` and `metadata`). + rx_string = "Reaction($rate, $(substrates), $(products), $(substoich), $(prodstoich)" + if rx.only_use_rate + rx_string = rx_string * "; only_use_rate = true" + end + return rx_string * ")" +end + +# Creates an annotation for the system's reactions. +function get_reactions_annotation(rn::ReactionSystem) + return "Reactions:" +end + +# Combines the 3 reactions-related functions in a constant tuple. +REACTIONS_FS = (has_reactions, get_reactions_string, get_reactions_annotation) + + +### Handles Equations ### + +# Checks if the reaction system have any equations. +function has_equations(rn::ReactionSystem) + return length(equations(rn)) > length(reactions(rn)) +end + +# Extract a string which declares the system's equations. +function get_equations_string(rn::ReactionSystem) + get_unsupported_comp_string("equations") +end + +# Creates an annotation for the system's equations. +function get_equations_annotation(rn::ReactionSystem) + get_unsupported_comp_annotation("Equations") +end + +# Combines the 3 equations-related functions in a constant tuple. +EQUATIONS_FS = (has_equations, get_equations_string, get_equations_annotation) \ No newline at end of file From 9b4bbdd3dcc15db8cc5ab43923dc1458eb760baf Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 29 Mar 2024 08:24:28 -0400 Subject: [PATCH 110/446] save progress --- src/Catalyst.jl | 13 +- .../serialisation_support.jl | 188 +++++++++++++++--- src/model_serialisation/serialise_fields.jl | 158 +++++++++++++-- .../serialise_reactionsystem.jl | 94 ++++++--- 4 files changed, 384 insertions(+), 69 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index fd2f10e2d6..7adaf341b3 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -153,11 +153,6 @@ export balance_reaction, balance_system include("steady_state_stability.jl") export steady_state_stability, steady_state_jac -# ReactionSystem serialisation. -include("model_serialisation/serialise_reactionsystem.jl") -include("model_serialisation/serialisation_support.jl") -export save_reaction_network - ### Extensions ### # HomotopyContinuation @@ -187,4 +182,12 @@ include("spatial_reaction_systems/utility.jl") include("spatial_reaction_systems/spatial_ODE_systems.jl") include("spatial_reaction_systems/lattice_jump_systems.jl") + +### ReactionSystem Serialisation ### +# Has to be at the end (because it uses records of all metadata declared by Catalyst). +include("model_serialisation/serialisation_support.jl") +include("model_serialisation/serialise_fields.jl") +include("model_serialisation/serialise_reactionsystem.jl") +export save_reaction_network + end # module diff --git a/src/model_serialisation/serialisation_support.jl b/src/model_serialisation/serialisation_support.jl index 43e8f4c033..29f0aa33a8 100644 --- a/src/model_serialisation/serialisation_support.jl +++ b/src/model_serialisation/serialisation_support.jl @@ -1,11 +1,35 @@ +## String Handling ### + +# Appends stuff to a string. +# E.g `@string_append! str_base str1 str2` becomes `str_base = str_base * str1 * str2`. +macro string_append!(string, inputs...) + rhs = :($string * $(inputs[1])) + for input in inputs[2:end] + push!(rhs.args, input) + end + return esc(:($string = $rhs)) +end + +# Prepends stuff to a string. Can only take 1 or 2 inputs. +# E.g `@string_prepend! str1 str_base` becomes `str_base = str1 * str_base`. +macro string_prepend!(input, string) + rhs = :($input * $string) + return esc(:($string = $rhs)) +end +macro string_prepend!(input1, input2, string) + rhs = :($input1 * $input2 * $string) + return esc(:($string = $rhs)) +end + + ### Field Serialisation Support Functions ### # Function which handles the addition of a single component to the file string. -function push_component(file_text::String, rn::ReactionSystem, annotate::Bool, comp_funcs::Tuple) +function push_field(file_text::String, rn::ReactionSystem, annotate::Bool, comp_funcs::Tuple) has_component, get_comp_string, get_comp_annotation = comp_funcs has_component(rn) || (return (file_text, false)) write_string = "\n" * get_comp_string(rn) - annotate && (write_string = "\n\n# " * get_comp_annotation(rn) * write_string) + annotate && (@string_prepend! "\n\n# " get_comp_annotation(rn) write_string) return (file_text * write_string, true) end @@ -21,38 +45,154 @@ function get_unsupported_comp_annotation(component::String) end -### Symbolics String Conversions ### +### String Conversion ### -# Converts a Num to a String. -function wrapped_num_2_string(num) - return String(Symbol(num)) +# Converts a numeric expression (e.g. p*X + 2Y) to a string (e.g. "p*X + 2Y"). Also ensures that for +# any variables (e.g. X(t)) the call part is stripped, and only variable name (e.g. X) is written. +function expression_2_string(expr; strip_call_dict = make_strip_call_dict(Symbolics.get_variables(expr))) + strip_called_expr = substitute(expr, strip_call_dict) + return repr(strip_called_expr) end -# Converts a vector of Nums to a single string with all (e.g. [X(t), Y(t)] becomes " X(t) Y(t)"). -function wrapped_nums_2_string(nums) - return prod([" " * wrapped_num_2_string(num) for num in nums]) +# Converts a vector of symbolics (e.g. the species or parameter vectors) to a string vector. Strips +# any calls (e.g. X(t) becomes X). E.g. a species vector [X, Y, Z] is converted to "[X, Y, Z]". +function syms_2_strings(syms) + strip_called_syms = [strip_call(Symbolics.unwrap(sym)) for sym in syms] + return "$(convert(Vector{Any}, strip_called_syms))"[4:end] +end + +# Converts a vector of symbolics (e.g. the species or parameter vectors) to a string corresponding to +# the code required to declare them (potential @parameters or @species commands must still be added). +# The `multiline_format` option formats it with a `begin ... end` block and declarations on separate lines. +function syms_2_declaration_string(syms; multiline_format = false) + decs_string = (multiline_format ? "begin" : "") + for sym in syms + delimiter = (multiline_format ? "\n\t" : " ") + @string_append! decs_string delimiter sym_2_declaration_string(sym; multiline_format) + end + multiline_format && (@string_append! decs_string "\nend") + return decs_string end -# Converts a vector of Symbolics to a (uncalled) vector (e.g. [X(t), Y(t), p] becomes " [X, Y, p]"). -function nums_2_uncalled_strin_vec(nums) - return "$(convert(Vector{Any}, uncall.(nums)))"[4:end] +# Converts a symbolic (e.g. a species or parameter) to a string corresponding to how it would be declared +# in code. Takes default values and metadata into account. Example output "p=2.0 [bounds=(0.0, 1.0)]". +# The `multiline_format` option formats the string as if it is part of a `begin .. end` block. +function sym_2_declaration_string(sym; multiline_format = false) + # Creates the basic symbol. The `"$(sym)"` ensures that we get e.g. "X(t)" and not "X". + dec_string = "$(sym)" + + # If there is a default value, adds this to the declaration. + if ModelingToolkit.hasdefault(sym) + def_val = x_2_string(ModelingToolkit.getdefault(sym)) + separator = (multiline_format ? " = " : "=") + @string_append! dec_string separator "$(def_val)" + end + + # Adds any metadata to the declaration. + metadata_to_declare = get_metadata_to_declare(sym) + if !isempty(metadata_to_declare) + metadata_string = (multiline_format ? ", [" : " [") + for metadata in metadata_to_declare + @string_append! metadata_string metadata_2_string(sym, metadata) ", " + end + @string_append! dec_string metadata_string[1:end-2] "]" + end + + # Returns the declaration entry for the symbol. + return dec_string end -# Converts a numeric expression to a string. `uncall_dict` is required to convert stuff like -# `p1*X(t) + p2` to `p1*X + p2`. -function num_2_string(num; uncall_dict = make_uncall_dict(Symbolics.get_variables(num))) - uncalled_num = substitute(num, uncall_dict) - return repr(uncalled_num) +# Converts a generic value to a String. Handles each type of value separately. Unsupported values might +# not necessarily generate valid code, and hence throw errors. Primarily used to write default values +# and metadata values (which hopefully almost exclusively) has simple, supported, types. Ideally, +# more supported types can be added here. +x_2_string(x::Num) = expression_2_string(x) +x_2_string(x::SymbolicUtils.BasicSymbolic{<:Real}) = expression_2_string(x) +x_2_string(x::String) = "\"$x\"" +x_2_string(x::Char) = "\'$x\'" +x_2_string(x::Symbol) = ":$x" +x_2_string(x::Number) = string(x) +function x_2_string(x::Vector) + output = "[" + for val in x + output = output * x_2_string(val) * ", " + end + return output[1:end-2] * "]" +end +function x_2_string(x::Tuple) + output = "(" + for val in x + output = output * x_2_string(val) * ", " + end + return output[1:end-2] * ")" +end +x_2_string(x::Pair) = "$(x_2_string(x[1])) => $(x_2_string(x[2]))" +x_2_string(x::Nothing) = "nothing" +x_2_string(x) = error("Tried to write an unsupported value ($(x)) of an unsupported type ($(typeof(x))) to a string.") + + +### Symbolics Metadata Handling ### + +# For a Symbolic, retrieve all metadata that needs to be added to its declaration. Certain metadata +# (such as default values and whether a variable is a species or not) are skipped (these are stored +# in the `SKIPPED_METADATA` constant). +# Because it is impossible to retrieve the keyword used to declare individual metadata from the +# metadata entry, these must be stored manually (in `RECOGNISED_METADATA`). If one of these are +# encountered, a warning is thrown and it is skipped (we could also throw an error). I have asked +# Shashi, and he claims there is not alternative (general) solution. +function get_metadata_to_declare(sym) + metadata_keys = collect(keys(sym.metadata)) + metadata_keys = filter(mdk -> !(mdk in SKIPPED_METADATA), metadata_keys) + if any(!(mdk in keys(RECOGNISED_METADATA)) for mdk in metadata_keys) + @warn "The following unrecognised metadata entries: $(setdiff(metadata_keys, keys(RECOGNISED_METADATA))) are not recognised for species/variable/parameter $sym. If you raise an issue at https://github.com/SciML/Catalyst.jl, we can add support for this metadata type." + metadata_keys = filter(mdk -> in(mdk, keys(RECOGNISED_METADATA)), metadata_keys) + end + return metadata_keys +end + +# Converts a given metadata into the string used to declare it. +function metadata_2_string(sym, metadata) + return RECOGNISED_METADATA[metadata] * " = " * x_2_string(sym.metadata[metadata]) end +# List of all recognised metadata (we should add as many as possible), and th keyword used to declare +# them in code. +const RECOGNISED_METADATA = Dict([ + Catalyst.ParameterConstantSpecies => "isconstantspecies" + Catalyst.VariableBCSpecies => "isbcspecies" + Catalyst.VariableSpecies => "isspecies" + Catalyst.EdgeParameter => "edgeparameter" + Catalyst.CompoundSpecies => "iscompound" + Catalyst.CompoundComponents => "components" + Catalyst.CompoundCoefficients => "coefficients" + + ModelingToolkit.VariableDescription => "description" + ModelingToolkit.VariableBounds => "bounds" + ModelingToolkit.VariableUnit => "unit" + ModelingToolkit.VariableConnectType => "connect" + ModelingToolkit.VariableNoiseType => "noise" + ModelingToolkit.VariableInput => "input" + ModelingToolkit.VariableOutput => "output" + ModelingToolkit.VariableIrreducible => "irreducible" + ModelingToolkit.VariableStatePriority => "state_priority" + ModelingToolkit.VariableMisc => "misc" + ModelingToolkit.TimeDomain => "timedomain" +]) + +# List of metadata that does not need to be explicitly declared to be added (or which is handled separately). +const SKIPPED_METADATA = [ModelingToolkit.MTKVariableTypeCtx, Symbolics.VariableSource, + Symbolics.VariableDefaultValue, Catalyst.VariableSpecies] + + ### Generic Expression Handling ### -# Function for converting a Symbolics of the form `X(t)` to the form `X`. -function uncall(var) - istree(var) ? Sym{Real}(Symbolics.getname(var)) : var +# Potentially strips the call for a symbolics. E.g. X(t) becomes X (but p remains p). This is used +# when variables are written to files, as in code they are used without the call part. +function strip_call(sym) + return istree(sym) ? Sym{Real}(Symbolics.getname(sym)) : sym end -# For a list of symbolics, makes an "uncall dict". It permits the conversion of e.g. `X(t)` to `X`. -function make_uncall_dict(syms) - return Dict([sym => uncall(Symbolics.unwrap(sym)) for sym in syms]) -end +# For an vector of symbolics, creates a dictionary taking each symbolics to each call-stripped form. +function make_strip_call_dict(syms) + return Dict([sym => strip_call(Symbolics.unwrap(sym)) for sym in syms]) +end \ No newline at end of file diff --git a/src/model_serialisation/serialise_fields.jl b/src/model_serialisation/serialise_fields.jl index 195c52e769..c108dcf117 100644 --- a/src/model_serialisation/serialise_fields.jl +++ b/src/model_serialisation/serialise_fields.jl @@ -7,7 +7,8 @@ end # Extract a string which declares the system's independent variable. function get_iv_string(rn::ReactionSystem) - return "@variables $(num_2_string(ModelingToolkit.get_iv(rn)))" + iv_dec = ModelingToolkit.get_iv(rn) + return "@variables $(iv_dec)" end # Creates an annotation for the system's independent variable. @@ -28,7 +29,7 @@ end # Extract a string which declares the system's spatial independent variables. function get_sivs_string(rn::ReactionSystem) - return "sivs = @variables$(wrapped_nums_2_string(rn.sivs))" + return "spatial_ivs = @variables$(get_species_string(rn.sivs))" end # Creates an annotation for the system's spatial independent variables. @@ -49,7 +50,7 @@ end # Extract a string which declares the system's species. function get_species_string(rn::ReactionSystem) - return "sps = @species$(wrapped_nums_2_string(species(rn)))" + return "sps = @species$(syms_2_declaration_string(species(rn)))" end # Creates an annotation for the system's species. @@ -71,7 +72,7 @@ end # Extract a string which declares the system's variables. function get_variables_string(rn::ReactionSystem) variables = filter(!isspecies, unknowns(rn)) - return "vars = @variables$(wrapped_nums_2_string(variables))" + return "vars = @variables$(syms_2_declaration_string(variables))" end # Creates an annotation for the system's . @@ -92,7 +93,7 @@ end # Extract a string which declares the system's parameters. function get_parameters_string(rn::ReactionSystem) - return "ps = @parameters$(wrapped_nums_2_string(parameters(rn)))" + return "ps = @parameters$(syms_2_declaration_string(parameters(rn)))" end # Creates an annotation for the system's parameters. @@ -113,31 +114,49 @@ end # Extract a string which declares the system's reactions. function get_reactions_string(rn::ReactionSystem) - uncall_dict = make_uncall_dict(unknowns(rn)) + # Creates a dictionary for converting symbolics to their call-stripped form (e.g. X(t) to X). + strip_call_dict = make_strip_call_dict(unknowns(rn)) + + # Handles the case with zero and one reaction separately. Only effect is nicer formatting. + (length(reactions(rn)) == 0) && (return "rxs = []") + (length(reactions(rn)) == 1) && (return "rxs = [$(reaction_string(rx, strip_call_dict))]") + + # Creates the string corresponding to the code which generates the system's reactions. rxs_string = "rxs = [" for rx in reactions(rn) - rxs_string = rxs_string * "\n\t" * reaction_string(rx, uncall_dict) * "," + @string_append! rxs_string "\n\t" * reaction_string(rx, strip_call_dict) "," end - # Updates the string (including removing the last `,`) and returns in. + # Updates the string (including removing the last `,`) and returns it. return rxs_string[1:end-1] * "\n]" end - # Creates a string that corresponds to the declaration of a single `Reaction`. -function reaction_string(rx::Reaction, uncall_dict) +function reaction_string(rx::Reaction, strip_call_dict) # Prepares the `Reaction` declaration components. - rate = num_2_string(rx.rate; uncall_dict) - substrates = isempty(rx.substrates) ? "nothing" : nums_2_uncalled_strin_vec(rx.substrates) - products = isempty(rx.products) ? "nothing" : nums_2_uncalled_strin_vec(rx.products) - substoich = isempty(rx.substoich) ? "nothing" : nums_2_uncalled_strin_vec(rx.substoich) - prodstoich = isempty(rx.prodstoich) ? "nothing" : nums_2_uncalled_strin_vec(rx.prodstoich) + rate = expression_2_string(rx.rate; strip_call_dict) + substrates = isempty(rx.substrates) ? "nothing" : x_2_string(rx.substrates) + products = isempty(rx.products) ? "nothing" : x_2_string(rx.products) + substoich = isempty(rx.substoich) ? "nothing" : x_2_string(rx.substoich) + prodstoich = isempty(rx.prodstoich) ? "nothing" : x_2_string(rx.prodstoich) # Creates the full expression, including adding kwargs (`only_use_rate` and `metadata`). rx_string = "Reaction($rate, $(substrates), $(products), $(substoich), $(prodstoich)" if rx.only_use_rate - rx_string = rx_string * "; only_use_rate = true" + @string_append! rx_string "; only_use_rate = true" + isempty(getmetadata_dict(rx)) || (rx_string = rx_string * ", ") + end + if !isempty(getmetadata_dict(rx)) + rx.only_use_rate || (@string_append! rx_string "; ") + @string_append! rx_string "metadata = [" + for entry in getmetadata_dict(rx) + metadata_entry = "$(x_2_string(entry)), " + @string_append! rx_string metadata_entry + end + rx_string = rx_string[1:end-2] * "]" end + + # Returns the Reaction string. return rx_string * ")" end @@ -168,4 +187,109 @@ function get_equations_annotation(rn::ReactionSystem) end # Combines the 3 equations-related functions in a constant tuple. -EQUATIONS_FS = (has_equations, get_equations_string, get_equations_annotation) \ No newline at end of file +EQUATIONS_FS = (has_equations, get_equations_string, get_equations_annotation) + + +### Handles Observables ### + +# Checks if the reaction system have any observables. +function has_observed(rn::ReactionSystem) + return false +end + +# Extract a string which declares the system's observables. +function get_observed_string(rn::ReactionSystem) + get_unsupported_comp_string("observables") +end + +# Creates an annotation for the system's observables. +function get_observed_annotation(rn::ReactionSystem) + get_unsupported_comp_annotation("Observables:") +end + +# Combines the 3 -related functions in a constant tuple. +OBSERVED_FS = (has_observed, get_observed_string, get_observed_annotation) + + +### Handles Continuous Events ### + +# Checks if the reaction system have any continuous events. +function has_continuous_events(rn::ReactionSystem) + return false +end + +# Extract a string which declares the system's continuous events. +function get_continuous_events_string(rn::ReactionSystem) + get_unsupported_comp_string("continuous events") +end + +# Creates an annotation for the system's continuous events. +function get_continuous_events_annotation(rn::ReactionSystem) + get_unsupported_comp_annotation("Continuous events:") +end + +# Combines the 3 -related functions in a constant tuple. +CONTINUOUS_EVENTS_FS = (has_continuous_events, get_continuous_events_string, get_continuous_events_annotation) + + +### Handles Discrete Events ### + +# Checks if the reaction system have any discrete events. +function has_discrete_events(rn::ReactionSystem) + return false +end + +# Extract a string which declares the system's discrete events. +function get_discrete_events_string(rn::ReactionSystem) + get_unsupported_comp_string("discrete events") +end + +# Creates an annotation for the system's discrete events. +function get_discrete_events_annotation(rn::ReactionSystem) + get_unsupported_comp_annotation("Discrete events:") +end + +# Combines the 3 -related functions in a constant tuple. +DISCRETE_EVENTS_FS = (has_discrete_events, get_discrete_events_string, get_discrete_events_annotation) + + +### Handles Systems ### + +# Checks if the reaction system have any systems. +function has_systems(rn::ReactionSystem) + return false +end + +# Extract a string which declares the system's systems. +function get_systems_string(rn::ReactionSystem) + get_unsupported_comp_string("systems") +end + +# Creates an annotation for the system's systems. +function get_systems_annotation(rn::ReactionSystem) + get_unsupported_comp_annotation("Systems:") +end + +# Combines the 3 systems-related functions in a constant tuple. +SYSTEMS_FS = (has_systems, get_systems_string, get_systems_annotation) + + +### Handles Connection Types ### + +# Checks if the reaction system have any connection types. +function has_connection_type(rn::ReactionSystem) + return false +end + +# Extract a string which declares the system's connection types. +function get_connection_type_string(rn::ReactionSystem) + get_unsupported_comp_string("connection types") +end + +# Creates an annotation for the system's connection types. +function get_connection_type_annotation(rn::ReactionSystem) + get_unsupported_comp_annotation("Connection types:") +end + +# Combines the 3 connection types-related functions in a constant tuple. +CONNECTION_TYPE_FS = (has_connection_type, get_connection_type_string, get_connection_type_annotation) \ No newline at end of file diff --git a/src/model_serialisation/serialise_reactionsystem.jl b/src/model_serialisation/serialise_reactionsystem.jl index b42d34bbe5..572776dd86 100644 --- a/src/model_serialisation/serialise_reactionsystem.jl +++ b/src/model_serialisation/serialise_reactionsystem.jl @@ -1,22 +1,44 @@ +""" + save_reaction_network(filename::String, rn::ReactionSystem; annotate = true) + +Save a `ReactionSystem` model to a file. + +Work in progress, currently missing features: +- Problems with ordering of declarations of species/variables/parameters that have defaults that are other species/variables/parameters. +- Saving of the `sivs` field has not been fully implemented. +- Saving of the `observed` field has not been fully implemented. +- Saving of the `continuous_events` field has not been fully implemented. +- Saving of the `discrete_events` field has not been fully implemented. +- Saving of the `systems` field has not been fully implemented. +- Saving of the `connection_type` field has not been fully implemented. +""" function save_reaction_network(filename::String, rn::ReactionSystem; annotate = true) # Initiates the file string. file_text = "" # Goes through each type of system component, potentially adding it to the string. - file_text, has_iv = push_component(file_text, rn, annotate, IV_FS) - file_text, has_sivs = push_component(file_text, rn, annotate, SIVS_FS) - file_text, has_species = push_component(file_text, rn, annotate, SPECIES_FS) - file_text, has_variables = push_component(file_text, rn, annotate, VARIABLES_FS) - file_text, has_parameters = push_component(file_text, rn, annotate, PARAMETERS_FS) - file_text, has_reactions = push_component(file_text, rn, annotate, REACTIONS_FS) - file_text, has_equations = push_component(file_text, rn, annotate, EQUATIONS_FS) - + file_text, _ = push_field(file_text, rn, annotate, IV_FS) + file_text, has_sivs = push_field(file_text, rn, annotate, SIVS_FS) + file_text, has_species = push_field(file_text, rn, annotate, SPECIES_FS) + file_text, has_variables = push_field(file_text, rn, annotate, VARIABLES_FS) + file_text, has_parameters = push_field(file_text, rn, annotate, PARAMETERS_FS) + file_text, has_reactions = push_field(file_text, rn, annotate, REACTIONS_FS) + file_text, has_equations = push_field(file_text, rn, annotate, EQUATIONS_FS) + file_text, has_observed = push_field(file_text, rn, annotate, OBSERVED_FS) + file_text, has_continuous_events = push_field(file_text, rn, annotate, CONTINUOUS_EVENTS_FS) + file_text, has_discrete_events = push_field(file_text, rn, annotate, DISCRETE_EVENTS_FS) + file_text, has_systems = push_field(file_text, rn, annotate, SYSTEMS_FS) + file_text, has_connection_type = push_field(file_text, rn, annotate, CONNECTION_TYPE_FS) + # Finalises the system. Creates the final `ReactionSystem` call. rs_creation_code = make_reaction_system_call(rn, file_text, annotate, - has_sivs, has_species, has_variables, - has_parameters, has_reactions, has_equations) - annotate || (file_text = "\n" * file_text) - file_text = "let" * file_text * "\n\n" * rs_creation_code * "\n\nend" + has_sivs, has_species, has_variables, has_parameters, + has_reactions, has_equations, has_observed, + has_discrete_events, has_continuous_events, + has_systems, has_connection_type) + annotate || (@string_prepend! "\n" file_text) + @string_prepend! "let" file_text + @string_append! file_text "\n\n" rs_creation_code "\n\nend" # Writes the model to a file. Then, returns nothing. open(filename, "w") do file @@ -25,11 +47,17 @@ function save_reaction_network(filename::String, rn::ReactionSystem; annotate = return nothing end -# Takes the actual text which creates the model, and wraps it in a `let ... end` statement and A -# ReactionSystem call, to create the final file text. -function make_reaction_system_call(rs::ReactionSystem, file_text, annotate, has_sivs, has_species, has_variables, has_parameters, has_reactions, has_equations) - # Creates base call. - iv = Catalyst.get_iv(rs) +# Takes the actual text which creates the model, and wraps it in a `let ... end` statement and a +# ReactionSystem call. This creates the finalised text that is written to a file. +function make_reaction_system_call(rs::ReactionSystem, file_text, annotate, has_sivs, has_species, + has_variables, has_parameters, has_reactions, has_equations, + has_observed, has_continuous_events, has_discrete_events, + has_systems, has_connection_type) + + # Gets the independent variable input. + iv = x_2_string(get_iv(rs)) + + # Gets the equations (reactions + equations) input. if has_reactions && has_equations eqs = "[rxs; eqs]" elseif has_reactions @@ -39,6 +67,8 @@ function make_reaction_system_call(rs::ReactionSystem, file_text, annotate, has_ else eqs = "[]" end + + # Gets the unknowns (species + variables) input. if has_species && has_variables unknowns = "[sps; vars]" elseif has_species @@ -48,28 +78,46 @@ function make_reaction_system_call(rs::ReactionSystem, file_text, annotate, has_ else unknowns = "[]" end + + # Gets the parameters input. if has_parameters ps = "ps" else ps = "[]" end + + # Initiates the ReactionSystem call with the mandatory inputs. reaction_system_string = "ReactionSystem($eqs, $iv, $unknowns, $ps" - # Appends additional (optional) arguments. + # Appends the reaction system name. Also initiates the optional argument part of the call. if Base.isidentifier(Catalyst.getname(rs)) rs_name = ":$(Catalyst.getname(rs))" else rs_name = "Symbol(\"$(Catalyst.getname(rs))\")" end - reaction_system_string = reaction_system_string * "; name = $(rs_name)" - reaction_system_string = reaction_system_string * ")" + @string_append! reaction_system_string "; name = $(rs_name)" + + # Goes through various fields that might exists, and if so, adds them to the string. + has_sivs && (@string_append! reaction_system_string ", spatial_ivs") + has_observed && (@string_append! reaction_system_string ", observed") + has_continuous_events && (@string_append! reaction_system_string ", continuous_events") + has_discrete_events && (@string_append! reaction_system_string ", discrete_events") + has_systems && (@string_append! reaction_system_string ", systems") + has_connection_type && (@string_append! reaction_system_string ", connection_type") + + # Potentially appends a combinatorial combinatoric_ratelaws statement. + Symbolics.unwrap(rs.combinatoric_ratelaws) || (@string_append! reaction_system_string ", combinatoric_ratelaws = false") + + # Potentially appends `ReactionSystem` metadata value(s). Weird composite types are not supported. + isnothing(rs.metadata) || (@string_append! reaction_system_string ", metadata = $(x_2_string(rs.metadata))") - # Returns the full call. + # Finalises the call. Appends potential annotation. If the system is complete, add a call for this. + @string_append! reaction_system_string ")" if !ModelingToolkit.iscomplete(rs) - reaction_system_string = "rs = $(reaction_system_string)\ncomplete(rs)" + @string_append! reaction_system_string "rs = $(reaction_system_string)\ncomplete(rs)" end if annotate - reaction_system_string = "# Declares ReactionSystem model:\n" * reaction_system_string + @string_prepend! "# Declares ReactionSystem model:\n" reaction_system_string end return reaction_system_string end \ No newline at end of file From 501727dd8ed8de353e8da8eef154acd8be429f18 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 14 Apr 2024 16:59:16 -0400 Subject: [PATCH 111/446] add continiuous and discrete events --- src/model_serialisation/serialise_fields.jl | 111 ++++++++++++++++-- .../serialise_reactionsystem.jl | 27 ++--- 2 files changed, 111 insertions(+), 27 deletions(-) diff --git a/src/model_serialisation/serialise_fields.jl b/src/model_serialisation/serialise_fields.jl index c108dcf117..afb2671a26 100644 --- a/src/model_serialisation/serialise_fields.jl +++ b/src/model_serialisation/serialise_fields.jl @@ -24,12 +24,12 @@ IV_FS = (has_iv, get_iv_string, get_iv_annotation) # Checks if the reaction system have any spatial independent variables. function has_sivs(rn::ReactionSystem) - return length(rn.sivs) != 0 + return !isempty(get_sivs(rn)) end # Extract a string which declares the system's spatial independent variables. function get_sivs_string(rn::ReactionSystem) - return "spatial_ivs = @variables$(get_species_string(rn.sivs))" + return "spatial_ivs = @variables$(get_species_string(get_sivs(rn)))" end # Creates an annotation for the system's spatial independent variables. @@ -117,8 +117,7 @@ function get_reactions_string(rn::ReactionSystem) # Creates a dictionary for converting symbolics to their call-stripped form (e.g. X(t) to X). strip_call_dict = make_strip_call_dict(unknowns(rn)) - # Handles the case with zero and one reaction separately. Only effect is nicer formatting. - (length(reactions(rn)) == 0) && (return "rxs = []") + # Handles the case with one reaction separately. Only effect is nicer formatting. (length(reactions(rn)) == 1) && (return "rxs = [$(reaction_string(rx, strip_call_dict))]") # Creates the string corresponding to the code which generates the system's reactions. @@ -178,12 +177,27 @@ end # Extract a string which declares the system's equations. function get_equations_string(rn::ReactionSystem) - get_unsupported_comp_string("equations") + # Creates a dictionary for converting symbolics to their call-stripped form (e.g. X(t) to X). + strip_call_dict = make_strip_call_dict(unknowns(rn)) + + # Handles the case with one equation separately. Only effect is nicer formatting. + if length(equations(rn)) - length(reactions(rn)) == 1 + return "eqs = [$(expression_2_string(equations(rn)[end]; strip_call_dict))]" + end + + # Creates the string corresponding to the code which generates the system's reactions. + eqs_string = "rxs = [" + for eq in reactions(rn)[length(reactions(rn)) + 1:end] + @string_append! eqs_string "\n\t" expression_2_string(eq; strip_call_dict) "," + end + + # Updates the string (including removing the last `,`) and returns it. + return eqs_string[1:end-1] * "\n]" end # Creates an annotation for the system's equations. function get_equations_annotation(rn::ReactionSystem) - get_unsupported_comp_annotation("Equations") + return "Equations:" end # Combines the 3 equations-related functions in a constant tuple. @@ -204,7 +218,7 @@ end # Creates an annotation for the system's observables. function get_observed_annotation(rn::ReactionSystem) - get_unsupported_comp_annotation("Observables:") + return "Observables:" end # Combines the 3 -related functions in a constant tuple. @@ -215,17 +229,53 @@ OBSERVED_FS = (has_observed, get_observed_string, get_observed_annotation) # Checks if the reaction system have any continuous events. function has_continuous_events(rn::ReactionSystem) - return false + return length(rn.continuous_events) > 0 end # Extract a string which declares the system's continuous events. function get_continuous_events_string(rn::ReactionSystem) - get_unsupported_comp_string("continuous events") + # Creates a dictionary for converting symbolics to their call-stripped form (e.g. X(t) to X). + strip_call_dict = make_strip_call_dict(unknowns(rn)) + + # Handles the case with one event separately. Only effect is nicer formatting. + if length(rn.continuous_events) == 1 + return "continuous_events = [$(continuous_event_string(rn.continuous_events.value[1], strip_call_dict))]" + end + + # Creates the string corresponding to the code which generates the system's reactions. + continuous_events_string = "continuous_events = [" + for continuous_event in rn.continuous_events.value + @string_append! continuous_events_string "\n\t" continuous_event_string(continuous_event, strip_call_dict) "," + end + + # Updates the string (including removing the last `,`) and returns it. + return continuous_events_string[1:end-1] * "\n]" +end + +# Creates a string that corresponds to the declaration of a single continuous event. +function continuous_event_string(continuous_event, strip_call_dict) + # Creates the string corresponding to the equations (i.e. conditions). + eqs_string = "[" + for eq in continuous_event.eqs + @string_append! eqs_string expression_2_string(eq; strip_call_dict) ", " + end + eqs_string = eqs_string[1:end-2] * "]" + + # Creates the string corresponding to the affects. + # Continuous events' `affect` field should probably be called `affects`. Likely the `s` was + # dropped by mistake in MTK. + affects_string = "[" + for affect in continuous_event.affect + @string_append! affects_string expression_2_string(affect; strip_call_dict) ", " + end + affects_string = affects_string[1:end-2] * "]" + + return eqs_string * " => " * affects_string end # Creates an annotation for the system's continuous events. function get_continuous_events_annotation(rn::ReactionSystem) - get_unsupported_comp_annotation("Continuous events:") + return "Continuous events:" end # Combines the 3 -related functions in a constant tuple. @@ -236,17 +286,52 @@ CONTINUOUS_EVENTS_FS = (has_continuous_events, get_continuous_events_string, get # Checks if the reaction system have any discrete events. function has_discrete_events(rn::ReactionSystem) - return false + return length(rn.discrete_events) > 0 end # Extract a string which declares the system's discrete events. function get_discrete_events_string(rn::ReactionSystem) - get_unsupported_comp_string("discrete events") + # Creates a dictionary for converting symbolics to their call-stripped form (e.g. X(t) to X). + strip_call_dict = make_strip_call_dict(unknowns(rn)) + + # Handles the case with one event separately. Only effect is nicer formatting. + if length(rn.discrete_events) == 1 + return "discrete_events = [$(discrete_event_string(rn.discrete_events.value[1], strip_call_dict))]" + end + + # Creates the string corresponding to the code which generates the system's reactions. + discrete_events_string = "discrete_events = [" + for discrete_event in rn.discrete_events.value + @string_append! discrete_events_string "\n\t" discrete_event_string(discrete_event, strip_call_dict) "," + end + + # Updates the string (including removing the last `,`) and returns it. + return discrete_events_string[1:end-1] * "\n]" +end + +# Creates a string that corresponds to the declaration of a single discrete event. +function discrete_event_string(discrete_event, strip_call_dict) + # Creates the string corresponding to the conditions. The special check is if the condition is + # an expression like `X > 5.0`. Here, "(...)" is added for purely aesthetic reasons. + condition_string = x_2_string(discrete_event.condition) + if discrete_event.condition isa SymbolicUtils.BasicSymbolic + @string_prepend! "(" condition_string + @string_append! condition_string ")" + end + + # Creates the string corresponding to the affects. + affects_string = "[" + for affect in discrete_event.affects + @string_append! affects_string expression_2_string(affect; strip_call_dict) ", " + end + affects_string = affects_string[1:end-2] * "]" + + return condition_string * " => " * affects_string end # Creates an annotation for the system's discrete events. function get_discrete_events_annotation(rn::ReactionSystem) - get_unsupported_comp_annotation("Discrete events:") + return "Discrete events:" end # Combines the 3 -related functions in a constant tuple. diff --git a/src/model_serialisation/serialise_reactionsystem.jl b/src/model_serialisation/serialise_reactionsystem.jl index 572776dd86..84443ef48c 100644 --- a/src/model_serialisation/serialise_reactionsystem.jl +++ b/src/model_serialisation/serialise_reactionsystem.jl @@ -5,12 +5,10 @@ Save a `ReactionSystem` model to a file. Work in progress, currently missing features: - Problems with ordering of declarations of species/variables/parameters that have defaults that are other species/variables/parameters. -- Saving of the `sivs` field has not been fully implemented. - Saving of the `observed` field has not been fully implemented. -- Saving of the `continuous_events` field has not been fully implemented. -- Saving of the `discrete_events` field has not been fully implemented. - Saving of the `systems` field has not been fully implemented. - Saving of the `connection_type` field has not been fully implemented. +- equations and reactions are used instead of get_rxs and get_eqs. """ function save_reaction_network(filename::String, rn::ReactionSystem; annotate = true) # Initiates the file string. @@ -31,11 +29,11 @@ function save_reaction_network(filename::String, rn::ReactionSystem; annotate = file_text, has_connection_type = push_field(file_text, rn, annotate, CONNECTION_TYPE_FS) # Finalises the system. Creates the final `ReactionSystem` call. - rs_creation_code = make_reaction_system_call(rn, file_text, annotate, - has_sivs, has_species, has_variables, has_parameters, - has_reactions, has_equations, has_observed, - has_discrete_events, has_continuous_events, - has_systems, has_connection_type) + # Enclose everything ing a `let ... end` block. + rs_creation_code = make_reaction_system_call(rn, annotate, has_sivs, has_species, has_variables, + has_parameters, has_reactions, has_equations, + has_observed, has_continuous_events, + has_discrete_events, has_systems, has_connection_type) annotate || (@string_prepend! "\n" file_text) @string_prepend! "let" file_text @string_append! file_text "\n\n" rs_creation_code "\n\nend" @@ -47,9 +45,9 @@ function save_reaction_network(filename::String, rn::ReactionSystem; annotate = return nothing end -# Takes the actual text which creates the model, and wraps it in a `let ... end` statement and a -# ReactionSystem call. This creates the finalised text that is written to a file. -function make_reaction_system_call(rs::ReactionSystem, file_text, annotate, has_sivs, has_species, +# Creates a ReactionSystem call for creating the model. Adds all the correct inputs to it. The input +# `has_` `Bool`s described which inputs are used. If model is `complete`, this is handled here. +function make_reaction_system_call(rs::ReactionSystem, annotate, has_sivs, has_species, has_variables, has_parameters, has_reactions, has_equations, has_observed, has_continuous_events, has_discrete_events, has_systems, has_connection_type) @@ -105,7 +103,7 @@ function make_reaction_system_call(rs::ReactionSystem, file_text, annotate, has_ has_systems && (@string_append! reaction_system_string ", systems") has_connection_type && (@string_append! reaction_system_string ", connection_type") - # Potentially appends a combinatorial combinatoric_ratelaws statement. + # Potentially appends a combinatoric_ratelaws statement. Symbolics.unwrap(rs.combinatoric_ratelaws) || (@string_append! reaction_system_string ", combinatoric_ratelaws = false") # Potentially appends `ReactionSystem` metadata value(s). Weird composite types are not supported. @@ -113,8 +111,9 @@ function make_reaction_system_call(rs::ReactionSystem, file_text, annotate, has_ # Finalises the call. Appends potential annotation. If the system is complete, add a call for this. @string_append! reaction_system_string ")" - if !ModelingToolkit.iscomplete(rs) - @string_append! reaction_system_string "rs = $(reaction_system_string)\ncomplete(rs)" + if ModelingToolkit.iscomplete(rs) + @string_prepend! "rs = " reaction_system_string + @string_append! reaction_system_string "\nrs = complete(rs)" end if annotate @string_prepend! "# Declares ReactionSystem model:\n" reaction_system_string From 03bbfe327f24f45223a2669a49e567944ee37d43 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 14 Apr 2024 17:08:15 -0400 Subject: [PATCH 112/446] fixes for hierarchial systems --- .../serialisation_support.jl | 3 ++ src/model_serialisation/serialise_fields.jl | 32 +++++++++---------- .../serialise_reactionsystem.jl | 1 - 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/model_serialisation/serialisation_support.jl b/src/model_serialisation/serialisation_support.jl index 29f0aa33a8..dfe92a82b0 100644 --- a/src/model_serialisation/serialisation_support.jl +++ b/src/model_serialisation/serialisation_support.jl @@ -195,4 +195,7 @@ end # For an vector of symbolics, creates a dictionary taking each symbolics to each call-stripped form. function make_strip_call_dict(syms) return Dict([sym => strip_call(Symbolics.unwrap(sym)) for sym in syms]) +end +function make_strip_call_dict(rn::ReactionSystem) + return make_strip_call_dict(get_unknowns(rn)) end \ No newline at end of file diff --git a/src/model_serialisation/serialise_fields.jl b/src/model_serialisation/serialise_fields.jl index afb2671a26..a4f696ae2e 100644 --- a/src/model_serialisation/serialise_fields.jl +++ b/src/model_serialisation/serialise_fields.jl @@ -45,12 +45,12 @@ SIVS_FS = (has_sivs, get_sivs_string, get_sivs_annotation) # Checks if the reaction system have any species. function has_species(rn::ReactionSystem) - return length(species(rn)) != 0 + return !isempty(get_species(rn)) end # Extract a string which declares the system's species. function get_species_string(rn::ReactionSystem) - return "sps = @species$(syms_2_declaration_string(species(rn)))" + return "sps = @species$(syms_2_declaration_string(get_species(rn)))" end # Creates an annotation for the system's species. @@ -66,12 +66,12 @@ SPECIES_FS = (has_species, get_species_string, get_species_annotation) # Checks if the reaction system have any variables. function has_variables(rn::ReactionSystem) - return length(unknowns(rn)) > length(species(rn)) + return length(get_unknowns(rn)) > length(get_species(rn)) end # Extract a string which declares the system's variables. function get_variables_string(rn::ReactionSystem) - variables = filter(!isspecies, unknowns(rn)) + variables = filter(!isspecies, get_unknowns(rn)) return "vars = @variables$(syms_2_declaration_string(variables))" end @@ -88,12 +88,12 @@ VARIABLES_FS = (has_variables, get_variables_string, get_variables_annotation) # Checks if the reaction system have any parameters. function has_parameters(rn::ReactionSystem) - return length(parameters(rn)) != 0 + return length(get_ps(rn)) != 0 end # Extract a string which declares the system's parameters. function get_parameters_string(rn::ReactionSystem) - return "ps = @parameters$(syms_2_declaration_string(parameters(rn)))" + return "ps = @parameters$(syms_2_declaration_string(get_ps(rn)))" end # Creates an annotation for the system's parameters. @@ -115,14 +115,14 @@ end # Extract a string which declares the system's reactions. function get_reactions_string(rn::ReactionSystem) # Creates a dictionary for converting symbolics to their call-stripped form (e.g. X(t) to X). - strip_call_dict = make_strip_call_dict(unknowns(rn)) + strip_call_dict = make_strip_call_dict(rn) # Handles the case with one reaction separately. Only effect is nicer formatting. - (length(reactions(rn)) == 1) && (return "rxs = [$(reaction_string(rx, strip_call_dict))]") + (length(get_rxs(rn)) == 1) && (return "rxs = [$(reaction_string(rx, strip_call_dict))]") # Creates the string corresponding to the code which generates the system's reactions. rxs_string = "rxs = [" - for rx in reactions(rn) + for rx in get_rxs(rn) @string_append! rxs_string "\n\t" * reaction_string(rx, strip_call_dict) "," end @@ -172,22 +172,22 @@ REACTIONS_FS = (has_reactions, get_reactions_string, get_reactions_annotation) # Checks if the reaction system have any equations. function has_equations(rn::ReactionSystem) - return length(equations(rn)) > length(reactions(rn)) + return length(get_eqs(rn)) > length(get_rxs(rn)) end # Extract a string which declares the system's equations. function get_equations_string(rn::ReactionSystem) # Creates a dictionary for converting symbolics to their call-stripped form (e.g. X(t) to X). - strip_call_dict = make_strip_call_dict(unknowns(rn)) + strip_call_dict = make_strip_call_dict(rn) # Handles the case with one equation separately. Only effect is nicer formatting. - if length(equations(rn)) - length(reactions(rn)) == 1 - return "eqs = [$(expression_2_string(equations(rn)[end]; strip_call_dict))]" + if length(get_eqs(rn)) - length(get_rxs(rn)) == 1 + return "eqs = [$(expression_2_string(get_eqs(rn)[end]; strip_call_dict))]" end # Creates the string corresponding to the code which generates the system's reactions. eqs_string = "rxs = [" - for eq in reactions(rn)[length(reactions(rn)) + 1:end] + for eq in get_eqs(rn)[length(get_rxs(rn)) + 1:end] @string_append! eqs_string "\n\t" expression_2_string(eq; strip_call_dict) "," end @@ -235,7 +235,7 @@ end # Extract a string which declares the system's continuous events. function get_continuous_events_string(rn::ReactionSystem) # Creates a dictionary for converting symbolics to their call-stripped form (e.g. X(t) to X). - strip_call_dict = make_strip_call_dict(unknowns(rn)) + strip_call_dict = make_strip_call_dict(rn) # Handles the case with one event separately. Only effect is nicer formatting. if length(rn.continuous_events) == 1 @@ -292,7 +292,7 @@ end # Extract a string which declares the system's discrete events. function get_discrete_events_string(rn::ReactionSystem) # Creates a dictionary for converting symbolics to their call-stripped form (e.g. X(t) to X). - strip_call_dict = make_strip_call_dict(unknowns(rn)) + strip_call_dict = make_strip_call_dict(rn) # Handles the case with one event separately. Only effect is nicer formatting. if length(rn.discrete_events) == 1 diff --git a/src/model_serialisation/serialise_reactionsystem.jl b/src/model_serialisation/serialise_reactionsystem.jl index 84443ef48c..36b574b977 100644 --- a/src/model_serialisation/serialise_reactionsystem.jl +++ b/src/model_serialisation/serialise_reactionsystem.jl @@ -8,7 +8,6 @@ Work in progress, currently missing features: - Saving of the `observed` field has not been fully implemented. - Saving of the `systems` field has not been fully implemented. - Saving of the `connection_type` field has not been fully implemented. -- equations and reactions are used instead of get_rxs and get_eqs. """ function save_reaction_network(filename::String, rn::ReactionSystem; annotate = true) # Initiates the file string. From 713c1519aca1e496faa06e9e3a39cc83dd1007ed Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 14 Apr 2024 21:15:24 -0400 Subject: [PATCH 113/446] Support hierarchial systems, some fixes, start adding tests --- src/Catalyst.jl | 6 +- .../serialisation_support.jl | 57 ++++- .../serialise_fields.jl | 228 +++++++++++++++--- .../serialise_reactionsystem.jl | 60 +++-- .../reactionsystem_serialisation.jl | 92 +++++++ 5 files changed, 386 insertions(+), 57 deletions(-) rename src/{model_serialisation => reactionsystem_serialisation}/serialisation_support.jl (79%) rename src/{model_serialisation => reactionsystem_serialisation}/serialise_fields.jl (56%) rename src/{model_serialisation => reactionsystem_serialisation}/serialise_reactionsystem.jl (72%) create mode 100644 test/miscellaneous_tests/reactionsystem_serialisation.jl diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 7adaf341b3..ea1a9f2c23 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -185,9 +185,9 @@ include("spatial_reaction_systems/lattice_jump_systems.jl") ### ReactionSystem Serialisation ### # Has to be at the end (because it uses records of all metadata declared by Catalyst). -include("model_serialisation/serialisation_support.jl") -include("model_serialisation/serialise_fields.jl") -include("model_serialisation/serialise_reactionsystem.jl") +include("reactionsystem_serialisation/serialisation_support.jl") +include("reactionsystem_serialisation/serialise_fields.jl") +include("reactionsystem_serialisation/serialise_reactionsystem.jl") export save_reaction_network end # module diff --git a/src/model_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl similarity index 79% rename from src/model_serialisation/serialisation_support.jl rename to src/reactionsystem_serialisation/serialisation_support.jl index dfe92a82b0..7c4d08b6ac 100644 --- a/src/model_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -65,7 +65,7 @@ end # the code required to declare them (potential @parameters or @species commands must still be added). # The `multiline_format` option formats it with a `begin ... end` block and declarations on separate lines. function syms_2_declaration_string(syms; multiline_format = false) - decs_string = (multiline_format ? "begin" : "") + decs_string = (multiline_format ? " begin" : "") for sym in syms delimiter = (multiline_format ? "\n\t" : " ") @string_append! decs_string delimiter sym_2_declaration_string(sym; multiline_format) @@ -81,6 +81,17 @@ function sym_2_declaration_string(sym; multiline_format = false) # Creates the basic symbol. The `"$(sym)"` ensures that we get e.g. "X(t)" and not "X". dec_string = "$(sym)" + # If the symbol have a non-default type, appends the declaration of this. + # Assumes that the type is on the form `SymbolicUtils.BasicSymbolic{X}`. Contain error checks + # to ensure that this is the case. + if !(sym isa SymbolicUtils.BasicSymbolic{Real}) + sym_type = String(Symbol(typeof(Symbolics.unwrap(k2)))) + if (sym_type[1:28] != "SymbolicUtils.BasicSymbolic{") || (sym_type[end] != '}') + error("Encountered symbolic of unexpected type: $sym_type.") + end + @string_append! dec_string "::" sym_type[29:end-1] + end + # If there is a default value, adds this to the declaration. if ModelingToolkit.hasdefault(sym) def_val = x_2_string(ModelingToolkit.getdefault(sym)) @@ -198,4 +209,48 @@ function make_strip_call_dict(syms) end function make_strip_call_dict(rn::ReactionSystem) return make_strip_call_dict(get_unknowns(rn)) +end + + +### Handle Parameters/Species/Variables Declaration Dependencies ### + +# Gets a vector with the symbolics a symbolic depends on (currently only considers defaults). +function get_dep_syms(sym) + ModelingToolkit.hasdefault(sym) || return [] + return Symbolics.get_variables(ModelingToolkit.getdefault(sym)) +end + +# Checks if a symbolic depends on an symbolics in a vector being declared. +# Because Symbolics has to utilise `isequal`, the `isdisjoint` function cannot be used. +function depends_on(sym, syms) + dep_syms = get_dep_syms(sym) + for s1 in dep_syms + for s2 in syms + isequal(s1, s2) && return true + end + end + return false +end + +# For a set of remaining parameters/species/variables (remaining_syms), return this split into +# two sets: +# One with those that does not depend on any sym in `all_remaining_syms`. +# One with those that does depend on at least one sym in `all_remaining_syms`. +function dependency_split(all_remaining_syms, remaining_syms) + writable_syms = filter(sym -> !depends_on(sym, all_remaining_syms), remaining_syms) + nonwritable_syms = filter(sym -> depends_on(sym, all_remaining_syms), remaining_syms) + return writable_syms, nonwritable_syms +end + + +### Other Functions ### + + +# Checks if a symbolic's declaration is "complicated". The declaration is considered complicated +# if it have metadata, default value, or type designation that must be declared. +function complicated_declaration(sym) + isempty(get_metadata_to_declare(sym)) || (return true) + ModelingToolkit.hasdefault(sym) && (return true) + (sym isa SymbolicUtils.BasicSymbolic{Real}) || (return true) + return false end \ No newline at end of file diff --git a/src/model_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl similarity index 56% rename from src/model_serialisation/serialise_fields.jl rename to src/reactionsystem_serialisation/serialise_fields.jl index a4f696ae2e..1b31c7e879 100644 --- a/src/model_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -41,16 +41,130 @@ end SIVS_FS = (has_sivs, get_sivs_string, get_sivs_annotation) +### Handles Species, Variables, and Parameters ### + +# Function which handles the addition of species, variable, and parameter declarations to the file +# text. These must be handled as a unity in case there are default value dependencies between these. +function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool) + # Fetches the systems parameters, species, and variables. Computes the `has_` `Bool`s. + ps_all = get_ps(rn) + sps_all = get_species(rn) + vars_all = filter(!isspecies, get_unknowns(rn)) + has_ps = has_parameters(rn) + has_sps = has_species(rn) + has_vars = has_variables(rn) + + # Checks which sets have dependencies which requires managing. + p_deps = any(depends_on(p, [ps_all; sps_all; vars_all]) for p in ps_all) + sp_deps = any(depends_on(sp, [sps_all; vars_all]) for sp in sps_all) + var_deps = any(depends_on(var, vars_all) for var in vars_all) + + # Makes the initial declaration. + if !p_deps && has_ps + annotate && (@string_append! file_text "\n\n# " get_parameters_annotation(rn)) + @string_append! file_text "\nps = " get_parameters_string(ps_all) + end + if !sp_deps && has_sps + annotate && (@string_append! file_text "\n\n# " get_species_annotation(rn)) + @string_append! file_text "\nsps = " get_species_string(sps_all) + end + if !var_deps && has_vars + annotate && (@string_append! file_text "\n\n# " get_variables_annotation(rn)) + @string_append! file_text "\nvars = " get_variables_string(vars_all) + end + + # If any set have dependencies, handle these. + # There are cases where the dependent syms come after their dependencies in the vector + # (e.g. corresponding to `@parameters p1 p2=p1`) + # which would not require this special treatment. However, this is currently not considered. + # Considering it would make the written code prettier, but would also require additional + # work in these functions to handle these cases (can be sorted out in the future). + if p_deps || sp_deps || var_deps + # Builds an annotation mentioning specially handled stuff. + if annotate + @string_append! file_text "\n\n# Some " + p_deps && (@string_append! file_text "parameters, ") + sp_deps && (@string_append! file_text "species, ") + var_deps && (@string_append! file_text "variables, ") + file_text = file_text[1:end-2] + @string_append! file_text " depends on the declaration of other parameters, species, and/or variables.\n# These are specially handled here.\n" + end + + # Pre-declares the sets with written/remaining parameters/species/variables. + # Whenever all/none are written depends on whether there were any initial dependencies. + remaining_ps = (p_deps ? ps_all : []) + remaining_sps = (sp_deps ? sps_all : []) + remaining_vars = (var_deps ? vars_all : []) + + # Iteratively loops through all parameters, species, and/or variables. In each iteration, + # adds the declaration of those that can still be declared. + while !(isempty(remaining_ps) && isempty(remaining_sps) && isempty(remaining_vars)) + # Checks which parameters/species/variables can be written. + writable_ps, nonwritable_ps = dependency_split([remaining_ps; remaining_sps; remaining_vars], remaining_ps) + writable_sps, nonwritable_sps = dependency_split([remaining_sps; remaining_vars], remaining_sps) + writable_vars, nonwritable_vars = dependency_split(remaining_vars, remaining_vars) + + # Writes those that can be written. + isempty(writable_ps) || @string_append! file_text get_parameters_string(writable_ps) "\n" + isempty(writable_sps) || @string_append! file_text get_species_string(writable_sps) "\n" + isempty(writable_vars) || @string_append! file_text get_variables_string(writable_vars) "\n" + + # Updates the remaining parameters/species/variables sets. + remaining_ps = nonwritable_ps + remaining_sps = nonwritable_sps + remaining_vars = nonwritable_vars + end + + # For parameters, species, and/or variables with dependencies, creates final vectors. + p_deps && (@string_append! file_text "ps = " syms_2_strings(ps_all) "\n") + sp_deps && (@string_append! file_text "sps = " syms_2_strings(sps_all) "\n") + var_deps && (@string_append! file_text "vars = " syms_2_strings(vars_all) "\n") + file_text = file_text[1:end-1] + end + + # Returns the finalised output. + return file_text, has_ps, has_sps, has_vars +end + + +### Handles Parameters ### +# Unlike most other fields, there are not called via `push_field`, but rather via `handle_us_n_ps`. +# Hence they work slightly differently. + +# Checks if the reaction system have any parameters. +function has_parameters(rn::ReactionSystem) + return !isempty(get_ps(rn)) +end + +# Extract a string which declares the system's parameters. Uses multiline declaration (a +# `begin ... end` block) if more than 3 parameters have a "complicated" declaration (if they +# have metadata, default value, or type designation). +function get_parameters_string(ps) + multiline_format = count(complicated_declaration(p) for p in ps) > 3 + return "@parameters$(syms_2_declaration_string(ps; multiline_format))" +end + +# Creates an annotation for the system's parameters. +function get_parameters_annotation(rn::ReactionSystem) + return "Parameters:" +end + + ### Handles Species ### +# Unlike most other fields, there are not called via `push_field`, but rather via `handle_us_n_ps`. +# Hence they work slightly differently. # Checks if the reaction system have any species. function has_species(rn::ReactionSystem) return !isempty(get_species(rn)) end -# Extract a string which declares the system's species. -function get_species_string(rn::ReactionSystem) - return "sps = @species$(syms_2_declaration_string(get_species(rn)))" +# Extract a string which declares the system's species. Uses multiline declaration (a +# `begin ... end` block) if more than 3 species have a "complicated" declaration (if they +# have metadata, default value, or type designation). +function get_species_string(sps) + multiline_format = count(complicated_declaration(sp) for sp in sps) > 3 + return "@species$(syms_2_declaration_string(sps; multiline_format))" end # Creates an annotation for the system's species. @@ -58,21 +172,22 @@ function get_species_annotation(rn::ReactionSystem) return "Species:" end -# Combines the 3 species-related functions in a constant tuple. -SPECIES_FS = (has_species, get_species_string, get_species_annotation) - ### Handles Variables ### +# Unlike most other fields, there are not called via `push_field`, but rather via `handle_us_n_ps`. +# Hence they work slightly differently. # Checks if the reaction system have any variables. function has_variables(rn::ReactionSystem) return length(get_unknowns(rn)) > length(get_species(rn)) end -# Extract a string which declares the system's variables. -function get_variables_string(rn::ReactionSystem) - variables = filter(!isspecies, get_unknowns(rn)) - return "vars = @variables$(syms_2_declaration_string(variables))" +# Extract a string which declares the system's variables. Uses multiline declaration (a +# `begin ... end` block) if more than 3 variables have a "complicated" declaration (if they +# have metadata, default value, or type designation). +function get_variables_string(vars) + multiline_format = count(complicated_declaration(var) for var in vars) > 3 + return "@variables$(syms_2_declaration_string(vars; multiline_format))" end # Creates an annotation for the system's . @@ -84,27 +199,6 @@ end VARIABLES_FS = (has_variables, get_variables_string, get_variables_annotation) -### Handles Parameters ### - -# Checks if the reaction system have any parameters. -function has_parameters(rn::ReactionSystem) - return length(get_ps(rn)) != 0 -end - -# Extract a string which declares the system's parameters. -function get_parameters_string(rn::ReactionSystem) - return "ps = @parameters$(syms_2_declaration_string(get_ps(rn)))" -end - -# Creates an annotation for the system's parameters. -function get_parameters_annotation(rn::ReactionSystem) - return "Parameters:" -end - -# Combines the 3 parameters-related functions in a constant tuple. -PARAMETERS_FS = (has_parameters, get_parameters_string, get_parameters_annotation) - - ### Handles Reactions ### # Checks if the reaction system have any reactions. @@ -208,12 +302,42 @@ EQUATIONS_FS = (has_equations, get_equations_string, get_equations_annotation) # Checks if the reaction system have any observables. function has_observed(rn::ReactionSystem) - return false + return !isempty(observed(rn)) end # Extract a string which declares the system's observables. function get_observed_string(rn::ReactionSystem) - get_unsupported_comp_string("observables") + # Finds the observable species and variables. + observed_unknowns = [obs_eq.lhs for obs_eq in observed(rn)] + observed_species = filter(isspecies, observed_unknowns) + observed_variables = filter(!isspecies, observed_unknowns) + + # Creates a dictionary for converting symbolics to their call-stripped form (e.g. X(t) to X). + strip_call_dict = make_strip_call_dict([get_unknowns(rn); observed_unknowns]) + + # Initialises the observables string with declaring the observable species/variables. + observed_string = "" + if !isempty(observed_species) + @string_append! observed_string "@species$(syms_2_declaration_string(observed_species))\n" + end + if !isempty(observed_variables) + @string_append! observed_string "@variables$(syms_2_declaration_string(observed_variables))\n" + end + + # Handles the case with one observable separately. Only effect is nicer formatting. + if length(observed(rn)) == 1 + @string_append! observed_string "observed = [$(expression_2_string(observed(rn)[1]; strip_call_dict))]" + return observed_string + end + + # Appends with the code which will generate observables equations. + @string_append! observed_string "observed = [" + for obs in observed(rn) + @string_append! observed_string "\n\t" expression_2_string(obs, strip_call_dict) "," + end + + # Updates the string (including removing the last `,`) and returns it. + return observed_string[1:end-1] * "\n]" end # Creates an annotation for the system's observables. @@ -340,19 +464,49 @@ DISCRETE_EVENTS_FS = (has_discrete_events, get_discrete_events_string, get_discr ### Handles Systems ### +# Specific `push_field` function, which is used for the system field (where the annotation option +# must be passed to the `get_component_string` function). Since non-ReactionSystem systems cannot be +# written to file, this functions throws an error if any such systems are encountered. +function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Bool) + # Checks whther there are any subsystems, and if these are ReactionSystems. + has_systems(rn) || (return (file_text, false)) + if any(!(system isa ReactionSystem) for system in ModelingToolkit.get_systems(rn)) + error("Tries to write a ReactionSystem to file which have non-ReactionSystem subs-systems. This is currently not possible.") + end + + # Adds the system declaration string to the file string. + write_string = "\n" * get_systems_string(rn, annotate) + annotate && (@string_prepend! "\n\n# " get_systems_annotation(rn) write_string) + return (file_text * write_string, true) +end + # Checks if the reaction system have any systems. function has_systems(rn::ReactionSystem) - return false + return !isempty(ModelingToolkit.get_systems(rn)) end # Extract a string which declares the system's systems. -function get_systems_string(rn::ReactionSystem) - get_unsupported_comp_string("systems") +function get_systems_string(rn::ReactionSystem, annotate::Bool) + # Initiates the `systems` string. It is pre-declared vector, into which the systems are added. + systems_string = "systems = Vector(undef, $(length(ModelingToolkit.get_systems(rn))))" + + # Loops through all systems, adding their declaration to the system string. + for (idx, system) in enumerate(ModelingToolkit.get_systems(rn)) + annotate && (@string_append! systems_string "\n\n# Declares subsystem: $(getname(system))") + + # Manipulates the subsystem declaration to make it nicer. + subsystem_string = get_full_system_string(system, annotate) + subsystem_string = replace(subsystem_string, "\n" => "\n\t") + subsystem_string = "let\n" * subsystem_string[7:end-6] * "end" + @string_append! systems_string "\nsystems[$idx] = " subsystem_string + end + + return systems_string end # Creates an annotation for the system's systems. function get_systems_annotation(rn::ReactionSystem) - get_unsupported_comp_annotation("Systems:") + return "Subystems:" end # Combines the 3 systems-related functions in a constant tuple. diff --git a/src/model_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl similarity index 72% rename from src/model_serialisation/serialise_reactionsystem.jl rename to src/reactionsystem_serialisation/serialise_reactionsystem.jl index 36b574b977..bcaa89643f 100644 --- a/src/model_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -1,30 +1,62 @@ """ save_reaction_network(filename::String, rn::ReactionSystem; annotate = true) -Save a `ReactionSystem` model to a file. - -Work in progress, currently missing features: -- Problems with ordering of declarations of species/variables/parameters that have defaults that are other species/variables/parameters. -- Saving of the `observed` field has not been fully implemented. -- Saving of the `systems` field has not been fully implemented. -- Saving of the `connection_type` field has not been fully implemented. +Save a `ReactionSystem` model to a file. The `ReactionSystem` is saved as runnable Julia code. This +can both be used to save a `ReactionSystem` model, but also to write it to a file for easy inspection. + +Arguments: +- `filename`: The name of the file to which the `ReactionSystem` is saved. +- `rn`: The `ReactionSystem` which should be saved to a file. +- `annotate = true`: Whether annotation should be added to the file. + +Example: +```julia +rn = @reaction_network begin + (p,d), 0 <--> X +end +save_reaction_network("rn.jls", rn) +``` +The model can now be loaded using +```julia +rn = include("rn.jls") +``` + +Notes: +- `ReactionSystem`s with the `connection_type` field has this ignored (saving of this field has not + been implemented yet). +- `ReactionSystem`s with non-`ReactionSystem` sub-systems (e.g. `ODESystem`s) cannot be saved. +- The `ReactionSystem` is saved using *programmatic* (not DSL) format for model creation. """ function save_reaction_network(filename::String, rn::ReactionSystem; annotate = true) + open(filename, "w") do file + write(file, get_full_system_string(rn, annotate)) + end + return nothing +end + +# Gets the full string which corresponds to the declaration of a system. Might be called recursively +# for systems with subsystems. +function get_full_system_string(rn::ReactionSystem, annotate::Bool) # Initiates the file string. file_text = "" + # Sub-systems must (unfortunately) be declared first (as the variables written in their internal + # let blocks otherwise will overwrite those of the main system). + # Systems are uses custom `push_field` function as these requires the annotation `Bool` + # to be passed to the function that creates the next sub-system declarations. + file_text, has_systems = push_systems_field(file_text, rn, annotate) + # Goes through each type of system component, potentially adding it to the string. + # Species, variables, and parameters must be handled differently in case there is default-values + # dependencies between them. file_text, _ = push_field(file_text, rn, annotate, IV_FS) file_text, has_sivs = push_field(file_text, rn, annotate, SIVS_FS) - file_text, has_species = push_field(file_text, rn, annotate, SPECIES_FS) - file_text, has_variables = push_field(file_text, rn, annotate, VARIABLES_FS) - file_text, has_parameters = push_field(file_text, rn, annotate, PARAMETERS_FS) + file_text, has_parameters, has_species, has_variables = handle_us_n_ps(file_text, rn, annotate) file_text, has_reactions = push_field(file_text, rn, annotate, REACTIONS_FS) file_text, has_equations = push_field(file_text, rn, annotate, EQUATIONS_FS) file_text, has_observed = push_field(file_text, rn, annotate, OBSERVED_FS) file_text, has_continuous_events = push_field(file_text, rn, annotate, CONTINUOUS_EVENTS_FS) file_text, has_discrete_events = push_field(file_text, rn, annotate, DISCRETE_EVENTS_FS) - file_text, has_systems = push_field(file_text, rn, annotate, SYSTEMS_FS) file_text, has_connection_type = push_field(file_text, rn, annotate, CONNECTION_TYPE_FS) # Finalises the system. Creates the final `ReactionSystem` call. @@ -37,11 +69,7 @@ function save_reaction_network(filename::String, rn::ReactionSystem; annotate = @string_prepend! "let" file_text @string_append! file_text "\n\n" rs_creation_code "\n\nend" - # Writes the model to a file. Then, returns nothing. - open(filename, "w") do file - write(file, file_text) - end - return nothing + return file_text end # Creates a ReactionSystem call for creating the model. Adds all the correct inputs to it. The input diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl new file mode 100644 index 0000000000..791a296dc3 --- /dev/null +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -0,0 +1,92 @@ +### Prepare Tests ### + +# Fetch packages. +using Catalyst + +# Sets the default `t` and `D` to use. +t = default_t() +D = default_time_deriv() + + +### Basic Test ### + +# Checks for a simple reaction network (containing variables, equations, and observables). +# Checks that declaration via DSL works. +# Checks annotated and non-annotated files against manually written ones. +let + # Creates and serialises the model. + rn = @reaction_system begin + @observables X2 ~ 2X + @equations D(V) ~ 1 - V + (p,d), 0 <--> X + end + save_reaction_network("test_serialisation_annotated.jl", rn) + save_reaction_network("test_serialisation.jl", rn; annotate = false) + + # Checks equivalence. + file_string_annotated = read("test_serialisation_annotated.jl", String) + file_string = read("test_serialisation.jl", String) + file_string_annotated_real = "" + file_string_real = "" + @test file_string_annotated == file_string_annotated_real + @test file == file_string_real + + # Deletes the files. + rm("test_serialisation_annotated.jl") + rm("test_serialisation.jl") +end + +# Tests for hierarchical system created programmatically. +# Checks that the species, variables, and parameters have their non-default types, default values, +# and metadata recorded correctly (these are not considered for system equality is tested). +let + +end + +# Tests for complicated hierarchical system. Tests with non-default independent variable, +# spatial independent variables, variables, (differential and algebraic) equations, observables +# (continuous and discrete) events, and with various species/variables/parameter metadata. +let + +end + + +### Other Tests ### + +# Tests that an error is generated when non-`ReactionSystem` subs-systems are used. +let + @variables V(t) + @species X(t) + @parameters p d V_max + + rxs = [ + Reaction(p, [], [X]), + Reaction(d, [X], []) + ] + eq = D(V) ~ V_max - V + + @named osys = ODESystem([eq], t) + @named rs = ReactionSystem(rxs, t; systems = [osys]) + @test_throws Exception save_reaction_network("failed_serialisation.jl", rs) +end + +# Checks that completeness is recorded correctly. +let + # Checks for complete system. + rs_complete = @reaction_network begin + (p,d), 0 <--> X + end + save_reaction_network("serialised_rs_complete.jl", rs_complete) + rs_complete_loaded = include("serialised_rs_complete.jl") + @test ModelingToolkit.iscomplete(rs_complete_loaded) + rn("serialised_rs_complete.jl") + + # Checks for non-complete system. + rs_incomplete = @network_component begin + (p,d), 0 <--> X + end + save_reaction_network("serialised_rs_incomplete.jl", rs_incomplete) + rs_incomplete_loaded = include("serialised_rs_incomplete.jl") + @test !ModelingToolkit.iscomplete(rs_incomplete_loaded) + rn("serialised_rs_incomplete.jl") +end \ No newline at end of file From 68d22e6d1d0a49a4df04b3e786c4b53796656abe Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 15 Apr 2024 15:03:31 -0400 Subject: [PATCH 114/446] add safety_check function, finish tests, finish docstring --- .../serialisation_support.jl | 35 +- .../serialise_fields.jl | 66 ++-- .../serialise_reactionsystem.jl | 17 +- .../reactionsystem_serialisation.jl | 304 +++++++++++++++++- 4 files changed, 372 insertions(+), 50 deletions(-) diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 7c4d08b6ac..2e41e24f46 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -119,26 +119,46 @@ end # more supported types can be added here. x_2_string(x::Num) = expression_2_string(x) x_2_string(x::SymbolicUtils.BasicSymbolic{<:Real}) = expression_2_string(x) +x_2_string(x::Bool) = string(x) x_2_string(x::String) = "\"$x\"" x_2_string(x::Char) = "\'$x\'" x_2_string(x::Symbol) = ":$x" x_2_string(x::Number) = string(x) +x_2_string(x::Pair) = "$(x_2_string(x[1])) => $(x_2_string(x[2]))" +x_2_string(x::Nothing) = "nothing" function x_2_string(x::Vector) output = "[" for val in x - output = output * x_2_string(val) * ", " + @string_append! output x_2_string(val) ", " end return output[1:end-2] * "]" end function x_2_string(x::Tuple) output = "(" for val in x - output = output * x_2_string(val) * ", " + @string_append! output x_2_string(val) ", " end return output[1:end-2] * ")" end -x_2_string(x::Pair) = "$(x_2_string(x[1])) => $(x_2_string(x[2]))" -x_2_string(x::Nothing) = "nothing" +function x_2_string(x::Dict) + output = "Dict([" + for key in keys(x) + @string_append! output x_2_string(key) " => " x_2_string(x[key]) ", " + end + return output[1:end-2] * "])" +end +function x_2_string(x::Union{Matrix, Symbolics.Arr{Any, 2}}) + output = "[" + for j = 1:size(x)[1] + for i = 1:size(x)[2] + @string_append! output x_2_string(x[j,i]) " " + end + output = output[1:end-1] * "; " + end + return output[1:end-2] *"]" +end + + x_2_string(x) = error("Tried to write an unsupported value ($(x)) of an unsupported type ($(typeof(x))) to a string.") @@ -236,10 +256,11 @@ end # two sets: # One with those that does not depend on any sym in `all_remaining_syms`. # One with those that does depend on at least one sym in `all_remaining_syms`. -function dependency_split(all_remaining_syms, remaining_syms) +# The first set is returned. Next `remaining_syms` is updated to be the second set. +function dependency_split!(remaining_syms, all_remaining_syms) writable_syms = filter(sym -> !depends_on(sym, all_remaining_syms), remaining_syms) - nonwritable_syms = filter(sym -> depends_on(sym, all_remaining_syms), remaining_syms) - return writable_syms, nonwritable_syms + filter!(sym -> depends_on(sym, all_remaining_syms), remaining_syms) + return writable_syms end diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index 1b31c7e879..3c52265979 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -7,7 +7,7 @@ end # Extract a string which declares the system's independent variable. function get_iv_string(rn::ReactionSystem) - iv_dec = ModelingToolkit.get_iv(rn) + iv_dec = MT.get_iv(rn) return "@variables $(iv_dec)" end @@ -29,7 +29,7 @@ end # Extract a string which declares the system's spatial independent variables. function get_sivs_string(rn::ReactionSystem) - return "spatial_ivs = @variables$(get_species_string(get_sivs(rn)))" + return "spatial_ivs = @variables$(syms_2_declaration_string(get_sivs(rn)))" end # Creates an annotation for the system's spatial independent variables. @@ -92,27 +92,24 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool) # Pre-declares the sets with written/remaining parameters/species/variables. # Whenever all/none are written depends on whether there were any initial dependencies. - remaining_ps = (p_deps ? ps_all : []) - remaining_sps = (sp_deps ? sps_all : []) - remaining_vars = (var_deps ? vars_all : []) + # `deepcopy` is required as these gets mutated by `dependency_split!`. + remaining_ps = (p_deps ? deepcopy(ps_all) : []) + remaining_sps = (sp_deps ? deepcopy(sps_all) : []) + remaining_vars = (var_deps ? deepcopy(vars_all) : []) # Iteratively loops through all parameters, species, and/or variables. In each iteration, # adds the declaration of those that can still be declared. while !(isempty(remaining_ps) && isempty(remaining_sps) && isempty(remaining_vars)) - # Checks which parameters/species/variables can be written. - writable_ps, nonwritable_ps = dependency_split([remaining_ps; remaining_sps; remaining_vars], remaining_ps) - writable_sps, nonwritable_sps = dependency_split([remaining_sps; remaining_vars], remaining_sps) - writable_vars, nonwritable_vars = dependency_split(remaining_vars, remaining_vars) + # Checks which parameters/species/variables can be written. The `dependency_split` + # function updates the `remaining_` input. + writable_ps = dependency_split!(remaining_ps, [remaining_ps; remaining_sps; remaining_vars]) + writable_sps = dependency_split!(remaining_sps, [remaining_ps; remaining_sps; remaining_vars]) + writable_vars = dependency_split!(remaining_vars, [remaining_ps; remaining_sps; remaining_vars]) # Writes those that can be written. isempty(writable_ps) || @string_append! file_text get_parameters_string(writable_ps) "\n" isempty(writable_sps) || @string_append! file_text get_species_string(writable_sps) "\n" isempty(writable_vars) || @string_append! file_text get_variables_string(writable_vars) "\n" - - # Updates the remaining parameters/species/variables sets. - remaining_ps = nonwritable_ps - remaining_sps = nonwritable_sps - remaining_vars = nonwritable_vars end # For parameters, species, and/or variables with dependencies, creates final vectors. @@ -280,7 +277,7 @@ function get_equations_string(rn::ReactionSystem) end # Creates the string corresponding to the code which generates the system's reactions. - eqs_string = "rxs = [" + eqs_string = "eqs = [" for eq in get_eqs(rn)[length(get_rxs(rn)) + 1:end] @string_append! eqs_string "\n\t" expression_2_string(eq; strip_call_dict) "," end @@ -308,7 +305,7 @@ end # Extract a string which declares the system's observables. function get_observed_string(rn::ReactionSystem) # Finds the observable species and variables. - observed_unknowns = [obs_eq.lhs for obs_eq in observed(rn)] + observed_unknowns = [obs_eq.lhs for obs_eq in MT.get_observed(rn)] observed_species = filter(isspecies, observed_unknowns) observed_variables = filter(!isspecies, observed_unknowns) @@ -325,15 +322,15 @@ function get_observed_string(rn::ReactionSystem) end # Handles the case with one observable separately. Only effect is nicer formatting. - if length(observed(rn)) == 1 - @string_append! observed_string "observed = [$(expression_2_string(observed(rn)[1]; strip_call_dict))]" + if length(MT.get_observed(rn)) == 1 + @string_append! observed_string "observed = [$(expression_2_string(MT.get_observed(rn)[1]; strip_call_dict))]" return observed_string end # Appends with the code which will generate observables equations. @string_append! observed_string "observed = [" - for obs in observed(rn) - @string_append! observed_string "\n\t" expression_2_string(obs, strip_call_dict) "," + for obs in MT.get_observed(rn) + @string_append! observed_string "\n\t" expression_2_string(obs; strip_call_dict) "," end # Updates the string (including removing the last `,`) and returns it. @@ -353,7 +350,7 @@ OBSERVED_FS = (has_observed, get_observed_string, get_observed_annotation) # Checks if the reaction system have any continuous events. function has_continuous_events(rn::ReactionSystem) - return length(rn.continuous_events) > 0 + return !isempty(MT.get_continuous_events(rn)) end # Extract a string which declares the system's continuous events. @@ -362,13 +359,13 @@ function get_continuous_events_string(rn::ReactionSystem) strip_call_dict = make_strip_call_dict(rn) # Handles the case with one event separately. Only effect is nicer formatting. - if length(rn.continuous_events) == 1 - return "continuous_events = [$(continuous_event_string(rn.continuous_events.value[1], strip_call_dict))]" + if length(MT.get_continuous_events(rn)) == 1 + return "continuous_events = [$(continuous_event_string(MT.get_continuous_events(rn)[1], strip_call_dict))]" end # Creates the string corresponding to the code which generates the system's reactions. continuous_events_string = "continuous_events = [" - for continuous_event in rn.continuous_events.value + for continuous_event in MT.get_continuous_events(rn) @string_append! continuous_events_string "\n\t" continuous_event_string(continuous_event, strip_call_dict) "," end @@ -410,7 +407,7 @@ CONTINUOUS_EVENTS_FS = (has_continuous_events, get_continuous_events_string, get # Checks if the reaction system have any discrete events. function has_discrete_events(rn::ReactionSystem) - return length(rn.discrete_events) > 0 + return !isempty(MT.get_discrete_events(rn)) end # Extract a string which declares the system's discrete events. @@ -419,13 +416,13 @@ function get_discrete_events_string(rn::ReactionSystem) strip_call_dict = make_strip_call_dict(rn) # Handles the case with one event separately. Only effect is nicer formatting. - if length(rn.discrete_events) == 1 - return "discrete_events = [$(discrete_event_string(rn.discrete_events.value[1], strip_call_dict))]" + if length(MT.get_discrete_events(rn)) == 1 + return "discrete_events = [$(discrete_event_string(MT.get_discrete_events(rn)[1], strip_call_dict))]" end # Creates the string corresponding to the code which generates the system's reactions. discrete_events_string = "discrete_events = [" - for discrete_event in rn.discrete_events.value + for discrete_event in MT.get_discrete_events(rn) @string_append! discrete_events_string "\n\t" discrete_event_string(discrete_event, strip_call_dict) "," end @@ -470,7 +467,7 @@ DISCRETE_EVENTS_FS = (has_discrete_events, get_discrete_events_string, get_discr function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Bool) # Checks whther there are any subsystems, and if these are ReactionSystems. has_systems(rn) || (return (file_text, false)) - if any(!(system isa ReactionSystem) for system in ModelingToolkit.get_systems(rn)) + if any(!(system isa ReactionSystem) for system in MT.get_systems(rn)) error("Tries to write a ReactionSystem to file which have non-ReactionSystem subs-systems. This is currently not possible.") end @@ -482,23 +479,26 @@ end # Checks if the reaction system have any systems. function has_systems(rn::ReactionSystem) - return !isempty(ModelingToolkit.get_systems(rn)) + return !isempty(MT.get_systems(rn)) end # Extract a string which declares the system's systems. +# The systems variable (`systems_X`) is the only variable where we append an identifier. This is +# to avoid it getting overwritten by other systems variables (not required for other variables +# due to the order they appear in). function get_systems_string(rn::ReactionSystem, annotate::Bool) # Initiates the `systems` string. It is pre-declared vector, into which the systems are added. - systems_string = "systems = Vector(undef, $(length(ModelingToolkit.get_systems(rn))))" + systems_string = "systems_$(getname(rn)) = Vector(undef, $(length(MT.get_systems(rn))))" # Loops through all systems, adding their declaration to the system string. - for (idx, system) in enumerate(ModelingToolkit.get_systems(rn)) + for (idx, system) in enumerate(MT.get_systems(rn)) annotate && (@string_append! systems_string "\n\n# Declares subsystem: $(getname(system))") # Manipulates the subsystem declaration to make it nicer. subsystem_string = get_full_system_string(system, annotate) subsystem_string = replace(subsystem_string, "\n" => "\n\t") subsystem_string = "let\n" * subsystem_string[7:end-6] * "end" - @string_append! systems_string "\nsystems[$idx] = " subsystem_string + @string_append! systems_string "\nsystems_$(getname(rn))[$idx] = " subsystem_string end return systems_string diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index bcaa89643f..d346c53fc5 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -1,5 +1,5 @@ """ - save_reaction_network(filename::String, rn::ReactionSystem; annotate = true) + save_reaction_network(filename::String, rn::ReactionSystem; annotate = true, safety_check = true) Save a `ReactionSystem` model to a file. The `ReactionSystem` is saved as runnable Julia code. This can both be used to save a `ReactionSystem` model, but also to write it to a file for easy inspection. @@ -8,6 +8,10 @@ Arguments: - `filename`: The name of the file to which the `ReactionSystem` is saved. - `rn`: The `ReactionSystem` which should be saved to a file. - `annotate = true`: Whether annotation should be added to the file. +- `safety_check = true`: After serialisation, Catalyst will automatically load the serialised + `ReactionSystem` and check that it is equal to `rn`. If it is not, an error will be thrown. For + models without the `connection_type` field, this should not happen. If performance is required + (i.e. when saving a large number of models), this can be disabled by setting `safety_check = false`. Example: ```julia @@ -25,12 +29,17 @@ Notes: - `ReactionSystem`s with the `connection_type` field has this ignored (saving of this field has not been implemented yet). - `ReactionSystem`s with non-`ReactionSystem` sub-systems (e.g. `ODESystem`s) cannot be saved. +- Reaction systems with components that have units cannot currently be saved. - The `ReactionSystem` is saved using *programmatic* (not DSL) format for model creation. """ -function save_reaction_network(filename::String, rn::ReactionSystem; annotate = true) +function save_reaction_network(filename::String, rn::ReactionSystem; annotate = true, safety_check = true) open(filename, "w") do file write(file, get_full_system_string(rn, annotate)) end + if safety_check && !isequal(rn, include(filename)) + rm(filename) + error("The serialised `ReactionSystem` is not equal to the original one. Please make a report (including the full system) at https://github.com/SciML/Catalyst.jl/issues. To disable this behaviour, please pass the `safety_check = false` argument to `save_reaction_network` (warning, this will permit the serialisation of an erroneous system).") + end return nothing end @@ -127,7 +136,7 @@ function make_reaction_system_call(rs::ReactionSystem, annotate, has_sivs, has_s has_observed && (@string_append! reaction_system_string ", observed") has_continuous_events && (@string_append! reaction_system_string ", continuous_events") has_discrete_events && (@string_append! reaction_system_string ", discrete_events") - has_systems && (@string_append! reaction_system_string ", systems") + has_systems && (@string_append! reaction_system_string ", systems = systems_$(getname(rs))") has_connection_type && (@string_append! reaction_system_string ", connection_type") # Potentially appends a combinatoric_ratelaws statement. @@ -140,7 +149,7 @@ function make_reaction_system_call(rs::ReactionSystem, annotate, has_sivs, has_s @string_append! reaction_system_string ")" if ModelingToolkit.iscomplete(rs) @string_prepend! "rs = " reaction_system_string - @string_append! reaction_system_string "\nrs = complete(rs)" + @string_append! reaction_system_string "\ncomplete(rs)" end if annotate @string_prepend! "# Declares ReactionSystem model:\n" reaction_system_string diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index 791a296dc3..7059c56088 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -2,6 +2,11 @@ # Fetch packages. using Catalyst +using ModelingToolkit: getdefault, getdescription, get_metadata + +# Creates missing getters for MTK metadata (can be removed once added to MTK). +getmisc(x) = SymbolicUtils.getmetadata(Symbolics.unwrap(x), ModelingToolkit.VariableMisc, nothing) +getinput(x) = SymbolicUtils.getmetadata(Symbolics.unwrap(x), ModelingToolkit.VariableInput, nothing) # Sets the default `t` and `D` to use. t = default_t() @@ -20,8 +25,8 @@ let @equations D(V) ~ 1 - V (p,d), 0 <--> X end - save_reaction_network("test_serialisation_annotated.jl", rn) - save_reaction_network("test_serialisation.jl", rn; annotate = false) + save_reaction_network("test_serialisation_annotated.jl", rn; safety_check = false) + save_reaction_network("test_serialisation.jl", rn; annotate = false, safety_check = false) # Checks equivalence. file_string_annotated = read("test_serialisation_annotated.jl", String) @@ -39,17 +44,303 @@ end # Tests for hierarchical system created programmatically. # Checks that the species, variables, and parameters have their non-default types, default values, # and metadata recorded correctly (these are not considered for system equality is tested). +# Checks that various types (processed by the `x_2_string` function) are serialised properly. +# Checks that `ReactionSystem` and `Reaction` metadata fields are recorded properly. let - + # Prepares various stuff to add as metadata. + bool_md = false + int_md = 3 + float_md = 1.2 + rat_md = 4//5 + sym_md = :sym + c_md = 'c' + str_md = "A string" + nothing_md = nothing + @parameters s r + symb_md = s + expr_md = 2s + r^3 + pair_md = rat_md => symb_md + tup_md = (float_md, str_md, expr_md) + vec_md = [float_md, sym_md, tup_md] + dict_md = Dict([c_md => str_md, symb_md => vec_md]) + mat_md = [rat_md sym_md; symb_md tup_md] + + # Creates parameters, variables, and species (with various metadata and default values). + @parameters begin + a, [input=bool_md] + b, [misc=int_md] + c = float_md, [misc=rat_md] + d1, [misc=c_md] + d2, [description=str_md] + e1, [misc=nothing_md] + e2, [misc=symb_md] + end + @variables begin + A(t) = float_md + B(t), [misc=expr_md] + C(t), [misc=pair_md] + D1(t), [misc=tup_md] + D2(t), [misc=vec_md] + E1(t), [misc=dict_md] + E2(t), [misc=mat_md] + end + @species begin + X(t), [input=bool_md] + Y(t), [misc=int_md] + Z(t), [misc=float_md] + V1(t), [description=str_md] + V2(t), [misc=dict_md] + W1(t), [misc=mat_md] + W2(t) = float_md + end + + # Creates the hierarchical model. Adds metadata to both reactions and the systems. + rxs1 = [ + Reaction(a + A, [X], [], metadata = [:misc => bool_md]) + Reaction(b + B, [Y], [], metadata = [:misc => int_md]) + Reaction(c + C, [Z], [], metadata = [:misc => sym_md]) + Reaction(d1 + D1, [V1], [], metadata = [:misc => str_md]) + Reaction(e1 + E1, [W1], [], metadata = [:misc => nothing_md]) + ] + rxs2 = [ + Reaction(a + A, [X], [], metadata = [:misc => expr_md]) + Reaction(b + B, [Y], [], metadata = [:misc => tup_md]) + Reaction(c + C, [Z], [], metadata = [:misc => vec_md]) + Reaction(d2 + D2, [V2], [], metadata = [:misc => dict_md]) + Reaction(e2 + E2, [W2], [], metadata = [:misc => mat_md]) + ] + @named rs2 = ReactionSystem(rxs2, t; metadata = dict_md) + @named rs1 = ReactionSystem(rxs1, t; systems = [rs2], metadata = mat_md) + rs = complete(rs1) + + # Loads the model and checks that it is correct. Removes the saved file + save_reaction_network("serialised_rs.jl", rs; safety_check = false) + rs_loaded = include("serialised_rs.jl") + @test rs == rs_loaded + rm("serialised_rs.jl") + + # Checks that parameters/species/variables metadata fields are correct. + @test isequal(getinput(rs_loaded.a), bool_md) + @test isequal(getmisc(rs_loaded.b), int_md) + @test isequal(getdefault(rs_loaded.c), float_md) + @test isequal(getmisc(rs_loaded.c), rat_md) + @test isequal(getmisc(rs_loaded.d1), c_md) + @test isequal(getdescription(rs_loaded.rs2.d2), str_md) + @test isequal(getmisc(rs_loaded.e1), nothing_md) + @test isequal(getmisc(rs_loaded.rs2.e2), symb_md) + + @test isequal(getdefault(rs.A), float_md) + @test isequal(getmisc(rs_loaded.B), expr_md) + @test isequal(getmisc(rs_loaded.C), pair_md) + @test isequal(getmisc(rs_loaded.D1), tup_md) + @test isequal(getmisc(rs_loaded.rs2.D2), vec_md) + @test isequal(getmisc(rs_loaded.E1), dict_md) + @test isequal(getmisc(rs_loaded.rs2.E2), mat_md) + + @test isequal(getinput(rs_loaded.X), bool_md) + @test isequal(getmisc(rs_loaded.Y), int_md) + @test isequal(getmisc(rs_loaded.Z), float_md) + @test isequal(getdescription(rs_loaded.V1), str_md) + @test isequal(getmisc(rs_loaded.rs2.V2), dict_md) + @test isequal(getmisc(rs_loaded.W1), mat_md) + @test isequal(getdefault(rs_loaded.rs2.W2), float_md) + + # Checks that `Reaction` metadata fields are correct. + @test isequal(getmetadata(get_rxs(rs_loaded)[1], :misc), bool_md) + @test isequal(getmetadata(get_rxs(rs_loaded)[2], :misc), int_md) + @test isequal(getmetadata(get_rxs(rs_loaded)[3], :misc), sym_md) + @test isequal(getmetadata(get_rxs(rs_loaded)[4], :misc), str_md) + @test isequal(getmetadata(get_rxs(rs_loaded)[5], :misc), nothing_md) + @test isequal(getmetadata(get_rxs(rs_loaded.rs2)[1], :misc), expr_md) + @test isequal(getmetadata(get_rxs(rs_loaded.rs2)[2], :misc), tup_md) + @test isequal(getmetadata(get_rxs(rs_loaded.rs2)[3], :misc), vec_md) + @test isequal(getmetadata(get_rxs(rs_loaded.rs2)[4], :misc), dict_md) + @test isequal(getmetadata(get_rxs(rs_loaded.rs2)[5], :misc), mat_md) + + # Checks that `ReactionSystem` metadata fields are correct. + @test isequal(get_metadata(rs_loaded), mat_md) + @test isequal(get_metadata(rs_loaded.rs2), dict_md) end -# Tests for complicated hierarchical system. Tests with non-default independent variable, -# spatial independent variables, variables, (differential and algebraic) equations, observables -# (continuous and discrete) events, and with various species/variables/parameter metadata. +# Checks systems where parameters/species/variables have complicated interdependency are correctly +# serialised. +# Checks for system with non-default independent variable. +let + # Prepares parameters/variables/species with complicated dependencies. + @variables τ + @parameters begin + b = 3.0 + c + f + end + @variables begin + A(τ) = c + B(τ) = c + A + f + C(τ) = 2.0 + D(τ) = C + G(τ) + end + @species begin + Y(τ) = f + Z(τ) + U(τ) = G + Z + V(τ) + end + @parameters begin + a = G + D + e = U + end + @variables begin + E(τ) = G + Z + F(τ) = f + G + end + @species begin + X(τ) = f + a + end + @parameters d = X + @species W(τ) = d + e + + # Creates model and checks it against serialised version. + @named rs = ReactionSystem([], τ, [X, Y, Z, U, V, W, A, B, C, D, E, F, G], [a, b, c, d, e, f]) + save_reaction_network("serialised_rs.jl", rs; safety_check = false) + @test rs == include("serialised_rs.jl") + rm("serialised_rs.jl") +end + + +# Tests for multi-layered hierarchical system. Tests with spatial independent variables, +# variables, (differential and algebraic) equations, observables (continuous and discrete) events, +# and with various species/variables/parameter/reaction/system metadata. +# Tests for complete and incomplete system. let + # Prepares spatial independent variables (technically not used and only supplied to systems). + sivs = @variables x y z [description="A spatial independent variable."] + # Prepares parameters, species, and variables. + @parameters p d k1_1 k2_1 k1_2 k2_2 k1_3 k2_3 k1_4 k2_4 a b_1 b_2 b_3 b_4 η + @parameters begin + t_1 = 2.0 + t_2::Float64 + t_3, [description="A parameter."] + t_4::Float32 = p, [description="A parameter."] + end + @species X(t) X2_1(t) X2_2(t) X2_3(t) X2_4(t)=p [description="A species."] + @variables A(t)=p [description="A variable."] B_1(t) B_2(t) B_3(t) B_4(t) O_1(t) O_2(t) O_3(t) O_4(t) + + # Prepares all equations. + eqs_1 = [ + Reaction(p, [], [X]; metadata = [:description => "A reaction"]), + Reaction(d, [X], []; metadata = [:noise_scaling => η]), + Reaction(k1_1, [X], [X2_1], [2], [1]), + Reaction(k2_1, [X2_1], [X], [1], [2]), + D(A) ~ a - A, + A + 2B_1^3 ~ b_1 * X + ] + eqs_2 = [ + Reaction(p, [], [X]; metadata = [:description => "A reaction"]), + Reaction(d, [X], []; metadata = [:noise_scaling => η]), + Reaction(k1_2, [X], [X2_2], [2], [1]), + Reaction(k2_2, [X2_2], [X], [1], [2]), + D(A) ~ a - A, + A + 2B_2^3 ~ b_2 * X + ] + eqs_3 = [ + Reaction(p, [], [X]; metadata = [:description => "A reaction"]), + Reaction(d, [X], []; metadata = [:noise_scaling => η]), + Reaction(k1_3, [X], [X2_3], [2], [1]), + Reaction(k2_3, [X2_3], [X], [1], [2]), + D(A) ~ a - A, + A + 2B_3^3 ~ b_3 * X + ] + eqs_4 = [ + Reaction(p, [], [X]; metadata = [:description => "A reaction"]), + Reaction(d, [X], []; metadata = [:noise_scaling => η]), + Reaction(k1_4, [X], [X2_4], [2], [1]), + Reaction(k2_4, [X2_4], [X], [1], [2]), + D(A) ~ a - A, + A + 2B_4^3 ~ b_4 * X + ] + + # Prepares all observables. + observed_1 = [O_1 ~ X + 2*X2_1] + observed_2 = [O_2 ~ X + 2*X2_2] + observed_3 = [O_3 ~ X + 2*X2_3] + observed_4 = [O_4 ~ X + 2*X2_4] + + # Prepares all events. + continuous_events_1 = [(A ~ t_1) => [A ~ A + 2.0, X ~ X/2]] + continuous_events_2 = [(A ~ t_2) => [A ~ A + 2.0, X ~ X/2]] + continuous_events_3 = [(A ~ t_3) => [A ~ A + 2.0, X ~ X/2]] + continuous_events_4 = [(A ~ t_4) => [A ~ A + 2.0, X ~ X/2]] + discrete_events_1 = [ + 10.0 => [X2_1 ~ X2_1 + 1.0] + [5.0, 10.0] => [b_1 ~ 2 * b_1] + (X > 5.0) => [X2_1 ~ X2_1 + 1.0, X ~ X - 1] + ] + discrete_events_2 = [ + 10.0 => [X2_2 ~ X2_2 + 1.0] + [5.0, 10.0] => [b_2 ~ 2 * b_2] + (X > 5.0) => [X2_2 ~ X2_2 + 1.0, X ~ X - 1] + ] + discrete_events_3 = [ + 10.0 => [X2_3 ~ X2_3 + 1.0] + [5.0, 10.0] => [b_3 ~ 2 * b_3] + (X > 5.0) => [X2_3 ~ X2_3 + 1.0, X ~ X - 1] + ] + discrete_events_4 = [ + 10.0 => [X2_4 ~ X2_4 + 1.0] + [5.0, 10.0] => [b_4 ~ 2 * b_4] + (X > 5.0) => [X2_4 ~ X2_4 + 1.0, X ~ X - 1] + ] + + # Creates the systems. + @named rs_4 = ReactionSystem(eqs_4, t; observed = observed_4, continuous_events = continuous_events_4, + discrete_events = discrete_events_4, spatial_ivs = sivs, + metadata = "System 4", systems = []) + @named rs_2 = ReactionSystem(eqs_2, t; observed = observed_2, continuous_events = continuous_events_2, + discrete_events = discrete_events_2, spatial_ivs = sivs, + metadata = "System 2", systems = []) + @named rs_3 = ReactionSystem(eqs_3, t; observed = observed_3, continuous_events = continuous_events_3, + discrete_events = discrete_events_3, spatial_ivs = sivs, + metadata = "System 3", systems = [rs_4]) + @named rs_1 = ReactionSystem(eqs_1, t; observed = observed_1, continuous_events = continuous_events_1, + discrete_events = discrete_events_1, spatial_ivs = sivs, + metadata = "System 1", systems = [rs_2, rs_3]) + rs = complete(rs_1) + + # Checks that the correct system is saved (both complete and incomplete ones). + save_reaction_network("serialised_rs_incomplete.jl", rs_1; safety_check = false) + @test isequal(rs_1, include("serialised_rs_incomplete.jl")) + save_reaction_network("serialised_rs_complete.jl", rs; safety_check = false) + @test isequal(rs, include("serialised_rs_complete.jl")) + rm("serialised_rs_incomplete.jl") + rm("serialised_rs_complete.jl") end +# Tests for (slightly more) complicate system created via the DSL. +# Tests for cases where the number of input is untested (i.e. multiple observables and continuous +# events, but single equations and discrete events). +let + # Declares the model. + rs = @reaction_network begin + @equations D(V) ~ 1 - V + @observables begin + X2 ~ 2*X + X3 ~ 3*X + end + @continuous_events begin + X2 < 5.0 => [X ~ X + 1.0] + X3 > 20.0 => [X ~ X - 1.0] + end + @discrete_events [5.0 => d ~ d/2] + d, X --> 0 + end + + # Checks that serialisation works. + save_reaction_network("serialised_rs", rs; safety_check = false) + @test isequal(rs, include("serialised_rs.jl")) + rm("serialised_rs.jl") +end ### Other Tests ### @@ -71,6 +362,7 @@ let end # Checks that completeness is recorded correctly. +# Checks without turning off the `safety_check` option. let # Checks for complete system. rs_complete = @reaction_network begin From 68b5d72e15204aebfe8ab5d9d2eb12f1b4b22c8c Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 16 Apr 2024 14:14:29 -0400 Subject: [PATCH 115/446] redo how hirearchial systems are handled. --- .../serialisation_support.jl | 18 ++++- .../serialise_fields.jl | 67 ++++++++++--------- .../serialise_reactionsystem.jl | 44 ++++++------ 3 files changed, 74 insertions(+), 55 deletions(-) diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 2e41e24f46..17a9fe35c8 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -25,10 +25,24 @@ end ### Field Serialisation Support Functions ### # Function which handles the addition of a single component to the file string. -function push_field(file_text::String, rn::ReactionSystem, annotate::Bool, comp_funcs::Tuple) +function push_field(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool, comp_funcs::Tuple) has_component, get_comp_string, get_comp_annotation = comp_funcs has_component(rn) || (return (file_text, false)) - write_string = "\n" * get_comp_string(rn) + + # Prepares the text creating the field. For non-top level systems, adds `local `. Observables + # must be handled differently (as the declaration is not at the beginning of the code for these). + # The independent variables is not declared as a variable, and also should not have a `1ocal `. + write_string = get_comp_string(rn) + if !(top_level || comp_funcs == IV_FS) + if comp_funcs == OBSERVED_FS + write_string = replace(write_string, "\nobserved = [" => "\nlocal observed = [") + else + @string_prepend! "local " write_string + end + end + @string_prepend! "\n" write_string + + # Adds (potential) annotation. Returns the expanded file text, and a Bool that this field was added. annotate && (@string_prepend! "\n\n# " get_comp_annotation(rn) write_string) return (file_text * write_string, true) end diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index 3c52265979..59c92eac45 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -45,7 +45,7 @@ SIVS_FS = (has_sivs, get_sivs_string, get_sivs_annotation) # Function which handles the addition of species, variable, and parameter declarations to the file # text. These must be handled as a unity in case there are default value dependencies between these. -function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool) +function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) # Fetches the systems parameters, species, and variables. Computes the `has_` `Bool`s. ps_all = get_ps(rn) sps_all = get_species(rn) @@ -60,17 +60,18 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool) var_deps = any(depends_on(var, vars_all) for var in vars_all) # Makes the initial declaration. + us_n_ps_string = "" if !p_deps && has_ps - annotate && (@string_append! file_text "\n\n# " get_parameters_annotation(rn)) - @string_append! file_text "\nps = " get_parameters_string(ps_all) + annotate && (@string_append! us_n_ps_string "\n\n# " get_parameters_annotation(rn)) + @string_append! us_n_ps_string "\nps = " get_parameters_string(ps_all) end if !sp_deps && has_sps - annotate && (@string_append! file_text "\n\n# " get_species_annotation(rn)) - @string_append! file_text "\nsps = " get_species_string(sps_all) + annotate && (@string_append! us_n_ps_string "\n\n# " get_species_annotation(rn)) + @string_append! us_n_ps_string "\nsps = " get_species_string(sps_all) end if !var_deps && has_vars - annotate && (@string_append! file_text "\n\n# " get_variables_annotation(rn)) - @string_append! file_text "\nvars = " get_variables_string(vars_all) + annotate && (@string_append! us_n_ps_string "\n\n# " get_variables_annotation(rn)) + @string_append! us_n_ps_string "\nvars = " get_variables_string(vars_all) end # If any set have dependencies, handle these. @@ -82,12 +83,12 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool) if p_deps || sp_deps || var_deps # Builds an annotation mentioning specially handled stuff. if annotate - @string_append! file_text "\n\n# Some " - p_deps && (@string_append! file_text "parameters, ") - sp_deps && (@string_append! file_text "species, ") - var_deps && (@string_append! file_text "variables, ") - file_text = file_text[1:end-2] - @string_append! file_text " depends on the declaration of other parameters, species, and/or variables.\n# These are specially handled here.\n" + @string_append! us_n_ps_string "\n\n# Some " + p_deps && (@string_append! us_n_ps_string "parameters, ") + sp_deps && (@string_append! us_n_ps_string "species, ") + var_deps && (@string_append! us_n_ps_string "variables, ") + us_n_ps_string = us_n_ps_string[1:end-2] + @string_append! us_n_ps_string " depends on the declaration of other parameters, species, and/or variables.\n# These are specially handled here.\n" end # Pre-declares the sets with written/remaining parameters/species/variables. @@ -107,20 +108,27 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool) writable_vars = dependency_split!(remaining_vars, [remaining_ps; remaining_sps; remaining_vars]) # Writes those that can be written. - isempty(writable_ps) || @string_append! file_text get_parameters_string(writable_ps) "\n" - isempty(writable_sps) || @string_append! file_text get_species_string(writable_sps) "\n" - isempty(writable_vars) || @string_append! file_text get_variables_string(writable_vars) "\n" + isempty(writable_ps) || @string_append! us_n_ps_string get_parameters_string(writable_ps) "\n" + isempty(writable_sps) || @string_append! us_n_ps_string get_species_string(writable_sps) "\n" + isempty(writable_vars) || @string_append! us_n_ps_string get_variables_string(writable_vars) "\n" end # For parameters, species, and/or variables with dependencies, creates final vectors. - p_deps && (@string_append! file_text "ps = " syms_2_strings(ps_all) "\n") - sp_deps && (@string_append! file_text "sps = " syms_2_strings(sps_all) "\n") - var_deps && (@string_append! file_text "vars = " syms_2_strings(vars_all) "\n") - file_text = file_text[1:end-1] + p_deps && (@string_append! us_n_ps_string "ps = " syms_2_strings(ps_all) "\n") + sp_deps && (@string_append! us_n_ps_string "sps = " syms_2_strings(sps_all) "\n") + var_deps && (@string_append! us_n_ps_string "vars = " syms_2_strings(vars_all) "\n") + us_n_ps_string = us_n_ps_string[1:end-1] end - # Returns the finalised output. - return file_text, has_ps, has_sps, has_vars + # If this is not a top-level system, `local ` must be added to all declarations. + if !top_level + us_n_ps_string = replace(us_n_ps_string, "\nps = " => "\nlocal ps = ") + us_n_ps_string = replace(us_n_ps_string, "\nsps = " => "\nlocal sps = ") + us_n_ps_string = replace(us_n_ps_string, "\nvars = " => "\nlocal vars = ") + end + + # Merges the file text with `us_n_ps_string` and return the final outputs. + return file_text * us_n_ps_string, has_ps, has_sps, has_vars end @@ -464,7 +472,7 @@ DISCRETE_EVENTS_FS = (has_discrete_events, get_discrete_events_string, get_discr # Specific `push_field` function, which is used for the system field (where the annotation option # must be passed to the `get_component_string` function). Since non-ReactionSystem systems cannot be # written to file, this functions throws an error if any such systems are encountered. -function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Bool) +function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) # Checks whther there are any subsystems, and if these are ReactionSystems. has_systems(rn) || (return (file_text, false)) if any(!(system isa ReactionSystem) for system in MT.get_systems(rn)) @@ -472,7 +480,9 @@ function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Boo end # Adds the system declaration string to the file string. - write_string = "\n" * get_systems_string(rn, annotate) + write_string = "\n" + top_level || (@string_append! write_string "local ") + @string_append! write_string get_systems_string(rn, annotate) annotate && (@string_prepend! "\n\n# " get_systems_annotation(rn) write_string) return (file_text * write_string, true) end @@ -483,22 +493,19 @@ function has_systems(rn::ReactionSystem) end # Extract a string which declares the system's systems. -# The systems variable (`systems_X`) is the only variable where we append an identifier. This is -# to avoid it getting overwritten by other systems variables (not required for other variables -# due to the order they appear in). function get_systems_string(rn::ReactionSystem, annotate::Bool) # Initiates the `systems` string. It is pre-declared vector, into which the systems are added. - systems_string = "systems_$(getname(rn)) = Vector(undef, $(length(MT.get_systems(rn))))" + systems_string = "systems = Vector(undef, $(length(MT.get_systems(rn))))" # Loops through all systems, adding their declaration to the system string. for (idx, system) in enumerate(MT.get_systems(rn)) annotate && (@string_append! systems_string "\n\n# Declares subsystem: $(getname(system))") # Manipulates the subsystem declaration to make it nicer. - subsystem_string = get_full_system_string(system, annotate) + subsystem_string = get_full_system_string(system, annotate, false) subsystem_string = replace(subsystem_string, "\n" => "\n\t") subsystem_string = "let\n" * subsystem_string[7:end-6] * "end" - @string_append! systems_string "\nsystems_$(getname(rn))[$idx] = " subsystem_string + @string_append! systems_string "\nsystems[$idx] = " subsystem_string end return systems_string diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index d346c53fc5..1fdf48e866 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -34,7 +34,7 @@ Notes: """ function save_reaction_network(filename::String, rn::ReactionSystem; annotate = true, safety_check = true) open(filename, "w") do file - write(file, get_full_system_string(rn, annotate)) + write(file, get_full_system_string(rn, annotate, true)) end if safety_check && !isequal(rn, include(filename)) rm(filename) @@ -45,34 +45,31 @@ end # Gets the full string which corresponds to the declaration of a system. Might be called recursively # for systems with subsystems. -function get_full_system_string(rn::ReactionSystem, annotate::Bool) +function get_full_system_string(rn::ReactionSystem, annotate::Bool, top_level::Bool) # Initiates the file string. file_text = "" - - # Sub-systems must (unfortunately) be declared first (as the variables written in their internal - # let blocks otherwise will overwrite those of the main system). - # Systems are uses custom `push_field` function as these requires the annotation `Bool` - # to be passed to the function that creates the next sub-system declarations. - file_text, has_systems = push_systems_field(file_text, rn, annotate) - + # Goes through each type of system component, potentially adding it to the string. # Species, variables, and parameters must be handled differently in case there is default-values # dependencies between them. - file_text, _ = push_field(file_text, rn, annotate, IV_FS) - file_text, has_sivs = push_field(file_text, rn, annotate, SIVS_FS) - file_text, has_parameters, has_species, has_variables = handle_us_n_ps(file_text, rn, annotate) - file_text, has_reactions = push_field(file_text, rn, annotate, REACTIONS_FS) - file_text, has_equations = push_field(file_text, rn, annotate, EQUATIONS_FS) - file_text, has_observed = push_field(file_text, rn, annotate, OBSERVED_FS) - file_text, has_continuous_events = push_field(file_text, rn, annotate, CONTINUOUS_EVENTS_FS) - file_text, has_discrete_events = push_field(file_text, rn, annotate, DISCRETE_EVENTS_FS) - file_text, has_connection_type = push_field(file_text, rn, annotate, CONNECTION_TYPE_FS) + # Systems uses custom `push_field` function as these requires the annotation `Bool`to be passed + # to the function that creates the next sub-system declarations. + file_text, _ = push_field(file_text, rn, annotate, top_level, IV_FS) + file_text, has_sivs = push_field(file_text, rn, annotate, top_level, SIVS_FS) + file_text, has_parameters, has_species, has_variables = handle_us_n_ps(file_text, rn, annotate, top_level) + file_text, has_reactions = push_field(file_text, rn, annotate, top_level, REACTIONS_FS) + file_text, has_equations = push_field(file_text, rn, annotate, top_level, EQUATIONS_FS) + file_text, has_observed = push_field(file_text, rn, annotate, top_level, OBSERVED_FS) + file_text, has_continuous_events = push_field(file_text, rn, annotate, top_level, CONTINUOUS_EVENTS_FS) + file_text, has_discrete_events = push_field(file_text, rn, annotate, top_level, DISCRETE_EVENTS_FS) + file_text, has_systems = push_systems_field(file_text, rn, annotate, top_level) + file_text, has_connection_type = push_field(file_text, rn, annotate, top_level, CONNECTION_TYPE_FS) # Finalises the system. Creates the final `ReactionSystem` call. # Enclose everything ing a `let ... end` block. - rs_creation_code = make_reaction_system_call(rn, annotate, has_sivs, has_species, has_variables, - has_parameters, has_reactions, has_equations, - has_observed, has_continuous_events, + rs_creation_code = make_reaction_system_call(rn, annotate, top_level, has_sivs, has_species, + has_variables, has_parameters, has_reactions, + has_equations, has_observed, has_continuous_events, has_discrete_events, has_systems, has_connection_type) annotate || (@string_prepend! "\n" file_text) @string_prepend! "let" file_text @@ -83,7 +80,7 @@ end # Creates a ReactionSystem call for creating the model. Adds all the correct inputs to it. The input # `has_` `Bool`s described which inputs are used. If model is `complete`, this is handled here. -function make_reaction_system_call(rs::ReactionSystem, annotate, has_sivs, has_species, +function make_reaction_system_call(rs::ReactionSystem, annotate, top_level, has_sivs, has_species, has_variables, has_parameters, has_reactions, has_equations, has_observed, has_continuous_events, has_discrete_events, has_systems, has_connection_type) @@ -136,7 +133,7 @@ function make_reaction_system_call(rs::ReactionSystem, annotate, has_sivs, has_s has_observed && (@string_append! reaction_system_string ", observed") has_continuous_events && (@string_append! reaction_system_string ", continuous_events") has_discrete_events && (@string_append! reaction_system_string ", discrete_events") - has_systems && (@string_append! reaction_system_string ", systems = systems_$(getname(rs))") + has_systems && (@string_append! reaction_system_string ", systems") has_connection_type && (@string_append! reaction_system_string ", connection_type") # Potentially appends a combinatoric_ratelaws statement. @@ -149,6 +146,7 @@ function make_reaction_system_call(rs::ReactionSystem, annotate, has_sivs, has_s @string_append! reaction_system_string ")" if ModelingToolkit.iscomplete(rs) @string_prepend! "rs = " reaction_system_string + top_level || (@string_prepend! "local " reaction_system_string) @string_append! reaction_system_string "\ncomplete(rs)" end if annotate From 1be73da6a40ff05296ad71137f6e12da8214336e Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 21 May 2024 14:02:55 -0400 Subject: [PATCH 116/446] rebase fix --- src/reactionsystem.jl | 8 +++----- src/reactionsystem_serialisation/serialisation_support.jl | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index f2ac871bc9..3be6b6c06c 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -1320,7 +1320,7 @@ Notes: - The default value of `combinatoric_ratelaws` will be the logical or of all `ReactionSystem`s. """ -function MT.flatten(rs::ReactionSystem; name = nameof(rs), complete = false) +function MT.flatten(rs::ReactionSystem; name = nameof(rs)) isempty(get_systems(rs)) && return rs # right now only NonlinearSystems and ODESystems can be handled as subsystems @@ -1338,8 +1338,7 @@ function MT.flatten(rs::ReactionSystem; name = nameof(rs), complete = false) balanced_bc_check = false, spatial_ivs = get_sivs(rs), continuous_events = MT.continuous_events(rs), - discrete_events = MT.discrete_events(rs), - complete = complete) + discrete_events = MT.discrete_events(rs)) end """ @@ -1396,8 +1395,7 @@ function ModelingToolkit.extend(sys::MT.AbstractSystem, rs::ReactionSystem; balanced_bc_check = false, spatial_ivs = sivs, continuous_events, - discrete_events, - complete = false) + discrete_events) end ### Units Handling ### diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 17a9fe35c8..e466bc7835 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -99,7 +99,7 @@ function sym_2_declaration_string(sym; multiline_format = false) # Assumes that the type is on the form `SymbolicUtils.BasicSymbolic{X}`. Contain error checks # to ensure that this is the case. if !(sym isa SymbolicUtils.BasicSymbolic{Real}) - sym_type = String(Symbol(typeof(Symbolics.unwrap(k2)))) + sym_type = String(Symbol(typeof(Symbolics.unwrap(sym)))) if (sym_type[1:28] != "SymbolicUtils.BasicSymbolic{") || (sym_type[end] != '}') error("Encountered symbolic of unexpected type: $sym_type.") end From 51d1dc7cd9628621c68970c4e61098b2420917de Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 21 May 2024 20:21:44 -0400 Subject: [PATCH 117/446] rebase fix again --- test/dsl/dsl_options.jl | 778 +++++++++++++++++++++++----------------- 1 file changed, 453 insertions(+), 325 deletions(-) diff --git a/test/dsl/dsl_options.jl b/test/dsl/dsl_options.jl index bdd71e2cfc..93ce90b541 100644 --- a/test/dsl/dsl_options.jl +++ b/test/dsl/dsl_options.jl @@ -1,4 +1,4 @@ -### Prepares Tests ### +#! format: off ### Prepares Tests ### @@ -16,214 +16,335 @@ t = default_t() ### Tests `@parameters`, `@species`, and `@variables` Options ### -# Sets the default `t` to use. -t = default_t() - -# Fetch test networks and functions. -include("../test_networks.jl") -include("../test_functions.jl") +# Test creating networks with/without options. +let + @reaction_network begin (k1, k2), A <--> B end + @reaction_network begin + @parameters k1 k2 + (k1, k2), A <--> B + end + @reaction_network begin + @parameters k1 k2 + @species A(t) B(t) + (k1, k2), A <--> B + end + @reaction_network begin + @species A(t) B(t) + (k1, k2), A <--> B + end -### Declares Testing Functions ### + @reaction_network begin + @parameters begin + k1 + k2 + end + (k1, k2), A <--> B + end + @reaction_network begin + @species begin + A(t) + B(t) + end + (k1, k2), A <--> B + end + @reaction_network begin + @parameters begin + k1 + k2 + end + @species begin + A(t) + B(t) + end + (k1, k2), A <--> B + end -function unpacksys(sys) - get_eqs(sys), get_iv(sys), get_unknowns(sys), get_ps(sys), nameof(sys), get_systems(sys) + n1 = @reaction_network rnname begin (k1, k2), A <--> B end + n2 = @reaction_network rnname begin + @parameters k1 k2 + (k1, k2), A <--> B + end + n3 = @reaction_network rnname begin + @species A(t) B(t) + (k1, k2), A <--> B + end + n4 = @reaction_network rnname begin + @parameters k1 k2 + @species A(t) B(t) + (k1, k2), A <--> B + end + n5 = @reaction_network rnname begin + (k1, k2), A <--> B + @parameters k1 k2 + end + n6 = @reaction_network rnname begin + (k1, k2), A <--> B + @species A(t) B(t) + end + n7 = @reaction_network rnname begin + (k1, k2), A <--> B + @parameters k1 k2 + @species A(t) B(t) + end + n8 = @reaction_network rnname begin + @parameters begin + k1 + k2 + end + (k1, k2), A <--> B + end + n9 = @reaction_network rnname begin + @species begin + A(t) + B(t) + end + (k1, k2), A <--> B + end + n10 = @reaction_network rnname begin + @parameters begin + k1 + k2 + end + @species begin + A(t) + B(t) + end + (k1, k2), A <--> B + end + @test all(==(n1), (n2, n3, n4, n5, n6, n7, n8, n9, n10)) end -opname(x) = istree(x) ? nameof(operation(x)) : nameof(x) -alleq(xs, ys) = all(isequal(x, y) for (x, y) in zip(xs, ys)) +# Tests that when either @species or @parameters is given, the other is inferred properly. +let + rn1 = @reaction_network begin + k*X, A + B --> 0 + end + @test issetequal(species(rn1), @species A(t) B(t)) + @test issetequal(parameters(rn1), @parameters k X) -# Gets all the reactants in a set of equations. -function all_reactants(eqs) - all_reactants = [] - for eq in eqs - append!(all_reactants, opname.(eq.substrates)) - append!(all_reactants, opname.(eq.products)) + rn2 = @reaction_network begin + @species A(t) B(t) X(t) + k*X, A + B --> 0 end - return Set{Symbol}(unique(all_reactants)) -end + @test issetequal(species(rn2), @species A(t) B(t) X(t)) + @test issetequal(parameters(rn2), @parameters k) -# Gets all parameters (where every reaction rate is constant) -function all_parameters(eqs) - return Set(unique(map(eq -> opname(eq.rate), eqs))) -end + rn3 = @reaction_network begin + @parameters k + k*X, A + B --> 0 + end + @test issetequal(species(rn3), @species A(t) B(t)) + @test issetequal(parameters(rn3), @parameters k X) -# Perform basic tests. -function basic_test(rn, N, unknowns_syms, p_syms) - eqs, iv, unknowns, ps, name, systems = unpacksys(rn) - @test length(eqs) == N - @test opname(iv) == :t - @test length(unknowns) == length(unknowns_syms) - @test issetequal(map(opname, unknowns), unknowns_syms) - @test all_reactants(eqs) == Set(unknowns_syms) - @test length(ps) == length(p_syms) - @test issetequal(map(opname, ps), p_syms) -end + rn4 = @reaction_network begin + @species A(t) B(t) X(t) + @parameters k + k*X, A + B --> 0 + end + @test issetequal(species(rn4), @species A(t) B(t) X(t)) + @test issetequal(parameters(rn4), @parameters k) -### Basic Tests ### + rn5 = @reaction_network begin + @parameters k B [isconstantspecies=true] + k*X, A + B --> 0 + end + @test issetequal(species(rn5), @species A(t)) + @test issetequal(parameters(rn5), @parameters k B X) +end -# Test basic properties of networks. +# Test inferring with stoichiometry symbols and interpolation. let - basic_test(reaction_networks_standard[1], 10, [:X1, :X2, :X3], - [:p1, :p2, :p3, :k1, :k2, :k3, :k4, :d1, :d2, :d3]) - @test all_parameters(get_eqs(reaction_networks_standard[1])) == - Set([:p1, :p2, :p3, :k1, :k2, :k3, :k4, :d1, :d2, :d3]) - basic_test(reaction_networks_standard[2], 3, [:X1, :X2], [:v1, :K1, :v2, :K2, :d]) - basic_test(reaction_networks_standard[3], 10, [:X1, :X2, :X3, :X4], - [:v1, :K1, :v2, :K2, :k1, :k2, :k3, :k4, :d]) - basic_test(reaction_networks_standard[4], 8, [:X1, :X2, :X3, :X4], - [:v1, :K1, :v2, :K2, :v3, :K3, :v4, :K4, :d1, :d2, :d3, :d4]) - basic_test(reaction_networks_standard[5], 8, [:X1, :X2, :X3, :X4], - [:p, :k1, :k2, :k3, :k4, :k5, :k6, :d]) - @test all_parameters(get_eqs(reaction_networks_standard[5])) == - Set([:p, :k1, :k2, :k3, :k4, :k5, :k6, :d]) - basic_test(reaction_networks_hill[1], 4, [:X1, :X2], - [:v1, :v2, :K1, :K2, :n1, :n2, :d1, :d2]) - basic_test(reaction_networks_constraint[1], 6, [:X1, :X2, :X3], - [:k1, :k2, :k3, :k4, :k5, :k6]) - basic_test(reaction_networks_real[1], 4, [:X, :Y], [:A, :B]) - basic_test(reaction_networks_weird[1], 2, [:X], [:p, :d]) - basic_test(reaction_networks_weird[2], 4, [:X, :Y, :Z], [:k1, :k2, :k3, :k4]) + @parameters k g h gg X y [isconstantspecies = true] + t = Catalyst.DEFAULT_IV + @species A(t) B(t) BB(t) C(t) + + rni = @reaction_network inferred begin + $k*X, $y + g*A + h*($gg)*B + $BB * C --> k*C + end + @test issetequal(species(rni), [A, B, BB, C]) + @test issetequal(parameters(rni), [k, g, h, gg, X, y]) + + rnii = @reaction_network inferred begin + @species BB(t) + @parameters y [isconstantspecies = true] + k*X, y + g*A + h*($gg)*B + BB * C --> k*C + end + @test rnii == rni end -# Compares networks to networks created using different arrow types. +# Tests that when some species or parameters are left out, the others are set properly. let - networks_1 = [] - networks_2 = [] - - different_arrow_1 = @reaction_network begin - (p1, p2, p3), ∅ > (X1, X2, X3) - (k1, k2), X2 ↔ X1 + 2X3 - (k3, k4), X1 ⟷ X3 - (d1, d2, d3), (X1, X2, X3) → ∅ - end - push!(networks_1, reaction_networks_standard[1]) - push!(networks_2, different_arrow_1) - - different_arrow_2 = @reaction_network begin - mmr(X2, v1, K1), ∅ → X1 - mm(X1, v2, K2), ∅ ↣ X2 - d, X1 + X2 ↦ ∅ - end - push!(networks_1, reaction_networks_standard[2]) - push!(networks_2, different_arrow_2) - - different_arrow_3 = @reaction_network begin - mm(X2, v1, K1), ∅ ⇾ X1 - mm(X3, v2, K2), ∅ ⟶ X2 - (k1, k2), X1 ⇄ X3 - (k3, k4), X3 + X2 ⇆ X4 + X1 - d, (X1, X2, X3, X4) ⟼ ∅ - end - push!(networks_1, reaction_networks_standard[3]) - push!(networks_2, different_arrow_3) - - different_arrow_4 = @reaction_network begin - mmr(X4, v1, K1), ∅ ⥟ X1 - mmr(X1, v2, K2), ∅ ⥟ X2 - mmr(X2, v3, K3), ∅ ⇀ X3 - mmr(X3, v4, K4), ∅ ⇁ X4 - (d1, d2, d3, d4), (X1, X2, X3, X4) --> ∅ - end - push!(networks_1, reaction_networks_standard[4]) - push!(networks_2, different_arrow_4) - - # Yes the name is different, I wanted one with several single direction arrows. - different_arrow_8 = @reaction_network begin - p, 2X1 < ∅ - k1, X2 ← X1 - (k2, k3), X3 ⟻ X2 - d, ∅ ↼ X3 - end - push!(networks_1, reaction_networks_standard[8]) - push!(networks_2, different_arrow_8) - - for (rn_1, rn_2) in zip(networks_1, networks_2) - for factor in [1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3] - u0 = rnd_u0(rn_1, rng; factor) - p = rnd_ps(rn_1, rng; factor) - t = rand(rng) - - @test f_eval(rn_1, u0, p, t) ≈ f_eval(rn_2, u0, p, t) - @test jac_eval(rn_1, u0, p, t) ≈ jac_eval(rn_2, u0, p, t) - @test g_eval(rn_1, u0, p, t) ≈ g_eval(rn_2, u0, p, t) - end + rn6 = @reaction_network begin + @species A(t) + k*X, A + B --> 0 + end + @test issetequal(species(rn6), @species A(t) B(t)) + @test issetequal(parameters(rn6), @parameters k X) + + rn7 = @reaction_network begin + @species A(t) X(t) + k*X, A + B --> 0 + end + @test issetequal(species(rn7), @species A(t) X(t) B(t)) + @test issetequal(parameters(rn7), @parameters k) + + rn7 = @reaction_network begin + @parameters B [isconstantspecies=true] + k*X, A + B --> 0 + end + @test issetequal(species(rn7), @species A(t)) + @test issetequal(parameters(rn7), @parameters B k X) + + rn8 = @reaction_network begin + @parameters B [isconstantspecies=true] k + k*X, A + B --> 0 + end + @test issetequal(species(rn8), @species A(t)) + @test issetequal(parameters(rn8), @parameters B k X) + + rn9 = @reaction_network begin + @parameters k1 X1 + @species A1(t) B1(t) + k1*X1, A1 + B1 --> 0 + k2*X2, A2 + B2 --> 0 + end + @test issetequal(species(rn9), @species A1(t) B1(t) A2(t) B2(t)) + @test issetequal(parameters(rn9), @parameters k1 X1 k2 X2) + + rn10 = @reaction_network begin + @parameters k1 X2 B2 [isconstantspecies=true] + @species A1(t) X1(t) + k1*X1, A1 + B1 --> 0 + k2*X2, A2 + B2 --> 0 + end + @test issetequal(species(rn10), @species A1(t) X1(t) B1(t) A2(t)) + @test issetequal(parameters(rn10), @parameters k1 X2 B2 k2) + + rn11 = @reaction_network begin + @parameters k1 k2 + @species X1(t) + k1*X1, A1 + B1 --> 0 + k2*X2, A2 + B2 --> 0 end + @test issetequal(species(rn11), @species X1(t) A1(t) A2(t) B1(t) B2(t)) + @test issetequal(parameters(rn11), @parameters k1 k2 X2) end # Checks that some created networks are identical. let - # Declares network. - differently_written_5 = @reaction_network begin - q, ∅ → Y1 - (l1, l2), Y1 ⟷ Y2 - (l3, l4), Y2 ⟷ Y3 - (l5, l6), Y3 ⟷ Y4 - c, Y4 → ∅ - end - - # Computes initial conditions/parameter values. - u0_vals = rand(rng, length(species(differently_written_5))) - ps_vals = rand(rng, length(parameters(differently_written_5))) - u0_1 = [:X1 => u0_vals[1], :X2 => u0_vals[2], :X3 => u0_vals[3], :X4 => u0_vals[4]] - u0_2 = [:Y1 => u0_vals[1], :Y2 => u0_vals[2], :Y3 => u0_vals[3], :Y4 => u0_vals[4]] - ps_1 = [:p => ps_vals[1], :k1 => ps_vals[2], :k2 => ps_vals[3], :k3 => ps_vals[4], - :k4 => ps_vals[5], :k5 => ps_vals[6], :k6 => ps_vals[7], :d => ps_vals[8]] - ps_2 = [:q => ps_vals[1], :l1 => ps_vals[2], :l2 => ps_vals[3], :l3 => ps_vals[4], - :l4 => ps_vals[5], :l5 => ps_vals[6], :l6 => ps_vals[7], :c => ps_vals[8]] - t = rand(rng) - - # Checks equivalence. - rn_1 = reaction_networks_standard[5] - rn_2 = differently_written_5 - @test f_eval(rn_1, u0_1, ps_1, t) ≈ f_eval(rn_2, u0_2, ps_2, t) - @test jac_eval(rn_1, u0_1, ps_1, t) ≈ jac_eval(rn_2, u0_2, ps_2, t) - @test g_eval(rn_1, u0_1, ps_1, t) ≈ g_eval(rn_2, u0_2, ps_2, t) + rn12 = @reaction_network rnname begin (k1, k2), A <--> B end + rn13 = @reaction_network rnname begin + @parameters k1 k2 + (k1, k2), A <--> B + end + rn14 = @reaction_network rnname begin + @species A(t) B(t) + (k1, k2), A <--> B + end + rn15 = @reaction_network rnname begin + @parameters k1 k2 + @species A(t) B(t) + (k1, k2), A <--> B + end + @test all(==(rn12), (rn13, rn14, rn15)) end -# Compares networks to networks written in different ways. +# Checks that the rights things are put in vectors. let - networks_1 = [] - networks_2 = [] - - # Unfold reactions. - differently_written_6 = @reaction_network begin - p1, ∅ → X1 - p2, ∅ → X2 - k1, 2X1 → X3 - k2, X3 → 2X1 - k3, X2 + X3 → 4X4 - k4, 4X4 → X2 + X3 - k5, X4 + X1 → 2X3 - k6, 2X3 → X4 + X1 - d, X1 → ∅ - d, X2 → ∅ - d, X3 → ∅ - d, X4 → ∅ - end - push!(networks_1, reaction_networks_standard[6]) - push!(networks_2, differently_written_6) - - # Ignore mass action. - differently_written_7 = @reaction_network begin - @parameters p1 p2 p3 k1 k2 k3 v1 K1 d1 d2 d3 d4 d5 - (p1, p2, p3), ∅ ⇒ (X1, X2, X3) - (k1 * X1 * X2^2 / 2, k2 * X4), X1 + 2X2 ⟺ X4 - (mm(X3, v1, K1) * X4, k3 * X5), X4 ⇔ X5 - (d1 * X1, d2 * X2, d3 * X3, d4 * X4, d5 * X5), ∅ ⟽ (X1, X2, X3, X4, X5) - end - push!(networks_1, reaction_networks_standard[7]) - push!(networks_2, differently_written_7) - - # Ignore mass action new arrows. - differently_written_8 = @reaction_network begin - @parameters p1 p2 p3 k1 k2 k3 v1 K1 d1 d2 d3 d4 d5 - (p1, p2, p3), ∅ => (X1, X2, X3) - (k1 * X1 * X2^2 / 2, k2 * X4), X1 + 2X2 ⟺ X4 - (mm(X3, v1, K1) * X4, k3 * X5), X4 ⇔ X5 - (d1 * X1, d2 * X2, d3 * X3, d4 * X4, d5 * X5), ∅ <= (X1, X2, X3, X4, X5) - end - push!(networks_1, reaction_networks_standard[7]) - push!(networks_2, differently_written_8) + rn18 = @reaction_network rnname begin + @parameters p d1 d2 + @species A(t) B(t) + p, 0 --> A + 1, A --> B + (d1, d2), (A, B) --> 0 + end + rn19 = @reaction_network rnname begin + p, 0 --> A + 1, A --> B + (d1, d2), (A, B) --> 0 + end + @test rn18 == rn19 + + @parameters p d1 d2 + @species A(t) B(t) + @test isequal(parameters(rn18)[1], p) + @test isequal(parameters(rn18)[2], d1) + @test isequal(parameters(rn18)[3], d2) + @test isequal(species(rn18)[1], A) + @test isequal(species(rn18)[2], B) + + rn20 = @reaction_network rnname begin + @species X(t) + @parameters S + mm(X,v,K), 0 --> Y + (k1,k2), 2Y <--> Y2 + d*Y, S*(Y2+Y) --> 0 + end + rn21 = @reaction_network rnname begin + @species X(t) Y(t) Y2(t) + @parameters v K k1 k2 d S + mm(X,v,K), 0 --> Y + (k1,k2), 2Y <--> Y2 + d*Y, S*(Y2+Y) --> 0 + end + rn22 = @reaction_network rnname begin + @species X(t) Y2(t) + @parameters d k1 + mm(X,v,K), 0 --> Y + (k1,k2), 2Y <--> Y2 + d*Y, S*(Y2+Y) --> 0 + end + @test all(==(rn20), (rn21, rn22)) + @parameters v K k1 k2 d S + @species X(t) Y(t) Y2(t) + @test issetequal(parameters(rn22),[v K k1 k2 d S]) + @test issetequal(species(rn22), [X Y Y2]) +end + +# Tests that defaults work. +let + rn26 = @reaction_network rnname begin + @parameters p=1.0 d1 d2=5 + @species A(t) B(t)=4 + p, 0 --> A + 1, A --> B + (d1, d2), (A, B) --> 0 + end + + rn27 = @reaction_network rnname begin + @parameters p1=1.0 p2=2.0 k1=4.0 k2=5.0 v=8.0 K=9.0 n=3 d=10.0 + @species X(t)=4.0 Y(t)=3.0 X2Y(t)=2.0 Z(t)=1.0 + (p1,p2), 0 --> (X,Y) + (k1,k2), 2X + Y --> X2Y + hill(X2Y,v,K,n), 0 --> Z + d, (X,Y,X2Y,Z) --> 0 + end + u0_27 = [] + p_27 = [] + + rn28 = @reaction_network rnname begin + @parameters p1=1.0 p2 k1=4.0 k2 v=8.0 K n=3 d + @species X(t)=4.0 Y(t) X2Y(t) Z(t)=1.0 + (p1,p2), 0 --> (X,Y) + (k1,k2), 2X + Y --> X2Y + hill(X2Y,v,K,n), 0 --> Z + d, (X,Y,X2Y,Z) --> 0 + end + u0_28 = symmap_to_varmap(rn28, [:p2=>2.0, :k2=>5.0, :K=>9.0, :d=>10.0]) + p_28 = symmap_to_varmap(rn28, [:Y=>3.0, :X2Y=>2.0]) + defs28 = Dict(Iterators.flatten((u0_28, p_28))) + + rn29 = @reaction_network rnname begin + @parameters p1 p2 k1 k2 v K n d + @species X(t) Y(t) X2Y(t) Z(t) + (p1,p2), 0 --> (X,Y) + (k1,k2), 2X + Y --> X2Y + hill(X2Y,v,K,n), 0 --> Z + d, (X,Y,X2Y,Z) --> 0 + end + u0_29 = symmap_to_varmap(rn29, [:p1=>1.0, :p2=>2.0, :k1=>4.0, :k2=>5.0, :v=>8.0, :K=>9.0, :n=>3, :d=>10.0]) + p_29 = symmap_to_varmap(rn29, [:X=>4.0, :Y=>3.0, :X2Y=>2.0, :Z=>1.0]) + defs29 = Dict(Iterators.flatten((u0_29, p_29))) @test ModelingToolkit.defaults(rn27) == defs29 @test merge(ModelingToolkit.defaults(rn28), defs28) == ModelingToolkit.defaults(rn27) @@ -338,6 +459,9 @@ let X ~ Xi + Xa Y ~ Y1 + Y2 end + (p,d), 0 <--> Xi + (k1,k2), Xi <--> Xa + (k3,k4), Y1 <--> Y2 end @unpack X, Xi, Xa, Y, Y1, Y2, p, d, k1, k2, k3, k4 = rn @@ -372,24 +496,18 @@ let @test_broken false # plot(sol; idxs=[:X, :Y]).series_list[2].plotattributes[:y][end] ≈ 3.0 end -# Compares networks to networks written without parameters, +# Compares programmatic and DSL system with observables. let - networks_1 = [] - networks_2 = [] - parameter_sets = [] - - # Different parameter and variable names. - no_parameters_9 = @reaction_network begin - (1.5, 1, 2), ∅ ⟶ (X1, X2, X3) - (0.01, 2.3, 1001), (X1, X2, X3) ⟶ ∅ - (π, 42), X1 + X2 ⟷ X3 - (19.9, 999.99), X3 ⟷ X4 - (sqrt(3.7), exp(1.9)), X4 ⟷ X1 + X2 - end - push!(networks_1, reaction_networks_standard[9]) - push!(networks_2, no_parameters_9) - push!(parameter_sets, [:p1 => 1.5, :p2 => 1, :p3 => 2, :d1 => 0.01, :d2 => 2.3, :d3 => 1001, - :k1 => π, :k2 => 42, :k3 => 19.9, :k4 => 999.99, :k5 => sqrt(3.7), :k6 => exp(1.9)]) + # Model declarations. + rn_dsl = @reaction_network begin + @observables begin + X ~ x + 2x2y + Y ~ y + x2y + end + k, 0 --> (x, y) + (kB, kD), 2x + y <--> x2y + d, (x,y,x2y) --> 0 + end @variables X(t) Y(t) @species x(t), y(t), x2y(t) @@ -405,17 +523,21 @@ let @named rn_prog = ReactionSystem([r1, r2, r3, r4, r5, r6, r7], t, [x, y, x2y], [k, kB, kD, d]; observed = obs_eqs) rn_prog = complete(rn_prog) - - for (rn_1, rn_2, p_1) in zip(networks_1, networks_2, parameter_sets) - for factor in [1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3] - u0 = rnd_u0(rn_1, rng; factor) - t = rand(rng) - - @test f_eval(rn_1, u0, p_1, t) ≈ f_eval(rn_2, u0, [], t) - @test jac_eval(rn_1, u0, p_1, t) ≈ jac_eval(rn_2, u0, [], t) - @test g_eval(rn_1, u0, p_1, t) ≈ g_eval(rn_2, u0, [], t) - end - end + # Make simulations. + u0 = [x => 1.0, y => 0.5, x2y => 0.0] + tspan = (0.0, 15.0) + ps = [k => 1.0, kD => 0.1, kB => 0.5, d => 5.0] + oprob_dsl = ODEProblem(rn_dsl, u0, tspan, ps) + oprob_prog = ODEProblem(rn_prog, u0, tspan, ps) + + sol_dsl = solve(oprob_dsl, Tsit5(); saveat=0.1) + sol_prog = solve(oprob_prog, Tsit5(); saveat=0.1) + + # Tests observables equal in both cases. + @test oprob_dsl[:X] == oprob_prog[:X] + @test oprob_dsl[:Y] == oprob_prog[:Y] + @test sol_dsl[:X] == sol_prog[:X] + @test sol_dsl[:Y] == sol_prog[:Y] end # Tests for complicated observable formula. @@ -435,51 +557,32 @@ let u0 = Dict([:X1 => 1.0, :X2 => 2.0, :X3 => 3.0, :X4 => 4.0]) ps = Dict([:p => 1.0, :d => 0.2, :k1 => 1.5, :k2 => 1.5, :k3 => 5.0, :k4 => 5.0, :op_1 => 1.5, :op_2 => 1.5]) - rxs_1 = [Reaction(p, nothing, [X1], nothing, [2]), - Reaction(k1, [X1], [X2], [1], [1]), - Reaction(k2, [X2], [X3], [1], [1]), - Reaction(k3, [X2], [X3], [1], [1]), - Reaction(d, [X3], nothing, [1], nothing)] - @named rs_1 = ReactionSystem(rxs_1, t, [X1, X2, X3], [p, k1, k2, k3, d]) - push!(identical_networks_4, reaction_networks_standard[8] => rs_1) + oprob = ODEProblem(rn, u0, (0.0, 1000.0), ps) + sol = solve(oprob, Tsit5()) @test sol[:X][1] == u0[:X1]^2 + ps[:op_1]*(u0[:X2] + 2*u0[:X3]) + u0[:X1]*u0[:X4]/ps[:op_2] + ps[:p] end - rxs_3 = [Reaction(k1, [X1], [X2], [1], [1]), - Reaction(0, [X2], [X3], [1], [1]), - Reaction(k2, [X3], [X4], [1], [1]), - Reaction(k3, [X4], [X5], [1], [1])] - @named rs_3 = ReactionSystem(rxs_3, t, [X1, X2, X3, X4, X5], [k1, k2, k3]) - push!(identical_networks_4, reaction_networks_weird[7] => rs_3) - - for networks in identical_networks_4 - @test isequal(get_iv(networks[1]), get_iv(networks[2])) - @test alleq(get_unknowns(networks[1]), get_unknowns(networks[2])) - @test alleq(get_ps(networks[1]), get_ps(networks[2])) - @test ModelingToolkit.get_systems(networks[1]) == - ModelingToolkit.get_systems(networks[2]) - @test length(get_eqs(networks[1])) == length(get_eqs(networks[2])) - for (e1, e2) in zip(get_eqs(networks[1]), get_eqs(networks[2])) - @test isequal(e1.rate, e2.rate) - @test isequal(e1.substrates, e2.substrates) - @test isequal(e1.products, e2.products) - @test isequal(e1.substoich, e2.substoich) - @test isequal(e1.prodstoich, e2.prodstoich) - @test isequal(e1.netstoich, e2.netstoich) - @test isequal(e1.only_use_rate, e2.only_use_rate) +# Checks that ivs are correctly found. +let + rn = @reaction_network begin + @ivs t x y + @species V1(t) V2(t,x) V3(t, y) W1(t) W2(t, y) + @observables begin + V ~ V1 + 2V2 + 3V3 + W ~ W1 + W2 end end + V,W = getfield.(observed(rn), :lhs) + @test isequal(arguments(ModelingToolkit.unwrap(V)), Any[rn.iv, rn.sivs[1], rn.sivs[2]]) + @test isequal(arguments(ModelingToolkit.unwrap(W)), Any[rn.iv, rn.sivs[2]]) end -### Tests Usage of Various Symbols ### - -# Tests that time is handled properly. +# Checks that metadata is written properly. let - time_network = @reaction_network begin - (t, k2), X1 ↔ X2 - (k3, t), X2 ↔ X3 - (t, k6), X3 ↔ X1 + rn = @reaction_network rn_observed begin + @observables (X, [description="my_description"]) ~ X1 + X2 + k, 0 --> X1 + X2 end @test ModelingToolkit.getdescription(observed(rn)[1].lhs) == "my_description" end @@ -502,62 +605,64 @@ let @test isequal(observed(rn1)[1].lhs.metadata, observed(rn2)[1].lhs.metadata) @test isequal(unknowns(rn1), unknowns(rn2)) - @test f_eval(reaction_networks_constraint[1], u, p_1, τ) ≈ f_eval(time_network, u, p_2, τ) - @test jac_eval(reaction_networks_constraint[1], u, p_1, τ) ≈ jac_eval(time_network, u, p_2, τ) - @test g_eval(reaction_networks_constraint[1], u, p_1, τ) ≈ g_eval(time_network, u, p_2, τ) + # Case with metadata. + rn3 = @reaction_network rn_observed begin + @observables (X, [description="description"]) ~ X1 + X2 + k, 0 --> X1 + X2 + end + rn4 = @reaction_network rn_observed begin + @variables X(t) [description="description"] + @observables X ~ X1 + X2 + k, 0 --> X1 + X2 end + @test isequal(observed(rn3)[1].rhs, observed(rn4)[1].rhs) + @test isequal(observed(rn3)[1].lhs.metadata, observed(rn4)[1].lhs.metadata) + @test isequal(unknowns(rn3), unknowns(rn4)) end -# Check that various symbols can be used as species/parameter names. +# Tests for interpolation into the observables option. let - @reaction_network begin - (a, A), n ⟷ N - (b, B), o ⟷ O - (c, C), p ⟷ P - (d, D), q ⟷ Q - (e, E), r ⟷ R - (f, F), s ⟷ S - (g, G), u ⟷ U - (h, H), v ⟷ V - (j, J), w ⟷ W - (k, K), x ⟷ X - (l, L), y ⟷ Y - (m, M), z ⟷ Z + # Interpolation into lhs. + @species X [description="An observable"] + rn1 = @reaction_network begin + @observables $X ~ X1 + X2 + (k1, k2), X1 <--> X2 end @test isequal(observed(rn1)[1].lhs, X) @test ModelingToolkit.getdescription(rn1.X) == "An observable" @test isspecies(rn1.X) @test length(unknowns(rn1)) == 2 - @reaction_network begin (1.0, 1.0), i ⟷ T end - - @reaction_network begin - (å, Å), ü ⟷ Ü - (ä, Ä), ñ ⟷ Ñ - (ö, Ö), æ ⟷ Æ + # Interpolation into rhs. + @parameters n [description="A parameter"] + @species S(t) + rn2 = @reaction_network begin + @observables Stot ~ $S + $n*Sn + (kB, kD), $n*S <--> Sn end + @unpack Stot, Sn, kD, kB = rn2 - @reaction_network begin - (α, Α), ν ⟷ Ν - (β, Β), ξ ⟷ Ξ - (γ, γ), ο ⟷ Ο - (δ, Δ), Π ⟷ Π - (ϵ, Ε), ρ ⟷ Ρ - (ζ, Ζ), σ ⟷ Σ - (η, Η), τ ⟷ Τ - (θ, Θ), υ ⟷ Υ - (ι, Ι), ϕ ⟷ Φ - (κ, κ), χ ⟷ Χ - (λ, Λ), ψ ↔ Ψ - (μ, Μ), ω ⟷ Ω - end + u0 = Dict([S => 5.0, Sn => 1.0]) + ps = Dict([n => 2, kB => 1.0, kD => 1.0]) + oprob = ODEProblem(rn2, u0, (0.0, 1.0), ps) + + @test issetequal(Symbolics.get_variables(observed(rn2)[1].rhs), [S, n, Sn]) + @test oprob[Stot] == u0[S] + ps[n]*u0[Sn] + @test length(unknowns(rn2)) == 2 end # Tests specific declaration of observables as species/variables. let rn = @reaction_network begin - k1, S + I --> 2I - k2, I --> R + @species X(t) + @variables Y(t) + @observables begin + X ~ X + 2X2 + Y ~ Y1 + Y2 + Z ~ X + Y + end + (kB,kD), 2X <--> X2 + (k1,k2), Y1 <--> Y2 end @test isspecies(rn.X) @@ -573,45 +678,68 @@ let k, 0 --> X1 + X2 end - rn2 = @reaction_network arrowtest begin - a1, C --> 0 - a2, 0 --> C - k1, A + B --> C - k2, C --> A + B - b1, B --> 0 + # System with observable in observable formula. + @test_throws Exception @eval @reaction_network begin + @observables begin + X ~ X1 + X2 + X2 ~ 2X + end + (p,d), 0 <--> X1 + X2 + end + + # Multiple @observables options + @test_throws Exception @eval @reaction_network begin + @observables X ~ X1 + X2 + @observables Y ~ Y1 + Y2 + k, 0 --> X1 + X2 + k, 0 --> Y1 + Y2 + end + @test_throws Exception @eval @reaction_network begin + @observables begin + X ~ X1 + X2 + end + @observables begin + X ~ 2(X1 + X2) + end + (p,d), 0 <--> X1 + X2 + end + + # Default value for compound. + @test_throws Exception @eval @reaction_network begin + @observables (X = 1.0) ~ X1 + X2 + k, 0 --> X1 + X2 end - @test rn1 == rn2 -end + # Forbidden symbols as observable names. + @test_throws Exception @eval @reaction_network begin + @observables t ~ t1 + t2 + k, 0 --> t1 + t2 + end + @test_throws Exception @eval @reaction_network begin + @observables im ~ i + m + k, 0 --> i + m + end -# Tests arrow variants in "@reaction" macro . -let - @test isequal((@reaction k, 0 --> X), (@reaction k, X <-- 0)) - @test isequal((@reaction k, 0 --> X), (@reaction k, X ⟻ 0)) - @test isequal((@reaction k, 0 --> X), (@reaction k, 0 → X)) - @test isequal((@reaction k, 0 --> X), (@reaction k, 0 ⥟ X)) -end + # Non-trivial observables expression. + @test_throws Exception @eval @reaction_network begin + @observables X - X1 ~ X2 + k, 0 --> X1 + X2 + end -# Test that symbols with special mean, or that are forbidden, are handled properly. -let - test_network = @reaction_network begin t * k, X --> ∅ end - @test length(species(test_network)) == 1 - @test length(parameters(test_network)) == 1 - - test_network = @reaction_network begin π, X --> ∅ end - @test length(species(test_network)) == 1 - @test length(parameters(test_network)) == 0 - @test reactions(test_network)[1].rate == π - - test_network = @reaction_network begin pi, X --> ∅ end - @test length(species(test_network)) == 1 - @test length(parameters(test_network)) == 0 - @test reactions(test_network)[1].rate == pi - - test_network = @reaction_network begin ℯ, X --> ∅ end - @test length(species(test_network)) == 1 - @test length(parameters(test_network)) == 0 - @test reactions(test_network)[1].rate == ℯ + # Occurrence of undeclared dependants. + @test_throws Exception @eval @reaction_network begin + @observables X ~ X1 + X2 + k, 0 --> X1 + end + + # Interpolation and explicit declaration of an observable. + @variables X(t) + @test_throws Exception @eval @reaction_network begin + @variables X(t) + @observables $X ~ X1 + X2 + (k1,k2), X1 <--> X2 + end +end ### Test `@equations` Option for Coupled CRN/Equations Models ### From bc1833d16c99fd19425d2bd6a4a712287e0b011e Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 21 May 2024 21:32:32 -0400 Subject: [PATCH 118/446] save progress --- .../serialisation_support.jl | 25 ++++--- .../serialise_fields.jl | 26 +++---- .../reactionsystem_serialisation.jl | 69 +++++++++++++++---- 3 files changed, 87 insertions(+), 33 deletions(-) diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index e466bc7835..60ed6913be 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -21,6 +21,13 @@ macro string_prepend!(input1, input2, string) return esc(:($string = $rhs)) end +# Gets the character at a specific index. +get_char(str, idx) = collect(str)[idx] +get_char_end(str, offset) = collect(str)[end - offset] +# Gets a substring (which is robust to unicode characters like η). +get_substring(str, idx1, idx2) = String(collect(str)[idx1, idx2]) +get_substring_end(str, idx1, offset) = String(collect(str)[idx1, end - offset]) + ### Field Serialisation Support Functions ### @@ -72,7 +79,7 @@ end # any calls (e.g. X(t) becomes X). E.g. a species vector [X, Y, Z] is converted to "[X, Y, Z]". function syms_2_strings(syms) strip_called_syms = [strip_call(Symbolics.unwrap(sym)) for sym in syms] - return "$(convert(Vector{Any}, strip_called_syms))"[4:end] + return get_substring("$(convert(Vector{Any}, strip_called_syms))", 4) end # Converts a vector of symbolics (e.g. the species or parameter vectors) to a string corresponding to @@ -100,10 +107,10 @@ function sym_2_declaration_string(sym; multiline_format = false) # to ensure that this is the case. if !(sym isa SymbolicUtils.BasicSymbolic{Real}) sym_type = String(Symbol(typeof(Symbolics.unwrap(sym)))) - if (sym_type[1:28] != "SymbolicUtils.BasicSymbolic{") || (sym_type[end] != '}') + if (get_substring(sym_type, 1, 28) != "SymbolicUtils.BasicSymbolic{") || (get_char_end(sym_type, 0) != '}') error("Encountered symbolic of unexpected type: $sym_type.") end - @string_append! dec_string "::" sym_type[29:end-1] + @string_append! dec_string "::" get_substring_end(sym_type, 29, -1) end # If there is a default value, adds this to the declaration. @@ -120,7 +127,7 @@ function sym_2_declaration_string(sym; multiline_format = false) for metadata in metadata_to_declare @string_append! metadata_string metadata_2_string(sym, metadata) ", " end - @string_append! dec_string metadata_string[1:end-2] "]" + @string_append! dec_string $(get_substring_end(metadata_string, 1, -2)) "]" end # Returns the declaration entry for the symbol. @@ -145,21 +152,21 @@ function x_2_string(x::Vector) for val in x @string_append! output x_2_string(val) ", " end - return output[1:end-2] * "]" + return get_substring_end(output, 1, -2) * "]" end function x_2_string(x::Tuple) output = "(" for val in x @string_append! output x_2_string(val) ", " end - return output[1:end-2] * ")" + return get_substring_end(output, 1, -2) * ")" end function x_2_string(x::Dict) output = "Dict([" for key in keys(x) @string_append! output x_2_string(key) " => " x_2_string(x[key]) ", " end - return output[1:end-2] * "])" + return get_substring_end(output, 1, -2) * "])" end function x_2_string(x::Union{Matrix, Symbolics.Arr{Any, 2}}) output = "[" @@ -167,9 +174,9 @@ function x_2_string(x::Union{Matrix, Symbolics.Arr{Any, 2}}) for i = 1:size(x)[2] @string_append! output x_2_string(x[j,i]) " " end - output = output[1:end-1] * "; " + output = get_substring_end(output, 1, -1) * "; " end - return output[1:end-2] *"]" + return get_substring_end(output, 1, -2) *"]" end diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index 59c92eac45..964e3bb617 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -87,7 +87,7 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, t p_deps && (@string_append! us_n_ps_string "parameters, ") sp_deps && (@string_append! us_n_ps_string "species, ") var_deps && (@string_append! us_n_ps_string "variables, ") - us_n_ps_string = us_n_ps_string[1:end-2] + us_n_ps_string = get_substring_end(us_n_ps_string, 1, -2) @string_append! us_n_ps_string " depends on the declaration of other parameters, species, and/or variables.\n# These are specially handled here.\n" end @@ -117,7 +117,7 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, t p_deps && (@string_append! us_n_ps_string "ps = " syms_2_strings(ps_all) "\n") sp_deps && (@string_append! us_n_ps_string "sps = " syms_2_strings(sps_all) "\n") var_deps && (@string_append! us_n_ps_string "vars = " syms_2_strings(vars_all) "\n") - us_n_ps_string = us_n_ps_string[1:end-1] + us_n_ps_string = get_substring_end(us_n_ps_string, 1, -1) end # If this is not a top-level system, `local ` must be added to all declarations. @@ -217,7 +217,9 @@ function get_reactions_string(rn::ReactionSystem) strip_call_dict = make_strip_call_dict(rn) # Handles the case with one reaction separately. Only effect is nicer formatting. - (length(get_rxs(rn)) == 1) && (return "rxs = [$(reaction_string(rx, strip_call_dict))]") + if length(get_rxs(rn)) == 1 + return "rxs = [$(reaction_string(get_rxs(rn)[1], strip_call_dict))]" + end # Creates the string corresponding to the code which generates the system's reactions. rxs_string = "rxs = [" @@ -226,7 +228,7 @@ function get_reactions_string(rn::ReactionSystem) end # Updates the string (including removing the last `,`) and returns it. - return rxs_string[1:end-1] * "\n]" + return get_substring_end(rxs_string, 1, -1) * "\n]" end # Creates a string that corresponds to the declaration of a single `Reaction`. @@ -251,7 +253,7 @@ function reaction_string(rx::Reaction, strip_call_dict) metadata_entry = "$(x_2_string(entry)), " @string_append! rx_string metadata_entry end - rx_string = rx_string[1:end-2] * "]" + rx_string = get_substring_end(rx_string, 1, -2) * "]" end # Returns the Reaction string. @@ -291,7 +293,7 @@ function get_equations_string(rn::ReactionSystem) end # Updates the string (including removing the last `,`) and returns it. - return eqs_string[1:end-1] * "\n]" + return get_substring_end(eqs_string, 1, -1) * "\n]" end # Creates an annotation for the system's equations. @@ -378,7 +380,7 @@ function get_continuous_events_string(rn::ReactionSystem) end # Updates the string (including removing the last `,`) and returns it. - return continuous_events_string[1:end-1] * "\n]" + return get_substring_end(continuous_events_string, 1, -1) * "\n]" end # Creates a string that corresponds to the declaration of a single continuous event. @@ -388,7 +390,7 @@ function continuous_event_string(continuous_event, strip_call_dict) for eq in continuous_event.eqs @string_append! eqs_string expression_2_string(eq; strip_call_dict) ", " end - eqs_string = eqs_string[1:end-2] * "]" + eqs_string = get_substring_end(eqs_string, 1, -2) * "]" # Creates the string corresponding to the affects. # Continuous events' `affect` field should probably be called `affects`. Likely the `s` was @@ -397,7 +399,7 @@ function continuous_event_string(continuous_event, strip_call_dict) for affect in continuous_event.affect @string_append! affects_string expression_2_string(affect; strip_call_dict) ", " end - affects_string = affects_string[1:end-2] * "]" + affects_string = get_substring_end(affects_string, 1, -2) * "]" return eqs_string * " => " * affects_string end @@ -435,7 +437,7 @@ function get_discrete_events_string(rn::ReactionSystem) end # Updates the string (including removing the last `,`) and returns it. - return discrete_events_string[1:end-1] * "\n]" + return get_substring_end(discrete_events_string, 1, -1) * "\n]" end # Creates a string that corresponds to the declaration of a single discrete event. @@ -453,7 +455,7 @@ function discrete_event_string(discrete_event, strip_call_dict) for affect in discrete_event.affects @string_append! affects_string expression_2_string(affect; strip_call_dict) ", " end - affects_string = affects_string[1:end-2] * "]" + affects_string = get_substring_end(affects_string, 1, -2) * "]" return condition_string * " => " * affects_string end @@ -504,7 +506,7 @@ function get_systems_string(rn::ReactionSystem, annotate::Bool) # Manipulates the subsystem declaration to make it nicer. subsystem_string = get_full_system_string(system, annotate, false) subsystem_string = replace(subsystem_string, "\n" => "\n\t") - subsystem_string = "let\n" * subsystem_string[7:end-6] * "end" + subsystem_string = "let\n" * get_substring_end(subsystem_string, 7, -6) * "end" @string_append! systems_string "\nsystems[$idx] = " subsystem_string end diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index 7059c56088..207084f507 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -2,6 +2,7 @@ # Fetch packages. using Catalyst +using Catalyst: get_rxs using ModelingToolkit: getdefault, getdescription, get_metadata # Creates missing getters for MTK metadata (can be removed once added to MTK). @@ -20,10 +21,10 @@ D = default_time_deriv() # Checks annotated and non-annotated files against manually written ones. let # Creates and serialises the model. - rn = @reaction_system begin + rn = @reaction_network rn begin @observables X2 ~ 2X @equations D(V) ~ 1 - V - (p,d), 0 <--> X + d, X --> 0 end save_reaction_network("test_serialisation_annotated.jl", rn; safety_check = false) save_reaction_network("test_serialisation.jl", rn; annotate = false, safety_check = false) @@ -31,10 +32,52 @@ let # Checks equivalence. file_string_annotated = read("test_serialisation_annotated.jl", String) file_string = read("test_serialisation.jl", String) - file_string_annotated_real = "" - file_string_real = "" + file_string_annotated_real = """let + + # Independent variable: + @variables t + + # Parameters: + ps = @parameters d + + # Species: + sps = @species X(t) + + # Variables: + vars = @variables V(t) + + # Reactions: + rxs = [Reaction(d, [X], nothing, [1], nothing)] + + # Equations: + eqs = [Differential(t)(V) ~ 1 - V] + + # Observables: + @variables X2(t) + observed = [X2 ~ 2X] + + # Declares ReactionSystem model: + rs = ReactionSystem([rxs; eqs], t, [sps; vars], ps; name = :rn, observed) + complete(rs) + + end""" + file_string_real = """let + + @variables t + ps = @parameters d + sps = @species X(t) + vars = @variables V(t) + rxs = [Reaction(d, [X], nothing, [1], nothing)] + eqs = [Differential(t)(V) ~ 1 - V] + @variables X2(t) + observed = [X2 ~ 2X] + + rs = ReactionSystem([rxs; eqs], t, [sps; vars], ps; name = :rn, observed) + complete(rs) + + end""" @test file_string_annotated == file_string_annotated_real - @test file == file_string_real + @test file_string == file_string_real # Deletes the files. rm("test_serialisation_annotated.jl") @@ -95,8 +138,10 @@ let end # Creates the hierarchical model. Adds metadata to both reactions and the systems. + # First reaction has `+ s + r` as that is easier than manually listing all symbolics. + # (These needs to be part of the system somehow, as they are only added through the `misc` metadata) rxs1 = [ - Reaction(a + A, [X], [], metadata = [:misc => bool_md]) + Reaction(a + A + s + r, [X], [], metadata = [:misc => bool_md]) Reaction(b + B, [Y], [], metadata = [:misc => int_md]) Reaction(c + C, [Z], [], metadata = [:misc => sym_md]) Reaction(d1 + D1, [V1], [], metadata = [:misc => str_md]) @@ -329,15 +374,15 @@ let X3 ~ 3*X end @continuous_events begin - X2 < 5.0 => [X ~ X + 1.0] - X3 > 20.0 => [X ~ X - 1.0] + [X ~ 5.0] => [X ~ X + 1.0] + [X ~ 20.0] => [X ~ X - 1.0] end - @discrete_events [5.0 => d ~ d/2] + @discrete_events 5.0 => [d ~ d/2] d, X --> 0 end # Checks that serialisation works. - save_reaction_network("serialised_rs", rs; safety_check = false) + save_reaction_network("serialised_rs.jl", rs; safety_check = false) @test isequal(rs, include("serialised_rs.jl")) rm("serialised_rs.jl") end @@ -371,7 +416,7 @@ let save_reaction_network("serialised_rs_complete.jl", rs_complete) rs_complete_loaded = include("serialised_rs_complete.jl") @test ModelingToolkit.iscomplete(rs_complete_loaded) - rn("serialised_rs_complete.jl") + rm("serialised_rs_complete.jl") # Checks for non-complete system. rs_incomplete = @network_component begin @@ -380,5 +425,5 @@ let save_reaction_network("serialised_rs_incomplete.jl", rs_incomplete) rs_incomplete_loaded = include("serialised_rs_incomplete.jl") @test !ModelingToolkit.iscomplete(rs_incomplete_loaded) - rn("serialised_rs_incomplete.jl") + rm("serialised_rs_incomplete.jl") end \ No newline at end of file From 1b2db7a7c5ae04a5a388e72e00a43fc402f6b15a Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 21 May 2024 22:02:47 -0400 Subject: [PATCH 119/446] save progress --- .../serialisation_support.jl | 10 +++++----- .../reactionsystem_serialisation.jl | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 60ed6913be..62b366ca5b 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -23,10 +23,10 @@ end # Gets the character at a specific index. get_char(str, idx) = collect(str)[idx] -get_char_end(str, offset) = collect(str)[end - offset] +get_char_end(str, offset) = collect(str)[end+offset] # Gets a substring (which is robust to unicode characters like η). -get_substring(str, idx1, idx2) = String(collect(str)[idx1, idx2]) -get_substring_end(str, idx1, offset) = String(collect(str)[idx1, end - offset]) +get_substring(str, idx1, idx2) = String(collect(str)[idx1:idx2]) +get_substring_end(str, idx1, offset) = String(collect(str)[idx1:end+offset]) ### Field Serialisation Support Functions ### @@ -79,7 +79,7 @@ end # any calls (e.g. X(t) becomes X). E.g. a species vector [X, Y, Z] is converted to "[X, Y, Z]". function syms_2_strings(syms) strip_called_syms = [strip_call(Symbolics.unwrap(sym)) for sym in syms] - return get_substring("$(convert(Vector{Any}, strip_called_syms))", 4) + return get_substring_end("$(convert(Vector{Any}, strip_called_syms))", 4, 0) end # Converts a vector of symbolics (e.g. the species or parameter vectors) to a string corresponding to @@ -127,7 +127,7 @@ function sym_2_declaration_string(sym; multiline_format = false) for metadata in metadata_to_declare @string_append! metadata_string metadata_2_string(sym, metadata) ", " end - @string_append! dec_string $(get_substring_end(metadata_string, 1, -2)) "]" + @string_append! dec_string get_substring_end(metadata_string, 1, -2) "]" end # Returns the declaration entry for the symbol. diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index 207084f507..10ca349a6a 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -1,7 +1,7 @@ ### Prepare Tests ### # Fetch packages. -using Catalyst +using Catalyst, Test using Catalyst: get_rxs using ModelingToolkit: getdefault, getdescription, get_metadata From 97196451217e2a5f588f339480772cb080cd0d28 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 22 May 2024 13:14:45 -0400 Subject: [PATCH 120/446] up --- .../serialise_reactionsystem.jl | 8 ++++--- .../reactionsystem_serialisation.jl | 21 ++++++++----------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index 1fdf48e866..20cd085d0c 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -36,9 +36,11 @@ function save_reaction_network(filename::String, rn::ReactionSystem; annotate = open(filename, "w") do file write(file, get_full_system_string(rn, annotate, true)) end - if safety_check && !isequal(rn, include(filename)) - rm(filename) - error("The serialised `ReactionSystem` is not equal to the original one. Please make a report (including the full system) at https://github.com/SciML/Catalyst.jl/issues. To disable this behaviour, please pass the `safety_check = false` argument to `save_reaction_network` (warning, this will permit the serialisation of an erroneous system).") + if safety_check + if !isequal(rn, include(joinpath(pwd(), filename))) + rm(filename) + error("The serialised `ReactionSystem` is not equal to the original one. Please make a report (including the full system) at https://github.com/SciML/Catalyst.jl/issues. To disable this behaviour, please pass the `safety_check = false` argument to `save_reaction_network` (warning, this will permit the serialisation of an erroneous system).") + end end return nothing end diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index 10ca349a6a..ee43d7b80d 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -270,7 +270,7 @@ let t_4::Float32 = p, [description="A parameter."] end @species X(t) X2_1(t) X2_2(t) X2_3(t) X2_4(t)=p [description="A species."] - @variables A(t)=p [description="A variable."] B_1(t) B_2(t) B_3(t) B_4(t) O_1(t) O_2(t) O_3(t) O_4(t) + @variables A(t)=p [description="A variable."] B_1(t) B_2(t) B_3(t) B_4(t) # Prepares all equations. eqs_1 = [ @@ -306,12 +306,6 @@ let A + 2B_4^3 ~ b_4 * X ] - # Prepares all observables. - observed_1 = [O_1 ~ X + 2*X2_1] - observed_2 = [O_2 ~ X + 2*X2_2] - observed_3 = [O_3 ~ X + 2*X2_3] - observed_4 = [O_4 ~ X + 2*X2_4] - # Prepares all events. continuous_events_1 = [(A ~ t_1) => [A ~ A + 2.0, X ~ X/2]] continuous_events_2 = [(A ~ t_2) => [A ~ A + 2.0, X ~ X/2]] @@ -339,16 +333,16 @@ let ] # Creates the systems. - @named rs_4 = ReactionSystem(eqs_4, t; observed = observed_4, continuous_events = continuous_events_4, + @named rs_4 = ReactionSystem(eqs_4, t; continuous_events = continuous_events_4, discrete_events = discrete_events_4, spatial_ivs = sivs, metadata = "System 4", systems = []) - @named rs_2 = ReactionSystem(eqs_2, t; observed = observed_2, continuous_events = continuous_events_2, + @named rs_2 = ReactionSystem(eqs_2, t; continuous_events = continuous_events_2, discrete_events = discrete_events_2, spatial_ivs = sivs, metadata = "System 2", systems = []) - @named rs_3 = ReactionSystem(eqs_3, t; observed = observed_3, continuous_events = continuous_events_3, + @named rs_3 = ReactionSystem(eqs_3, t; continuous_events = continuous_events_3, discrete_events = discrete_events_3, spatial_ivs = sivs, metadata = "System 3", systems = [rs_4]) - @named rs_1 = ReactionSystem(eqs_1, t; observed = observed_1, continuous_events = continuous_events_1, + @named rs_1 = ReactionSystem(eqs_1, t; continuous_events = continuous_events_1, discrete_events = discrete_events_1, spatial_ivs = sivs, metadata = "System 1", systems = [rs_2, rs_3]) rs = complete(rs_1) @@ -365,6 +359,9 @@ end # Tests for (slightly more) complicate system created via the DSL. # Tests for cases where the number of input is untested (i.e. multiple observables and continuous # events, but single equations and discrete events). +# Currently broken due to Symbolics doing something weird with observable variables, where these +# end up not being internal due to something internal in symbolics. I have tried tracking down the +# obscure symbolics subfields. let # Declares the model. rs = @reaction_network begin @@ -383,7 +380,7 @@ let # Checks that serialisation works. save_reaction_network("serialised_rs.jl", rs; safety_check = false) - @test isequal(rs, include("serialised_rs.jl")) + @test_broken isequal(rs, include("serialised_rs.jl")) rm("serialised_rs.jl") end From adcc75f8635a79311b58286e0755c37d6d92ec0e Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jun 2024 12:25:34 -0400 Subject: [PATCH 121/446] save progress --- .../reactionsystem_serialisation.jl | 14 +++++++------- test/runtests.jl | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index ee43d7b80d..00bbcb1982 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -160,7 +160,7 @@ let # Loads the model and checks that it is correct. Removes the saved file save_reaction_network("serialised_rs.jl", rs; safety_check = false) - rs_loaded = include("serialised_rs.jl") + rs_loaded = include("../serialised_rs.jl") @test rs == rs_loaded rm("serialised_rs.jl") @@ -248,7 +248,7 @@ let # Creates model and checks it against serialised version. @named rs = ReactionSystem([], τ, [X, Y, Z, U, V, W, A, B, C, D, E, F, G], [a, b, c, d, e, f]) save_reaction_network("serialised_rs.jl", rs; safety_check = false) - @test rs == include("serialised_rs.jl") + @test rs == include("../serialised_rs.jl") rm("serialised_rs.jl") end @@ -349,9 +349,9 @@ let # Checks that the correct system is saved (both complete and incomplete ones). save_reaction_network("serialised_rs_incomplete.jl", rs_1; safety_check = false) - @test isequal(rs_1, include("serialised_rs_incomplete.jl")) + @test isequal(rs_1, include("../serialised_rs_incomplete.jl")) save_reaction_network("serialised_rs_complete.jl", rs; safety_check = false) - @test isequal(rs, include("serialised_rs_complete.jl")) + @test isequal(rs, include("../serialised_rs_complete.jl")) rm("serialised_rs_incomplete.jl") rm("serialised_rs_complete.jl") end @@ -380,7 +380,7 @@ let # Checks that serialisation works. save_reaction_network("serialised_rs.jl", rs; safety_check = false) - @test_broken isequal(rs, include("serialised_rs.jl")) + @test_broken isequal(rs, include("../serialised_rs.jl")) rm("serialised_rs.jl") end @@ -411,7 +411,7 @@ let (p,d), 0 <--> X end save_reaction_network("serialised_rs_complete.jl", rs_complete) - rs_complete_loaded = include("serialised_rs_complete.jl") + rs_complete_loaded = include("../serialised_rs_complete.jl") @test ModelingToolkit.iscomplete(rs_complete_loaded) rm("serialised_rs_complete.jl") @@ -420,7 +420,7 @@ let (p,d), 0 <--> X end save_reaction_network("serialised_rs_incomplete.jl", rs_incomplete) - rs_incomplete_loaded = include("serialised_rs_incomplete.jl") + rs_incomplete_loaded = include("../serialised_rs_incomplete.jl") @test !ModelingToolkit.iscomplete(rs_incomplete_loaded) rm("serialised_rs_incomplete.jl") end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 9196ca91c8..a561cc973d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,6 +10,7 @@ using SafeTestsets, Test ### Run Tests ### @time begin + @time @safetestset "ReactionSystem Serialisation Balancing" begin include("miscellaneous_tests/reactionsystem_serialisation.jl") end #if GROUP == "All" || GROUP == "ModelCreation" # Tests the `ReactionSystem` structure and its properties. @time @safetestset "Reaction Structure" begin include("reactionsystem_core/reaction.jl") end @@ -37,6 +38,7 @@ using SafeTestsets, Test @time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end @time @safetestset "Compound Species" begin include("miscellaneous_tests/compound_macro.jl") end @time @safetestset "Reaction Balancing" begin include("miscellaneous_tests/reaction_balancing.jl") end + @time @safetestset "ReactionSystem Serialisation Balancing" begin include("miscellaneous_tests/reactionsystem_serialisation.jl") end # Tests reaction network analysis features. @time @safetestset "Conservation Laws" begin include("network_analysis/conservation_laws.jl") end From 552eb6201aeb65b9a954a4b6b04c35358f45ab8c Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jun 2024 12:28:51 -0400 Subject: [PATCH 122/446] up --- src/reactionsystem.jl | 15 +++++++++++++++ test/reactionsystem_core/reactionsystem.jl | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 3be6b6c06c..da65139bec 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -207,6 +207,13 @@ end ### ReactionSystem Structure ### +# Constant storing all reaction system fields (in order). Used to check whether the `ReactionSystem` +# structure have been updated (in the `reactionsystem_uptodate` function). +const reactionsystem_fields = [:eqs, :rxs, :iv, :sivs, :unknowns, :species, :ps, :var_to_name, + :observed, :name, :systems, :defaults, :connection_type, + :networkproperties, :combinatoric_ratelaws, :continuous_events, + :discrete_events, :metadata, :complete] + """ $(TYPEDEF) @@ -1025,6 +1032,14 @@ end ### General `ReactionSystem`-specific Functions ### +# Checks if the `ReactionSystem` structure have been updated without also updating the +# `reactionsystem_fields` constant. If this is the case, returns `false`. This is used in +# certain functionalities which would break if the `ReactionSystem` structure is updated without +# also updating tehse functionalities. +function reactionsystem_uptodate() + fieldnames(ReactionSystem) == reactionsystem_fields +end + # used in the `__unpacksys` function. function __unpacksys(rn) ex = :(begin end) diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index 7e854efcc7..95988c0cbd 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -740,4 +740,12 @@ let @test nameof(ModelingToolkit.get_iv(empty_network)) == :t @test length(ModelingToolkit.get_unknowns(empty_network)) == 0 @test length(ModelingToolkit.get_ps(empty_network)) == 0 +end + +# Checks that the `reactionsystem_uptodate` function work. If it does not, the ReactionSystem +# strcuture's fields have been updated, without updating the `reactionsystem_fields` costant. If so, +# there are several places in the code where the `reactionsystem_uptodate` function is called, here +# the code might need adaptation to take the updated reaction system into account. +let + @test Catalyst.reactionsystem_uptodate() end \ No newline at end of file From 241cbd5dad60ecb36548be4c86b13d397b1f92f4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jun 2024 12:36:16 -0400 Subject: [PATCH 123/446] start tests --- test/failed_serialisation.jl | 0 test/runtests.jl | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 test/failed_serialisation.jl diff --git a/test/failed_serialisation.jl b/test/failed_serialisation.jl new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/runtests.jl b/test/runtests.jl index a561cc973d..6d084286c5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,7 +10,6 @@ using SafeTestsets, Test ### Run Tests ### @time begin - @time @safetestset "ReactionSystem Serialisation Balancing" begin include("miscellaneous_tests/reactionsystem_serialisation.jl") end #if GROUP == "All" || GROUP == "ModelCreation" # Tests the `ReactionSystem` structure and its properties. @time @safetestset "Reaction Structure" begin include("reactionsystem_core/reaction.jl") end @@ -38,7 +37,7 @@ using SafeTestsets, Test @time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end @time @safetestset "Compound Species" begin include("miscellaneous_tests/compound_macro.jl") end @time @safetestset "Reaction Balancing" begin include("miscellaneous_tests/reaction_balancing.jl") end - @time @safetestset "ReactionSystem Serialisation Balancing" begin include("miscellaneous_tests/reactionsystem_serialisation.jl") end + @time @safetestset "ReactionSystem Serialisation" begin include("miscellaneous_tests/reactionsystem_serialisation.jl") end # Tests reaction network analysis features. @time @safetestset "Conservation Laws" begin include("network_analysis/conservation_laws.jl") end From 48edfb5e4ea0e78f8cb55c12f0f118d42750201d Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jun 2024 12:47:25 -0400 Subject: [PATCH 124/446] save_reaction_network -> save_reactionsystem --- src/Catalyst.jl | 2 +- src/reactionsystem.jl | 4 ++-- .../serialise_reactionsystem.jl | 8 ++++---- .../reactionsystem_serialisation.jl | 20 +++++++++---------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index ea1a9f2c23..6521d0afb0 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -188,6 +188,6 @@ include("spatial_reaction_systems/lattice_jump_systems.jl") include("reactionsystem_serialisation/serialisation_support.jl") include("reactionsystem_serialisation/serialise_fields.jl") include("reactionsystem_serialisation/serialise_reactionsystem.jl") -export save_reaction_network +export save_reactionsystem end # module diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index da65139bec..e86d536fec 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -209,10 +209,10 @@ end # Constant storing all reaction system fields (in order). Used to check whether the `ReactionSystem` # structure have been updated (in the `reactionsystem_uptodate` function). -const reactionsystem_fields = [:eqs, :rxs, :iv, :sivs, :unknowns, :species, :ps, :var_to_name, +const reactionsystem_fields = (:eqs, :rxs, :iv, :sivs, :unknowns, :species, :ps, :var_to_name, :observed, :name, :systems, :defaults, :connection_type, :networkproperties, :combinatoric_ratelaws, :continuous_events, - :discrete_events, :metadata, :complete] + :discrete_events, :metadata, :complete) """ $(TYPEDEF) diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index 20cd085d0c..91589a436a 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -1,5 +1,5 @@ """ - save_reaction_network(filename::String, rn::ReactionSystem; annotate = true, safety_check = true) + save_reactionsystem(filename::String, rn::ReactionSystem; annotate = true, safety_check = true) Save a `ReactionSystem` model to a file. The `ReactionSystem` is saved as runnable Julia code. This can both be used to save a `ReactionSystem` model, but also to write it to a file for easy inspection. @@ -18,7 +18,7 @@ Example: rn = @reaction_network begin (p,d), 0 <--> X end -save_reaction_network("rn.jls", rn) +save_reactionsystem("rn.jls", rn) ``` The model can now be loaded using ```julia @@ -32,14 +32,14 @@ Notes: - Reaction systems with components that have units cannot currently be saved. - The `ReactionSystem` is saved using *programmatic* (not DSL) format for model creation. """ -function save_reaction_network(filename::String, rn::ReactionSystem; annotate = true, safety_check = true) +function save_reactionsystem(filename::String, rn::ReactionSystem; annotate = true, safety_check = true) open(filename, "w") do file write(file, get_full_system_string(rn, annotate, true)) end if safety_check if !isequal(rn, include(joinpath(pwd(), filename))) rm(filename) - error("The serialised `ReactionSystem` is not equal to the original one. Please make a report (including the full system) at https://github.com/SciML/Catalyst.jl/issues. To disable this behaviour, please pass the `safety_check = false` argument to `save_reaction_network` (warning, this will permit the serialisation of an erroneous system).") + error("The serialised `ReactionSystem` is not equal to the original one. Please make a report (including the full system) at https://github.com/SciML/Catalyst.jl/issues. To disable this behaviour, please pass the `safety_check = false` argument to `save_reactionsystem` (warning, this will permit the serialisation of an erroneous system).") end end return nothing diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index 00bbcb1982..eab88d0abf 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -26,8 +26,8 @@ let @equations D(V) ~ 1 - V d, X --> 0 end - save_reaction_network("test_serialisation_annotated.jl", rn; safety_check = false) - save_reaction_network("test_serialisation.jl", rn; annotate = false, safety_check = false) + save_reactionsystem("test_serialisation_annotated.jl", rn; safety_check = false) + save_reactionsystem("test_serialisation.jl", rn; annotate = false, safety_check = false) # Checks equivalence. file_string_annotated = read("test_serialisation_annotated.jl", String) @@ -159,7 +159,7 @@ let rs = complete(rs1) # Loads the model and checks that it is correct. Removes the saved file - save_reaction_network("serialised_rs.jl", rs; safety_check = false) + save_reactionsystem("serialised_rs.jl", rs; safety_check = false) rs_loaded = include("../serialised_rs.jl") @test rs == rs_loaded rm("serialised_rs.jl") @@ -247,7 +247,7 @@ let # Creates model and checks it against serialised version. @named rs = ReactionSystem([], τ, [X, Y, Z, U, V, W, A, B, C, D, E, F, G], [a, b, c, d, e, f]) - save_reaction_network("serialised_rs.jl", rs; safety_check = false) + save_reactionsystem("serialised_rs.jl", rs; safety_check = false) @test rs == include("../serialised_rs.jl") rm("serialised_rs.jl") end @@ -348,9 +348,9 @@ let rs = complete(rs_1) # Checks that the correct system is saved (both complete and incomplete ones). - save_reaction_network("serialised_rs_incomplete.jl", rs_1; safety_check = false) + save_reactionsystem("serialised_rs_incomplete.jl", rs_1; safety_check = false) @test isequal(rs_1, include("../serialised_rs_incomplete.jl")) - save_reaction_network("serialised_rs_complete.jl", rs; safety_check = false) + save_reactionsystem("serialised_rs_complete.jl", rs; safety_check = false) @test isequal(rs, include("../serialised_rs_complete.jl")) rm("serialised_rs_incomplete.jl") rm("serialised_rs_complete.jl") @@ -379,7 +379,7 @@ let end # Checks that serialisation works. - save_reaction_network("serialised_rs.jl", rs; safety_check = false) + save_reactionsystem("serialised_rs.jl", rs; safety_check = false) @test_broken isequal(rs, include("../serialised_rs.jl")) rm("serialised_rs.jl") end @@ -400,7 +400,7 @@ let @named osys = ODESystem([eq], t) @named rs = ReactionSystem(rxs, t; systems = [osys]) - @test_throws Exception save_reaction_network("failed_serialisation.jl", rs) + @test_throws Exception save_reactionsystem("failed_serialisation.jl", rs) end # Checks that completeness is recorded correctly. @@ -410,7 +410,7 @@ let rs_complete = @reaction_network begin (p,d), 0 <--> X end - save_reaction_network("serialised_rs_complete.jl", rs_complete) + save_reactionsystem("serialised_rs_complete.jl", rs_complete) rs_complete_loaded = include("../serialised_rs_complete.jl") @test ModelingToolkit.iscomplete(rs_complete_loaded) rm("serialised_rs_complete.jl") @@ -419,7 +419,7 @@ let rs_incomplete = @network_component begin (p,d), 0 <--> X end - save_reaction_network("serialised_rs_incomplete.jl", rs_incomplete) + save_reactionsystem("serialised_rs_incomplete.jl", rs_incomplete) rs_incomplete_loaded = include("../serialised_rs_incomplete.jl") @test !ModelingToolkit.iscomplete(rs_incomplete_loaded) rm("serialised_rs_incomplete.jl") From b8b5656ff5fb91c5454c31fcaddf7908d27bd7f5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jun 2024 12:51:50 -0400 Subject: [PATCH 125/446] add reactionsystem struct check function --- src/reactionsystem.jl | 8 +++++--- .../serialise_reactionsystem.jl | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index e86d536fec..17823da694 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -208,7 +208,7 @@ end ### ReactionSystem Structure ### # Constant storing all reaction system fields (in order). Used to check whether the `ReactionSystem` -# structure have been updated (in the `reactionsystem_uptodate` function). +# structure have been updated (in the `reactionsystem_uptodate_check` function). const reactionsystem_fields = (:eqs, :rxs, :iv, :sivs, :unknowns, :species, :ps, :var_to_name, :observed, :name, :systems, :defaults, :connection_type, :networkproperties, :combinatoric_ratelaws, :continuous_events, @@ -1036,8 +1036,10 @@ end # `reactionsystem_fields` constant. If this is the case, returns `false`. This is used in # certain functionalities which would break if the `ReactionSystem` structure is updated without # also updating tehse functionalities. -function reactionsystem_uptodate() - fieldnames(ReactionSystem) == reactionsystem_fields +function reactionsystem_uptodate_check() + if fieldnames(ReactionSystem) != reactionsystem_fields + @warn "The `ReactionSystem` strcuture have been modified without this being taken into account in the functionality you are attempting to use. Please report this at https://github.com/SciML/Catalyst.jl/issues. Proceed with cautioun, as there might be errors in whichever funcionality you are attempting to use". + end end # used in the `__unpacksys` function. diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index 91589a436a..3dc61c5c75 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -33,6 +33,7 @@ Notes: - The `ReactionSystem` is saved using *programmatic* (not DSL) format for model creation. """ function save_reactionsystem(filename::String, rn::ReactionSystem; annotate = true, safety_check = true) + reactionsystem_uptodate_check() open(filename, "w") do file write(file, get_full_system_string(rn, annotate, true)) end From 882f6ae7e63c18104f8443a55ef8f823424a7d39 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jun 2024 12:52:47 -0400 Subject: [PATCH 126/446] test `reactionsystem_uptodate_check` --- test/reactionsystem_core/reactionsystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index 95988c0cbd..6876026e0b 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -747,5 +747,5 @@ end # there are several places in the code where the `reactionsystem_uptodate` function is called, here # the code might need adaptation to take the updated reaction system into account. let - @test Catalyst.reactionsystem_uptodate() + @test_nowarn Catalyst.reactionsystem_uptodate_check() end \ No newline at end of file From 6197abf9cec3492f6d8467b5a8c2702c39312e9b Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jun 2024 12:56:16 -0400 Subject: [PATCH 127/446] fix --- src/reactionsystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 17823da694..edb0f9fedf 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -1038,7 +1038,7 @@ end # also updating tehse functionalities. function reactionsystem_uptodate_check() if fieldnames(ReactionSystem) != reactionsystem_fields - @warn "The `ReactionSystem` strcuture have been modified without this being taken into account in the functionality you are attempting to use. Please report this at https://github.com/SciML/Catalyst.jl/issues. Proceed with cautioun, as there might be errors in whichever funcionality you are attempting to use". + @warn "The `ReactionSystem` strcuture have been modified without this being taken into account in the functionality you are attempting to use. Please report this at https://github.com/SciML/Catalyst.jl/issues. Proceed with cautioun, as there might be errors in whichever funcionality you are attempting to use." end end From f02e90d163c498d503890eab8667fcd9e0a8968e Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jun 2024 09:56:23 -0400 Subject: [PATCH 128/446] init --- docs/Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index ebb086fda6..246727a23e 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,7 +5,6 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DiffEqParamEstim = "1130ab10-4a5a-5621-a13d-e4788d82bd4c" -DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DynamicalSystems = "61744808-ddfa-5f27-97ff-6e42cc95d634" From c7eef72eb922e909f1d2436970f5b6bd0093cb5b Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 17 Jan 2024 17:58:33 -0500 Subject: [PATCH 129/446] init --- .../model_file_loading_and_export.md | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 docs/src/catalyst_applications/model_file_loading_and_export.md diff --git a/docs/src/catalyst_applications/model_file_loading_and_export.md b/docs/src/catalyst_applications/model_file_loading_and_export.md new file mode 100644 index 0000000000..cabd837a7f --- /dev/null +++ b/docs/src/catalyst_applications/model_file_loading_and_export.md @@ -0,0 +1,101 @@ +# Loading Chemical Reaction Network Models from Files +Catalyst stores chemical reaction network (CRN) models in `ReactionSystem` structures. This tutorial describes how to load such `ReactionSystem`s from, and save them to, files. This can be used to save models between Julia sessions, or transfer them from one session to another. Furthermore, to facilitate the computation modelling of CRNs, several standardised file formats have been created to represent CRN models (e.g. [SBML](https://sbml.org/)). These enables e.g. CRN models to be shared between different software and programming languages. While Catalyst itself does not have the functionality for loading such files, we will here (briefly) introduce a few packages that can load files to Catalyst `ReactionSystem`s. + +## Saving Catalyst models to, and loading them from, Julia files +Catalyst provides a `save_reaction_system` function, enabling the user to save a `ReactionSystem` to a file. Here we demonstrate it by first creating a simple catalysis network +```@example file_handling_1 +using Catalyst +rn = @reaction_network begin + kB, S + E --> SE + kD, SE --> S + E + kP, SE --> P + E +end +``` +and next saving it to a file +```@example file_handling_1 +save_reaction_system("reaction_network.jl", rn) +``` +Here, `save_reaction_system`'s first argument is the path to the file where we wish to save it. The second argument is the `ReactionSystem` we wish to save. To load the file, we use Julia's `include` function: +```@example file_handling_1 +loaded_rn = include("reaction_network.jl") +``` + +!!! note + The destination file can be in a folder. E.g. `save_reaction_system("my_folder/reaction_network.jl", rn)` saves the model to the file "reaction_network.jl" in the folder "my_folder". + +`include` is used to execute the Julia code from any file. This means that `save_reaction_system` actually saves the model as executable code which re-generates the exact model which was saved (this is the reason why we use the ".jl" extension for the saved file). Indeed, if we print the file we can confirm this: +```@example file_handling_1 +println(read("reaction_network.jl", String)) +rm("reaction_network.jl") # hide +``` + +## Loading and Saving arbitrary Julia variables using Serialization.jl +Julia provide a general and lightweight interface for loading and saving Julia structures to and from files that it can be good to be aware of. It is called [Serialization.jl](https://docs.julialang.org/en/v1/stdlib/Serialization/) and provides two functions, `serialize` and `deserialize`. The first allow us to write a Julia structure to a file. E.g. if we wish to save a parameter set associated with our model, we can use +```@example file_handling_2 +using Serialization +ps = [:kB => 1.0, :kD => 0.1, :kP => 2.0] +serialize("saved_parameters.jls", ps) +``` +Here, we use the extension ".jls" (standing for **J**u**L**ia **S**erialization), however, any can be used. To load a structure, we can then use +```@example file_handling_2 +loaded_sol = deserialize("saved_parameters.jls") +``` + +## [Loading .net files usings ReactionNetworkImporters.jl](@id file_loading_rni_net) +A general-purpose format for storing CRN models are so-called .net files. These can be generated by e.g. [BioNetGen](https://bionetgen.org/). The [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl) package enables the loading of such files to Catalyst `ReactionSystem`. Here we load a [repressilator](https://en.wikipedia.org/wiki/Repressilator) model stored in the "repressilator.net" file: +```@example file_handling_3 +using ReactionNetworkImporters +cp("../assets/model_files/repressilator.net", "repressilator.net") # hide +prn = loadrxnetwork(BNGNetwork(), "repressilator.net") +rm("repressilator.net") # hide +prn # hide +``` +Here, .net files not only contain information regarding the reaction network itself, but also the numeric values (initial conditions and parameter values) required for simulating it. Hence, `loadrxnetwork` generates a `ParsedReactionNetwork` structure, containing all this information. You can access the model as `prn.rn`, the initial conditions as `prn.u0`, and the parameter values as `prn.p`. Furthermore, these initial conditions and parameter values are also made [*default* values](@ref ref) of the model. + +A parsed reaction network can be provided directly to various problem types for simulation. E.g. here we perform an ODE simulation of our repressilator model: +```@example file_handling_3 +using Catalyst, OrdinaryDiffEq, Plots +tspan = (0.0, 1000.0) +oprob = ODEProblem(prn, tspan) +sol = solve(oprob) +plot(sol) +``` + +!!! note + It should be noted that .net files supports a wide range of potential model features, not all of which are currently supported by ReactionNetworkImporters. Hence, there might be some .net files which `loadrxnetwork` will not be able to load. + +A more detailed description of ReactionNetworkImporter's features can be found in its [documentation](https://docs.sciml.ai/ReactionNetworkImporters/stable/). + +## Loading SBML files using SBMLImporter.jl and SBMLToolkit.jl +The Systems Biology Markup Language (SBML) is the most widespread format for representing CRN models. Currently, there exists two different Julia packages, [SBMLImporter.jl](https://github.com/sebapersson/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), both of which are able to load SBML files to Catalyst `ReactionSystem` structures. SBML is able to represent a *very* wide range of model featuresSome of these are not supported by these packages (or Catalyst). Hence, there exist SBML files (typically containing obscure model features such as events with time delays) that currently cannot be loaded into Catalyst models. + +SBMLImporter's `load_SBML` function can be used to load SBML files. Here, we load a [Brusselator](https://en.wikipedia.org/wiki/Brusselator) model stored in the "brusselator.xml" file: +```@example file_handling_4 +using SBMLImporter +cp("../assets/model_files/brusselator.xml", "brusselator.xml") # hide +prn, cbs = load_SBML("brusselator.xml") +rm("brusselator.xml") # hide +prn, cbs # hide +``` +Here, while [ReactionNetworkImporters generates a `ParsedReactionSystem` only](@ref file_loading_rni_net), SBMLImporter generates a `ParsedReactionSystem` (here stored in `prn`) and a [so-called `CallbackSet`](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/#CallbackSet) (here stored in `cbs`). While `prn` can be used to create various problems, when we simulate them, we must also supply `cbs`. E.g. to simulate our brusselator we use: +```@example file_handling_4 +using Catalyst, OrdinaryDiffEq, Plots +tspan = (0.0, 1000.0) +oprob = ODEProblem(prn, tspan) +sol = solve(oprob; cb=cbs) +plot(sol) +``` + +A more detailed description of SBMLImporter's features can be found in its [documentation](https://docs.sciml.ai/ReactionNetworkImporters/stable/). + +### SBMLImporter and SBMLToolkit +Above, we described how to use SBMLImporter to import SBML files. Alternatively, SBMLToolkit can be used instead. It has a slightly different syntax, which is described in its [documentation](https://github.com/SciML/SBMLToolkit.jl). A short comparison of the two packages can be found [here](https://github.com/sebapersson/SBMLImporter.jl?tab=readme-ov-file#differences-compared-to-sbmltoolkit). Generally, while they both perform well, we note that for *jump simulations* SBMLImporter is preferable (its way for internally representing reaction event enables more performant jump simulations). + +## Loading models from matrix representation using ReactionNetworkImporters.jl +While CRN models can be represented through various file formats, they can also be represented in various matrix forms. E.g. a CRN with $m$ species and $n$ reactions (and with constant rates) can be represented with either +- An $mxn$ substrate matrix (with each species's substrate stoichiometry in each reaction) and an $nxm$ product matrix (with each species's product stoichiometry in each reaction). + +Or +- An $mxn$ complex stoichiometric matrix (...) and a $2mxn$ incidence matrix (...). + +The advantage with these forms is that they offer a compact and very general way to represent a large class of CRNs. ReactionNetworkImporters have the functionality for converting matrices of these forms directly into Catalyst `ReactionSystem` models. Instructions on how to do this are available in [ReactionNetworkImporter's documentation](https://docs.sciml.ai/ReactionNetworkImporters/stable/#Loading-a-matrix-representation). \ No newline at end of file From 399374d094a415b82e6612c7df17906c98177ce2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jun 2024 12:46:33 -0400 Subject: [PATCH 130/446] save progress --- docs/pages.jl | 1 + .../model_file_loading_and_export.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename docs/src/{catalyst_applications => model_creation}/model_file_loading_and_export.md (96%) diff --git a/docs/pages.jl b/docs/pages.jl index 65ea437951..2572bcb69b 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -13,6 +13,7 @@ pages = Any[ #"model_creation/constraint_equations.md", # Events. #"model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. + "model_creation/model_file_loading_and_export.md",# Distributed parameters, rates, and initial conditions. # Loading and writing models to files. "model_creation/model_visualisation.md", #"model_creation/network_analysis.md", diff --git a/docs/src/catalyst_applications/model_file_loading_and_export.md b/docs/src/model_creation/model_file_loading_and_export.md similarity index 96% rename from docs/src/catalyst_applications/model_file_loading_and_export.md rename to docs/src/model_creation/model_file_loading_and_export.md index cabd837a7f..b492cd9843 100644 --- a/docs/src/catalyst_applications/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -1,5 +1,5 @@ # Loading Chemical Reaction Network Models from Files -Catalyst stores chemical reaction network (CRN) models in `ReactionSystem` structures. This tutorial describes how to load such `ReactionSystem`s from, and save them to, files. This can be used to save models between Julia sessions, or transfer them from one session to another. Furthermore, to facilitate the computation modelling of CRNs, several standardised file formats have been created to represent CRN models (e.g. [SBML](https://sbml.org/)). These enables e.g. CRN models to be shared between different software and programming languages. While Catalyst itself does not have the functionality for loading such files, we will here (briefly) introduce a few packages that can load files to Catalyst `ReactionSystem`s. +Catalyst stores chemical reaction network (CRN) models in `ReactionSystem` structures. This tutorial describes how to load such `ReactionSystem`s from, and save them to, files. This can be used to save models between Julia sessions, or transfer them from one session to another. Furthermore, to facilitate the computation modelling of CRNs, several standardised file formats have been created to represent CRN models (e.g. [SBML](https://sbml.org/)). These enables CRN models to be shared between different softwares and programming languages. While Catalyst itself does not have the functionality for loading such files, we will here (briefly) introduce a few packages that can load files to Catalyst `ReactionSystem`s. ## Saving Catalyst models to, and loading them from, Julia files Catalyst provides a `save_reaction_system` function, enabling the user to save a `ReactionSystem` to a file. Here we demonstrate it by first creating a simple catalysis network From a52ced86ec1b8abee3a7d117cfb7c0f86b3fa5bd Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jun 2024 13:27:41 -0400 Subject: [PATCH 131/446] add model files --- docs/src/assets/model_files/brusselator.xml | 161 ++++++++++++++++++ docs/src/assets/model_files/repressilator.net | 93 ++++++++++ .../model_file_loading_and_export.md | 37 ++-- 3 files changed, 275 insertions(+), 16 deletions(-) create mode 100644 docs/src/assets/model_files/brusselator.xml create mode 100644 docs/src/assets/model_files/repressilator.net diff --git a/docs/src/assets/model_files/brusselator.xml b/docs/src/assets/model_files/brusselator.xml new file mode 100644 index 0000000000..228706ff9b --- /dev/null +++ b/docs/src/assets/model_files/brusselator.xml @@ -0,0 +1,161 @@ + + + + + + + + + + + 2024-01-09T21:46:45Z + + + + + + + + + 2024-01-09T21:46:45Z + + + 2024-01-09T21:46:45Z + + + + + + + + + + v + + v + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + compartment + k1 + + + X + 2 + + Y + + + + + + + + + + + + + + + + + + + compartment + B + X + + + + + + + + + + + + + compartment + k1 + X + + + + + + + + + + + + + + + + compartment + + Constant_flux__irreversible + A + + + + + + + + \ No newline at end of file diff --git a/docs/src/assets/model_files/repressilator.net b/docs/src/assets/model_files/repressilator.net new file mode 100644 index 0000000000..769d7a71f0 --- /dev/null +++ b/docs/src/assets/model_files/repressilator.net @@ -0,0 +1,93 @@ +# Created by BioNetGen 2.3.1 +begin parameters + 1 Na 6.022e23 # Constant + 2 V 1.4e-15 # Constant + 3 c0 1e9 # Constant + 4 c1 224 # Constant + 5 c2 9 # Constant + 6 c3 0.5 # Constant + 7 c4 5e-4 # Constant + 8 c5 0.167 # Constant + 9 c6 ln(2)/120 # Constant + 10 c7 ln(2)/600 # Constant + 11 tF 1e-4 # Constant + 12 rF 1000 # Constant + 13 pF 1000 # Constant + 14 _rateLaw1 (((c0/Na)/V)*tF)/pF # ConstantExpression + 15 _rateLaw2 c1*tF # ConstantExpression + 16 _rateLaw3 (((c0/Na)/V)*tF)/pF # ConstantExpression + 17 _rateLaw4 c2*tF # ConstantExpression + 18 _rateLaw5 c3*rF # ConstantExpression + 19 _rateLaw6 c4*rF # ConstantExpression + 20 _rateLaw7 (c5/rF)*pF # ConstantExpression + 21 _rateLaw8 (((c0/Na)/V)*tF)/pF # ConstantExpression + 22 _rateLaw9 c1*tF # ConstantExpression + 23 _rateLaw10 (((c0/Na)/V)*tF)/pF # ConstantExpression + 24 _rateLaw11 c2*tF # ConstantExpression + 25 _rateLaw12 c3*rF # ConstantExpression + 26 _rateLaw13 c4*rF # ConstantExpression + 27 _rateLaw14 (c5/rF)*pF # ConstantExpression + 28 _rateLaw15 (((c0/Na)/V)*tF)/pF # ConstantExpression + 29 _rateLaw16 c1*tF # ConstantExpression + 30 _rateLaw17 (((c0/Na)/V)*tF)/pF # ConstantExpression + 31 _rateLaw18 c2*tF # ConstantExpression + 32 _rateLaw19 c3*rF # ConstantExpression + 33 _rateLaw20 c4*rF # ConstantExpression + 34 _rateLaw21 (c5/rF)*pF # ConstantExpression +end parameters +begin species + 1 Null() 1 + 2 gTetR(lac!1,lac!2).pLacI(tet!1).pLacI(tet!2) 1 + 3 gCI(tet!1,tet!2).pTetR(cI!1).pTetR(cI!2) 1 + 4 gLacI(cI!1,cI!2).pCI(lac!1).pCI(lac!2) 1 + 5 mTetR() 3163 + 6 mCI() 6819 + 7 mLacI() 129 + 8 pTetR(cI) 183453 + 9 pCI(lac) 2006198 + 10 pLacI(tet) 165670 + 11 gTetR(lac!1,lac).pLacI(tet!1) 0 + 12 gCI(tet!1,tet).pTetR(cI!1) 0 + 13 gLacI(cI!1,cI).pCI(lac!1) 0 + 14 gTetR(lac,lac) 0 + 15 gCI(tet,tet) 0 + 16 gLacI(cI,cI) 0 +end species +begin reactions + 1 2 10,11 2*_rateLaw4 #_reverse__R2 + 2 2 2,5 _rateLaw6 #_R4 + 3 5 5,8 _rateLaw7 #_R5 + 4 1,5 1 c6 #_R6 + 5 1,8 1 c7 #_R7 + 6 3 8,12 2*_rateLaw11 #_reverse__R9 + 7 3 3,6 _rateLaw13 #_R11 + 8 6 6,9 _rateLaw14 #_R12 + 9 1,6 1 c6 #_R13 + 10 1,9 1 c7 #_R14 + 11 4 9,13 2*_rateLaw18 #_reverse__R16 + 12 4 4,7 _rateLaw20 #_R18 + 13 7 7,10 _rateLaw21 #_R19 + 14 1,7 1 c6 #_R20 + 15 1,10 1 c7 #_R21 + 16 11 10,14 _rateLaw2 #_reverse__R1 + 17 10,11 2 _rateLaw3 #_R2 + 18 11 5,11 _rateLaw6 #_R4 + 19 12 8,15 _rateLaw9 #_reverse__R8 + 20 8,12 3 _rateLaw10 #_R9 + 21 12 6,12 _rateLaw13 #_R11 + 22 13 9,16 _rateLaw16 #_reverse__R15 + 23 9,13 4 _rateLaw17 #_R16 + 24 13 7,13 _rateLaw20 #_R18 + 25 10,14 11 2*_rateLaw1 #_R1 + 26 14 5,14 _rateLaw5 #_R3 + 27 8,15 12 2*_rateLaw8 #_R8 + 28 15 6,15 _rateLaw12 #_R10 + 29 9,16 13 2*_rateLaw15 #_R15 + 30 16 7,16 _rateLaw19 #_R17 +end reactions +begin groups + 1 pTetR 8 + 2 pCI 9 + 3 pLacI 10 + 4 NULL 1 +end groups \ No newline at end of file diff --git a/docs/src/model_creation/model_file_loading_and_export.md b/docs/src/model_creation/model_file_loading_and_export.md index b492cd9843..1d32293587 100644 --- a/docs/src/model_creation/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -2,7 +2,7 @@ Catalyst stores chemical reaction network (CRN) models in `ReactionSystem` structures. This tutorial describes how to load such `ReactionSystem`s from, and save them to, files. This can be used to save models between Julia sessions, or transfer them from one session to another. Furthermore, to facilitate the computation modelling of CRNs, several standardised file formats have been created to represent CRN models (e.g. [SBML](https://sbml.org/)). These enables CRN models to be shared between different softwares and programming languages. While Catalyst itself does not have the functionality for loading such files, we will here (briefly) introduce a few packages that can load files to Catalyst `ReactionSystem`s. ## Saving Catalyst models to, and loading them from, Julia files -Catalyst provides a `save_reaction_system` function, enabling the user to save a `ReactionSystem` to a file. Here we demonstrate it by first creating a simple catalysis network +Catalyst provides a `save_reactionsystem` function, enabling the user to save a `ReactionSystem` to a file. Here we demonstrate this by first creating a [simple cross-coupling model](@ref basic_CRN_library_cc): ```@example file_handling_1 using Catalyst rn = @reaction_network begin @@ -13,21 +13,24 @@ end ``` and next saving it to a file ```@example file_handling_1 -save_reaction_system("reaction_network.jl", rn) +save_reactionsystem("cross_coupling.jl", rn) ``` -Here, `save_reaction_system`'s first argument is the path to the file where we wish to save it. The second argument is the `ReactionSystem` we wish to save. To load the file, we use Julia's `include` function: +Here, `save_reactionsystem`'s first argument is the path to the file where we wish to save it. The second argument is the `ReactionSystem` we wish to save. To load the file, we use Julia's `include` function: ```@example file_handling_1 -loaded_rn = include("reaction_network.jl") +loaded_rn = include("cross_coupling.jl") ``` !!! note - The destination file can be in a folder. E.g. `save_reaction_system("my_folder/reaction_network.jl", rn)` saves the model to the file "reaction_network.jl" in the folder "my_folder". + The destination file can be in a folder. E.g. `save_reactionsystem("my_folder/reaction_network.jl", rn)` saves the model to the file "reaction_network.jl" in the folder "my_folder". -`include` is used to execute the Julia code from any file. This means that `save_reaction_system` actually saves the model as executable code which re-generates the exact model which was saved (this is the reason why we use the ".jl" extension for the saved file). Indeed, if we print the file we can confirm this: +Here, `include` is used to execute the Julia code from any file. This means that `save_reactionsystem` actually saves the model as executable code which re-generates the exact model which was saved (this is the reason why we use the ".jl" extension for the saved file). Indeed, if we print the file we can confirm this: ```@example file_handling_1 -println(read("reaction_network.jl", String)) -rm("reaction_network.jl") # hide +read("cross_coupling.jl", String) ``` +```@example file_handling_1 +rm("cross_coupling.jl") +``` +This functionality can also be used or print a model to simplify checking its various components. ## Loading and Saving arbitrary Julia variables using Serialization.jl Julia provide a general and lightweight interface for loading and saving Julia structures to and from files that it can be good to be aware of. It is called [Serialization.jl](https://docs.julialang.org/en/v1/stdlib/Serialization/) and provides two functions, `serialize` and `deserialize`. The first allow us to write a Julia structure to a file. E.g. if we wish to save a parameter set associated with our model, we can use @@ -42,7 +45,7 @@ loaded_sol = deserialize("saved_parameters.jls") ``` ## [Loading .net files usings ReactionNetworkImporters.jl](@id file_loading_rni_net) -A general-purpose format for storing CRN models are so-called .net files. These can be generated by e.g. [BioNetGen](https://bionetgen.org/). The [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl) package enables the loading of such files to Catalyst `ReactionSystem`. Here we load a [repressilator](https://en.wikipedia.org/wiki/Repressilator) model stored in the "repressilator.net" file: +A general-purpose format for storing CRN models are so-called .net files. These can be generated by e.g. [BioNetGen](https://bionetgen.org/). The [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl) package enables the loading of such files to Catalyst `ReactionSystem`. Here we load a [Repressilator](@ref basic_CRN_library_repressilator) model stored in the "repressilator.net" file: ```@example file_handling_3 using ReactionNetworkImporters cp("../assets/model_files/repressilator.net", "repressilator.net") # hide @@ -50,16 +53,18 @@ prn = loadrxnetwork(BNGNetwork(), "repressilator.net") rm("repressilator.net") # hide prn # hide ``` -Here, .net files not only contain information regarding the reaction network itself, but also the numeric values (initial conditions and parameter values) required for simulating it. Hence, `loadrxnetwork` generates a `ParsedReactionNetwork` structure, containing all this information. You can access the model as `prn.rn`, the initial conditions as `prn.u0`, and the parameter values as `prn.p`. Furthermore, these initial conditions and parameter values are also made [*default* values](@ref ref) of the model. +Here, .net files not only contain information regarding the reaction network itself, but also the numeric values (initial conditions and parameter values) required for simulating it. Hence, `loadrxnetwork` generates a `ParsedReactionNetwork` structure, containing all this information. You can access the model as `prn.rn`, the initial conditions as `prn.u0`, and the parameter values as `prn.p`. Furthermore, these initial conditions and parameter values are also made [*default* values](@ref dsl_advanced_options_default_vals) of the model. -A parsed reaction network can be provided directly to various problem types for simulation. E.g. here we perform an ODE simulation of our repressilator model: +A parsed reaction network's content can then be provided to various problem types for simulation. E.g. here we perform an ODE simulation of our repressilator model: ```@example file_handling_3 using Catalyst, OrdinaryDiffEq, Plots -tspan = (0.0, 1000.0) -oprob = ODEProblem(prn, tspan) +tspan = (0.0, 100.0) +oprob = ODEProblem(prn.rn, Float64[], tspan, Float64[]) sol = solve(oprob) plot(sol) ``` +Note that, as all initial conditions and parameters have default values, we can provide empty vectors for these into our `ODEProblem`. + !!! note It should be noted that .net files supports a wide range of potential model features, not all of which are currently supported by ReactionNetworkImporters. Hence, there might be some .net files which `loadrxnetwork` will not be able to load. @@ -69,7 +74,7 @@ A more detailed description of ReactionNetworkImporter's features can be found i ## Loading SBML files using SBMLImporter.jl and SBMLToolkit.jl The Systems Biology Markup Language (SBML) is the most widespread format for representing CRN models. Currently, there exists two different Julia packages, [SBMLImporter.jl](https://github.com/sebapersson/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), both of which are able to load SBML files to Catalyst `ReactionSystem` structures. SBML is able to represent a *very* wide range of model featuresSome of these are not supported by these packages (or Catalyst). Hence, there exist SBML files (typically containing obscure model features such as events with time delays) that currently cannot be loaded into Catalyst models. -SBMLImporter's `load_SBML` function can be used to load SBML files. Here, we load a [Brusselator](https://en.wikipedia.org/wiki/Brusselator) model stored in the "brusselator.xml" file: +SBMLImporter's `load_SBML` function can be used to load SBML files. Here, we load a [Brusselator](@ref basic_CRN_library_brusselator) model stored in the "brusselator.xml" file: ```@example file_handling_4 using SBMLImporter cp("../assets/model_files/brusselator.xml", "brusselator.xml") # hide @@ -81,8 +86,8 @@ Here, while [ReactionNetworkImporters generates a `ParsedReactionSystem` only](@ ```@example file_handling_4 using Catalyst, OrdinaryDiffEq, Plots tspan = (0.0, 1000.0) -oprob = ODEProblem(prn, tspan) -sol = solve(oprob; cb=cbs) +oprob = ODEProblem(prn.rn, Float64[], tspan, Float64[]) +sol = solve(oprob; callback = cbs) plot(sol) ``` From 8d7c79f95197d7edbacf70202ac91da135501903 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jun 2024 15:01:46 -0400 Subject: [PATCH 132/446] up --- .../assets/brusselator_sim_SBMLImporter.svg | 50 +++++++ ...essilator_sim_ReactionNetworkImporters.svg | 52 ++++++++ .../model_file_loading_and_export.md | 125 +++++++++++++----- 3 files changed, 193 insertions(+), 34 deletions(-) create mode 100644 docs/src/assets/brusselator_sim_SBMLImporter.svg create mode 100644 docs/src/assets/repressilator_sim_ReactionNetworkImporters.svg diff --git a/docs/src/assets/brusselator_sim_SBMLImporter.svg b/docs/src/assets/brusselator_sim_SBMLImporter.svg new file mode 100644 index 0000000000..ea99a21681 --- /dev/null +++ b/docs/src/assets/brusselator_sim_SBMLImporter.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/repressilator_sim_ReactionNetworkImporters.svg b/docs/src/assets/repressilator_sim_ReactionNetworkImporters.svg new file mode 100644 index 0000000000..adb9b1262c --- /dev/null +++ b/docs/src/assets/repressilator_sim_ReactionNetworkImporters.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/model_creation/model_file_loading_and_export.md b/docs/src/model_creation/model_file_loading_and_export.md index 1d32293587..9812ab97f0 100644 --- a/docs/src/model_creation/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -1,14 +1,14 @@ # Loading Chemical Reaction Network Models from Files -Catalyst stores chemical reaction network (CRN) models in `ReactionSystem` structures. This tutorial describes how to load such `ReactionSystem`s from, and save them to, files. This can be used to save models between Julia sessions, or transfer them from one session to another. Furthermore, to facilitate the computation modelling of CRNs, several standardised file formats have been created to represent CRN models (e.g. [SBML](https://sbml.org/)). These enables CRN models to be shared between different softwares and programming languages. While Catalyst itself does not have the functionality for loading such files, we will here (briefly) introduce a few packages that can load files to Catalyst `ReactionSystem`s. +Catalyst stores chemical reaction network (CRN) models in `ReactionSystem` structures. This tutorial describes how to load such `ReactionSystem`s from, and save them to, files. This can be used to save models between Julia sessions, or transfer them from one session to another. Furthermore, to facilitate the computation modelling of CRNs, several standardised file formats have been created to represent CRN models (e.g. [SBML](https://sbml.org/)). This enables CRN models to be shared between different softwares and programming languages. While Catalyst itself does not have the functionality for loading such files, we will here (briefly) introduce a few packages that can load different file types to Catalyst `ReactionSystem`s. ## Saving Catalyst models to, and loading them from, Julia files Catalyst provides a `save_reactionsystem` function, enabling the user to save a `ReactionSystem` to a file. Here we demonstrate this by first creating a [simple cross-coupling model](@ref basic_CRN_library_cc): ```@example file_handling_1 using Catalyst -rn = @reaction_network begin - kB, S + E --> SE - kD, SE --> S + E - kP, SE --> P + E +cc_system = @reaction_network begin + k₁, S₁ + C --> S₁C + k₂, S₁C + S₂ --> CP + k₃, CP --> C + P end ``` and next saving it to a file @@ -18,78 +18,101 @@ save_reactionsystem("cross_coupling.jl", rn) Here, `save_reactionsystem`'s first argument is the path to the file where we wish to save it. The second argument is the `ReactionSystem` we wish to save. To load the file, we use Julia's `include` function: ```@example file_handling_1 loaded_rn = include("cross_coupling.jl") +rm("cross_coupling.jl") # hide +loaded_rn # hide ``` !!! note - The destination file can be in a folder. E.g. `save_reactionsystem("my_folder/reaction_network.jl", rn)` saves the model to the file "reaction_network.jl" in the folder "my_folder". + The destination file can be in a folder. E.g. `save_reactionsystem("my\_folder/reaction_network.jl", rn)` saves the model to the file "reaction\_network.jl" in the folder "my_folder". -Here, `include` is used to execute the Julia code from any file. This means that `save_reactionsystem` actually saves the model as executable code which re-generates the exact model which was saved (this is the reason why we use the ".jl" extension for the saved file). Indeed, if we print the file we can confirm this: -```@example file_handling_1 -read("cross_coupling.jl", String) +Here, `include` is used to execute the Julia code from any file. This means that `save_reactionsystem` actually saves the model as executable code which re-generates the exact model which was saved (this is the reason why we use the ".jl" extension for the saved file). Indeed, we can confirm this if we check what is printed in the file: ``` -```@example file_handling_1 -rm("cross_coupling.jl") +let + +# Independent variable: +@variables t + +# Parameters: +ps = @parameters kB kD kP + +# Species: +sps = @species S(t) E(t) SE(t) P(t) + +# Reactions: +rxs = [ + Reaction(kB, [S, E], [SE], [1, 1], [1]), + Reaction(kD, [SE], [S, E], [1], [1, 1]), + Reaction(kP, [SE], [P, E], [1], [1, 1]) +] + +# Declares ReactionSystem model: +rs = ReactionSystem(rxs, t, sps, ps; name = Symbol("##ReactionSystem#12592")) +complete(rs) + +end ``` -This functionality can also be used or print a model to simplify checking its various components. +!!! note + The code that `save_reactionsystem` prints uses [programmatic modelling](@ref ref) to generate the written model. + +In addition to transferring models between Julia sessions, the `save_reactionsystem` function can also be used or print a model to a text file where you can easily inspect its components. ## Loading and Saving arbitrary Julia variables using Serialization.jl -Julia provide a general and lightweight interface for loading and saving Julia structures to and from files that it can be good to be aware of. It is called [Serialization.jl](https://docs.julialang.org/en/v1/stdlib/Serialization/) and provides two functions, `serialize` and `deserialize`. The first allow us to write a Julia structure to a file. E.g. if we wish to save a parameter set associated with our model, we can use +Julia provides a general and lightweight interface for loading and saving Julia structures to and from files that it can be good to be aware of. It is called [Serialization.jl](https://docs.julialang.org/en/v1/stdlib/Serialization/) and provides two functions, `serialize` and `deserialize`. The first allows us to write a Julia structure to a file. E.g. if we wish to save a parameter set associated with our model, we can use ```@example file_handling_2 using Serialization -ps = [:kB => 1.0, :kD => 0.1, :kP => 2.0] +ps = [:k₁ => 1.0, :k₂ => 0.1, :k₃ => 2.0] serialize("saved_parameters.jls", ps) ``` -Here, we use the extension ".jls" (standing for **J**u**L**ia **S**erialization), however, any can be used. To load a structure, we can then use +Here, we use the extension ".jls" (standing for **J**u**L**ia **S**erialization), however, any extension code can be used. To load a structure, we can then use ```@example file_handling_2 loaded_sol = deserialize("saved_parameters.jls") ``` -## [Loading .net files usings ReactionNetworkImporters.jl](@id file_loading_rni_net) -A general-purpose format for storing CRN models are so-called .net files. These can be generated by e.g. [BioNetGen](https://bionetgen.org/). The [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl) package enables the loading of such files to Catalyst `ReactionSystem`. Here we load a [Repressilator](@ref basic_CRN_library_repressilator) model stored in the "repressilator.net" file: -```@example file_handling_3 +## [Loading .net files using ReactionNetworkImporters.jl](@id file_loading_rni_net) +A general-purpose format for storing CRN models is so-called .net files. These can be generated by e.g. [BioNetGen](https://bionetgen.org/). The [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl) package enables the loading of such files to Catalyst `ReactionSystem`. Here we load a [Repressilator](@ref basic_CRN_library_repressilator) model stored in the "repressilator.net" file: +```julia using ReactionNetworkImporters -cp("../assets/model_files/repressilator.net", "repressilator.net") # hide prn = loadrxnetwork(BNGNetwork(), "repressilator.net") -rm("repressilator.net") # hide -prn # hide ``` Here, .net files not only contain information regarding the reaction network itself, but also the numeric values (initial conditions and parameter values) required for simulating it. Hence, `loadrxnetwork` generates a `ParsedReactionNetwork` structure, containing all this information. You can access the model as `prn.rn`, the initial conditions as `prn.u0`, and the parameter values as `prn.p`. Furthermore, these initial conditions and parameter values are also made [*default* values](@ref dsl_advanced_options_default_vals) of the model. A parsed reaction network's content can then be provided to various problem types for simulation. E.g. here we perform an ODE simulation of our repressilator model: -```@example file_handling_3 +```julia using Catalyst, OrdinaryDiffEq, Plots -tspan = (0.0, 100.0) +tspan = (0.0, 10000.0) oprob = ODEProblem(prn.rn, Float64[], tspan, Float64[]) sol = solve(oprob) -plot(sol) +plot(sol; idxs = [:mTetR, :mLacI, :mCI]) ``` +![Repressilator Simulation](../assets/repressilator_sim_ReactionNetworkImporters.svg) + Note that, as all initial conditions and parameters have default values, we can provide empty vectors for these into our `ODEProblem`. !!! note - It should be noted that .net files supports a wide range of potential model features, not all of which are currently supported by ReactionNetworkImporters. Hence, there might be some .net files which `loadrxnetwork` will not be able to load. + It should be noted that .net files support a wide range of potential model features, not all of which are currently supported by ReactionNetworkImporters. Hence, there might be some .net files which `loadrxnetwork` will not be able to load. A more detailed description of ReactionNetworkImporter's features can be found in its [documentation](https://docs.sciml.ai/ReactionNetworkImporters/stable/). ## Loading SBML files using SBMLImporter.jl and SBMLToolkit.jl -The Systems Biology Markup Language (SBML) is the most widespread format for representing CRN models. Currently, there exists two different Julia packages, [SBMLImporter.jl](https://github.com/sebapersson/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), both of which are able to load SBML files to Catalyst `ReactionSystem` structures. SBML is able to represent a *very* wide range of model featuresSome of these are not supported by these packages (or Catalyst). Hence, there exist SBML files (typically containing obscure model features such as events with time delays) that currently cannot be loaded into Catalyst models. +The Systems Biology Markup Language (SBML) is the most widespread format for representing CRN models. Currently, there exist two different Julia packages, [SBMLImporter.jl](https://github.com/sebapersson/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), that are able to load SBML files to Catalyst `ReactionSystem` structures. SBML is able to represent a *very* wide range of model features. Some of these are not supported by these packages (or Catalyst). Hence, there exist SBML files (typically containing obscure model features such as events with time delays) that currently cannot be loaded into Catalyst models). SBMLImporter's `load_SBML` function can be used to load SBML files. Here, we load a [Brusselator](@ref basic_CRN_library_brusselator) model stored in the "brusselator.xml" file: -```@example file_handling_4 +```julia using SBMLImporter -cp("../assets/model_files/brusselator.xml", "brusselator.xml") # hide prn, cbs = load_SBML("brusselator.xml") -rm("brusselator.xml") # hide -prn, cbs # hide ``` Here, while [ReactionNetworkImporters generates a `ParsedReactionSystem` only](@ref file_loading_rni_net), SBMLImporter generates a `ParsedReactionSystem` (here stored in `prn`) and a [so-called `CallbackSet`](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/#CallbackSet) (here stored in `cbs`). While `prn` can be used to create various problems, when we simulate them, we must also supply `cbs`. E.g. to simulate our brusselator we use: -```@example file_handling_4 +```julia using Catalyst, OrdinaryDiffEq, Plots -tspan = (0.0, 1000.0) -oprob = ODEProblem(prn.rn, Float64[], tspan, Float64[]) +tspan = (0.0, 50.0) +oprob = ODEProblem(prn.rn, prn.u0, tspan, prn.p) sol = solve(oprob; callback = cbs) plot(sol) ``` +![Brusselator Simulation](../assets/brusselator_sim_SBMLImporter.svg) + +Note that, while ReactionNetworkImporters adds initial condition and species values as default to the imported model, SBMLImporter does not do this. These must hence be provided to the `ODEProblem` directly. A more detailed description of SBMLImporter's features can be found in its [documentation](https://docs.sciml.ai/ReactionNetworkImporters/stable/). @@ -103,4 +126,38 @@ While CRN models can be represented through various file formats, they can also Or - An $mxn$ complex stoichiometric matrix (...) and a $2mxn$ incidence matrix (...). -The advantage with these forms is that they offer a compact and very general way to represent a large class of CRNs. ReactionNetworkImporters have the functionality for converting matrices of these forms directly into Catalyst `ReactionSystem` models. Instructions on how to do this are available in [ReactionNetworkImporter's documentation](https://docs.sciml.ai/ReactionNetworkImporters/stable/#Loading-a-matrix-representation). \ No newline at end of file +The advantage of these forms is that they offer a compact and very general way to represent a large class of CRNs. ReactionNetworkImporters have the functionality for converting matrices of these forms directly into Catalyst `ReactionSystem` models. Instructions on how to do this are available in [ReactionNetworkImporter's documentation](https://docs.sciml.ai/ReactionNetworkImporters/stable/#Loading-a-matrix-representation). + + +--- +## [Citations](@id petab_citations) +If you use any of this functionality in your research, [in addition to Catalyst](@ref catalyst_citation), please cite the paper(s) corresponding to whichever package(s) you used: +``` +@software{2022ReactionNetworkImporters, + author = {Isaacson, Samuel}, + title = {{ReactionNetworkImporters.jl}}, + howpublished = {\url{https://github.com/SciML/ReactionNetworkImporters.jl}}, + year = {2022} +} +``` +``` +@software{2024SBMLImporter, + author = {Persson, Sebastian}, + title = {{SBMLImporter.jl}}, + howpublished = {\url{https://github.com/sebapersson/SBMLImporter.jl}}, + year = {2024} +} +``` +``` +@article{LangJainRackauckas+2024, + url = {https://doi.org/10.1515/jib-2024-0003}, + title = {SBMLToolkit.jl: a Julia package for importing SBML into the SciML ecosystem}, + title = {}, + author = {Paul F. Lang and Anand Jain and Christopher Rackauckas}, + pages = {20240003}, + journal = {Journal of Integrative Bioinformatics}, + doi = {doi:10.1515/jib-2024-0003}, + year = {2024}, + lastchecked = {2024-06-02} +} +``` \ No newline at end of file From d782065cbbaec19fcb8df0120cf557911783fbce Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jun 2024 09:32:45 -0400 Subject: [PATCH 133/446] up --- .../model_creation/model_file_loading_and_export.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/src/model_creation/model_file_loading_and_export.md b/docs/src/model_creation/model_file_loading_and_export.md index 9812ab97f0..ea869a193a 100644 --- a/docs/src/model_creation/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -95,12 +95,12 @@ Note that, as all initial conditions and parameters have default values, we can A more detailed description of ReactionNetworkImporter's features can be found in its [documentation](https://docs.sciml.ai/ReactionNetworkImporters/stable/). ## Loading SBML files using SBMLImporter.jl and SBMLToolkit.jl -The Systems Biology Markup Language (SBML) is the most widespread format for representing CRN models. Currently, there exist two different Julia packages, [SBMLImporter.jl](https://github.com/sebapersson/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), that are able to load SBML files to Catalyst `ReactionSystem` structures. SBML is able to represent a *very* wide range of model features. Some of these are not supported by these packages (or Catalyst). Hence, there exist SBML files (typically containing obscure model features such as events with time delays) that currently cannot be loaded into Catalyst models). +The Systems Biology Markup Language (SBML) is the most widespread format for representing CRN models. Currently, there exist two different Julia packages, [SBMLImporter.jl](https://github.com/sebapersson/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), that are able to load SBML files to Catalyst `ReactionSystem` structures. SBML is able to represent a *very* wide range of model features, with both packages supporting most features. However, there exist SBML files (typically containing obscure model features such as events with time delays) that currently cannot be loaded into Catalyst models. SBMLImporter's `load_SBML` function can be used to load SBML files. Here, we load a [Brusselator](@ref basic_CRN_library_brusselator) model stored in the "brusselator.xml" file: ```julia using SBMLImporter -prn, cbs = load_SBML("brusselator.xml") +prn, cbs = load_SBML("brusselator.xml", massaction = true) ``` Here, while [ReactionNetworkImporters generates a `ParsedReactionSystem` only](@ref file_loading_rni_net), SBMLImporter generates a `ParsedReactionSystem` (here stored in `prn`) and a [so-called `CallbackSet`](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/#CallbackSet) (here stored in `cbs`). While `prn` can be used to create various problems, when we simulate them, we must also supply `cbs`. E.g. to simulate our brusselator we use: ```julia @@ -114,10 +114,13 @@ plot(sol) Note that, while ReactionNetworkImporters adds initial condition and species values as default to the imported model, SBMLImporter does not do this. These must hence be provided to the `ODEProblem` directly. -A more detailed description of SBMLImporter's features can be found in its [documentation](https://docs.sciml.ai/ReactionNetworkImporters/stable/). +A more detailed description of SBMLImporter's features can be found in its [documentation](https://sebapersson.github.io/SBMLImporter.jl/stable/). + +!!! note + The `massaction = true` option informs the importer that the target model follows mass-action principles. When given, this enables SBMLImporter to make appropriate modification to the model (which are important for e.g. jump simulation performance). ### SBMLImporter and SBMLToolkit -Above, we described how to use SBMLImporter to import SBML files. Alternatively, SBMLToolkit can be used instead. It has a slightly different syntax, which is described in its [documentation](https://github.com/SciML/SBMLToolkit.jl). A short comparison of the two packages can be found [here](https://github.com/sebapersson/SBMLImporter.jl?tab=readme-ov-file#differences-compared-to-sbmltoolkit). Generally, while they both perform well, we note that for *jump simulations* SBMLImporter is preferable (its way for internally representing reaction event enables more performant jump simulations). +Above, we described how to use SBMLImporter to import SBML files. Alternatively, SBMLToolkit can be used instead. It has a slightly different syntax, which is described in its [documentation](https://github.com/SciML/SBMLToolkit.jl), and does not support as wide range of SBML features as SBMLImporter. A short comparison of the two packages can be found [here](https://github.com/sebapersson/SBMLImporter.jl?tab=readme-ov-file#differences-compared-to-sbmltoolkit). Generally, while they both perform well, we note that for *jump simulations* SBMLImporter is preferable (its way for internally representing reaction event enables more performant jump simulations). ## Loading models from matrix representation using ReactionNetworkImporters.jl While CRN models can be represented through various file formats, they can also be represented in various matrix forms. E.g. a CRN with $m$ species and $n$ reactions (and with constant rates) can be represented with either From f952cf8b7882665b0ed123a3dccd59b33c75a460 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jun 2024 09:33:50 -0400 Subject: [PATCH 134/446] spelling fix --- docs/src/model_creation/model_file_loading_and_export.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/model_creation/model_file_loading_and_export.md b/docs/src/model_creation/model_file_loading_and_export.md index ea869a193a..bcc5e4646c 100644 --- a/docs/src/model_creation/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -120,7 +120,7 @@ A more detailed description of SBMLImporter's features can be found in its [docu The `massaction = true` option informs the importer that the target model follows mass-action principles. When given, this enables SBMLImporter to make appropriate modification to the model (which are important for e.g. jump simulation performance). ### SBMLImporter and SBMLToolkit -Above, we described how to use SBMLImporter to import SBML files. Alternatively, SBMLToolkit can be used instead. It has a slightly different syntax, which is described in its [documentation](https://github.com/SciML/SBMLToolkit.jl), and does not support as wide range of SBML features as SBMLImporter. A short comparison of the two packages can be found [here](https://github.com/sebapersson/SBMLImporter.jl?tab=readme-ov-file#differences-compared-to-sbmltoolkit). Generally, while they both perform well, we note that for *jump simulations* SBMLImporter is preferable (its way for internally representing reaction event enables more performant jump simulations). +Above, we described how to use SBMLImporter to import SBML files. Alternatively, SBMLToolkit can be used instead. It has a slightly different syntax, which is described in its [documentation](https://github.com/SciML/SBMLToolkit.jl), and does not support as wide a range of SBML features as SBMLImporter. A short comparison of the two packages can be found [here](https://github.com/sebapersson/SBMLImporter.jl?tab=readme-ov-file#differences-compared-to-sbmltoolkit). Generally, while they both perform well, we note that for *jump simulations* SBMLImporter is preferable (its way for internally representing reaction event enables more performant jump simulations). ## Loading models from matrix representation using ReactionNetworkImporters.jl While CRN models can be represented through various file formats, they can also be represented in various matrix forms. E.g. a CRN with $m$ species and $n$ reactions (and with constant rates) can be represented with either From d98b39545a0c109f9092b52c0dbbad213d0e8722 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jun 2024 09:34:26 -0400 Subject: [PATCH 135/446] spelling fix 2 --- docs/src/model_creation/model_file_loading_and_export.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/model_creation/model_file_loading_and_export.md b/docs/src/model_creation/model_file_loading_and_export.md index bcc5e4646c..52c576634d 100644 --- a/docs/src/model_creation/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -117,7 +117,7 @@ Note that, while ReactionNetworkImporters adds initial condition and species val A more detailed description of SBMLImporter's features can be found in its [documentation](https://sebapersson.github.io/SBMLImporter.jl/stable/). !!! note - The `massaction = true` option informs the importer that the target model follows mass-action principles. When given, this enables SBMLImporter to make appropriate modification to the model (which are important for e.g. jump simulation performance). + The `massaction = true` option informs the importer that the target model follows mass-action principles. When given, this enables SBMLImporter to make appropriate modifications to the model (which are important for e.g. jump simulation performance). ### SBMLImporter and SBMLToolkit Above, we described how to use SBMLImporter to import SBML files. Alternatively, SBMLToolkit can be used instead. It has a slightly different syntax, which is described in its [documentation](https://github.com/SciML/SBMLToolkit.jl), and does not support as wide a range of SBML features as SBMLImporter. A short comparison of the two packages can be found [here](https://github.com/sebapersson/SBMLImporter.jl?tab=readme-ov-file#differences-compared-to-sbmltoolkit). Generally, while they both perform well, we note that for *jump simulations* SBMLImporter is preferable (its way for internally representing reaction event enables more performant jump simulations). From 7f2d23427e0c253c6c60fdc68bd041df51d99b2a Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jun 2024 12:04:26 -0400 Subject: [PATCH 136/446] ensure that files written in docs are properly deleted --- docs/src/model_creation/model_file_loading_and_export.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/src/model_creation/model_file_loading_and_export.md b/docs/src/model_creation/model_file_loading_and_export.md index 52c576634d..bcd86167d8 100644 --- a/docs/src/model_creation/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -13,13 +13,13 @@ end ``` and next saving it to a file ```@example file_handling_1 -save_reactionsystem("cross_coupling.jl", rn) +save_reactionsystem("cross_coupling.jl", cc_system) ``` Here, `save_reactionsystem`'s first argument is the path to the file where we wish to save it. The second argument is the `ReactionSystem` we wish to save. To load the file, we use Julia's `include` function: ```@example file_handling_1 -loaded_rn = include("cross_coupling.jl") +cc_loaded = include("cross_coupling.jl") rm("cross_coupling.jl") # hide -loaded_rn # hide +cc_loaded # hide ``` !!! note @@ -66,6 +66,8 @@ serialize("saved_parameters.jls", ps) Here, we use the extension ".jls" (standing for **J**u**L**ia **S**erialization), however, any extension code can be used. To load a structure, we can then use ```@example file_handling_2 loaded_sol = deserialize("saved_parameters.jls") +rm("saved_parameters.jls") # hide +loaded_sol # hide ``` ## [Loading .net files using ReactionNetworkImporters.jl](@id file_loading_rni_net) From dd78f9b83ff415f8067364c47ddabf98ea4fe9d1 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 18 May 2024 10:42:14 -0400 Subject: [PATCH 137/446] init --- docs/pages.jl | 2 +- .../sde_simulation_performance.md | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 docs/src/model_simulation/sde_simulation_performance.md diff --git a/docs/pages.jl b/docs/pages.jl index 2572bcb69b..baa0b51775 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -32,7 +32,7 @@ pages = Any[ "model_simulation/ensemble_simulations.md", # Stochastic simulation statistical analysis. "model_simulation/ode_simulation_performance.md", - # SDE Performance considerations/advice. + "model_simulation/sde_simulation_performance.md", # Jump Performance considerations/advice. # Finite state projection ], diff --git a/docs/src/model_simulation/sde_simulation_performance.md b/docs/src/model_simulation/sde_simulation_performance.md new file mode 100644 index 0000000000..1d697f2d59 --- /dev/null +++ b/docs/src/model_simulation/sde_simulation_performance.md @@ -0,0 +1,18 @@ +# [Advice for performant SDE simulations](@id sde_simulation_performance) +While there exist relatively straightforward approaches to improve performance for [ODE](@ref ref) and [Jump](@ref ref) simulations, this is generally not the case for SDE simulations. Below, we briefly describe some options. However, as one starts to investigate these, one quickly reaches what is (or could be) active areas of research. + +## [SDE solver selection](@id sde_simulation_performance_solvers) +We have previously described [how ODE solver selection](@ref ref) can impact simulation performance. Again, it can be worthwhile to investigate this for SDE simulations. Throughout this documentation, we generally use the `STrapezoid` solver as the default choice. However, if the `DifferentialEquations` package is loaded +```julia +using DifferentialEquations +``` +automatic SDE solver selection is automatically enabled (just like is the case for ODEs by default). Generally, the automatic SDe solver choice enabled by `DifferentialEquations` is better than just using `STrapezoid`. Next, if performance is critical, it can be worthwhile to check the [list of available SDE solvers](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) to find one with advantageous performance for a given problem. When doing so, it is important to pick a solver compatible with *non-diagonal noise* and with [*Ito problems*](https://en.wikipedia.org/wiki/It%C3%B4_calculus). + +## [Options for Jacobian computation](@id sde_simulation_performance_jacobian) +In [the section on ODE simulation performance](@ref ref) we describe various options for computing the system Jacobian, and how these could be used to improve performance for [implicit solvers](@ref ref). These can be used in tandem with implicit SDE solvers (such as `ImplicitEM`). However, due to additional considerations during SDE simulations, it is much less certain whether these will actually have any impact on performance. So while these options might be worth reading about and trialling, there is no guarantee that they will be beneficial. + +## [Parallelisation on CPUs and GPUs](@id sde_simulation_performance_parallelisation) +We have previously described how simulation parallelisation can be used to [improve performance when multiple ODE simulations are carried out](@ref ref). The same approaches can be used for SDE simulations. Indeed, it is often more relevant for SDEs, as these often are re-simulated using identical simulation conditions (to investigate their typical behaviour across many samples). CPU parallelisation of SDE simulations uses the same approach as for ODEs. For GPU parallelisation we + +### [Multilevel Monte Carlo](@id sde_simulation_performance_parallelisation_mlmc) +An approach for speeding up parallel stochastic simulations are so-called [*multilevel Monte Carlo approaches*](https://en.wikipedia.org/wiki/Multilevel_Monte_Carlo_method) (MLMC). These are used when a stochastic process is simulated repeatedly using identical simulation conditions. Here, instead of performing all simulations using identical tolerance, the ensemble is simulated using a range of tolerances (primarily lower ones, which yields faster simulations). Currently, [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) do not have a native implementation for performing MLMC simulations (this will hopefully be added in the future). However, if high performance of parallel SDE simulations is required, these approaches may be worth investigating. \ No newline at end of file From 3b583c98d28413ee529da3dbf155db22f928f061 Mon Sep 17 00:00:00 2001 From: vydu Date: Sun, 12 May 2024 00:07:15 -0400 Subject: [PATCH 138/446] Implemented complex balance checking --- src/Project.toml | 2 + src/networkapi.jl | 1801 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1803 insertions(+) create mode 100644 src/Project.toml create mode 100644 src/networkapi.jl diff --git a/src/Project.toml b/src/Project.toml new file mode 100644 index 0000000000..e7b949696b --- /dev/null +++ b/src/Project.toml @@ -0,0 +1,2 @@ +[deps] +Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83" diff --git a/src/networkapi.jl b/src/networkapi.jl new file mode 100644 index 0000000000..f0cc164856 --- /dev/null +++ b/src/networkapi.jl @@ -0,0 +1,1801 @@ + +######### Accessors: ######### + +function filter_nonrxsys(network) + systems = get_systems(network) + rxsystems = ReactionSystem[] + for sys in systems + (sys isa ReactionSystem) && push!(rxsystems, sys) + end + rxsystems +end + +function species(network::ReactionSystem, sts) + [MT.renamespace(network, st) for st in sts] +end + +""" + species(network) + +Given a [`ReactionSystem`](@ref), return a vector of all species defined in the system and +any subsystems that are of type `ReactionSystem`. To get the species and non-species +variables in the system and all subsystems, including non-`ReactionSystem` subsystems, uses +`states(network)`. + +Notes: +- If `ModelingToolkit.get_systems(network)` is non-empty will allocate. +""" +function species(network) + sts = get_species(network) + systems = filter_nonrxsys(network) + isempty(systems) && return sts + unique([sts; reduce(vcat, map(sys -> species(sys, species(sys)), systems))]) +end + +""" + nonspecies(network) + +Return the non-species variables within the network, i.e. those states for which `isspecies +== false`. + +Notes: +- Allocates a new array to store the non-species variables. +""" +function nonspecies(network) + states(network)[(numspecies(network) + 1):end] +end + +""" + reactionparams(network) + +Given a [`ReactionSystem`](@ref), return a vector of all parameters defined +within the system and any subsystems that are of type `ReactionSystem`. To get +the parameters in the system and all subsystems, including non-`ReactionSystem` +subsystems, use `parameters(network)`. + +Notes: +- Allocates and has to calculate these dynamically by comparison for each reaction. +""" +function reactionparams(network) + ps = get_ps(network) + systems = filter_nonrxsys(network) + isempty(systems) && return ps + unique([ps; reduce(vcat, map(sys -> species(sys, reactionparams(sys)), systems))]) +end + +""" + numparams(network) + +Return the total number of parameters within the given system and all subsystems. +""" +function numparams(network) + nps = length(get_ps(network)) + for sys in get_systems(network) + nps += numparams(sys) + end + nps +end + +function namespace_reactions(network::ReactionSystem) + rxs = reactions(network) + isempty(rxs) && return Reaction[] + map(rx -> namespace_equation(rx, network), rxs) +end + +""" + reactions(network) + +Given a [`ReactionSystem`](@ref), return a vector of all `Reactions` in the system. + +Notes: +- If `ModelingToolkit.get_systems(network)` is not empty, will allocate. +""" +function reactions(network) + rxs = get_rxs(network) + systems = filter_nonrxsys(network) + isempty(systems) && (return rxs) + [rxs; reduce(vcat, namespace_reactions.(systems); init = Reaction[])] +end + +""" + speciesmap(network) + +Given a [`ReactionSystem`](@ref), return a Dictionary mapping species that +participate in `Reaction`s to their index within [`species(network)`](@ref). +""" +function speciesmap(network) + nps = get_networkproperties(network) + if isempty(nps.speciesmap) + nps.speciesmap = Dict(S => i for (i, S) in enumerate(species(network))) + end + nps.speciesmap +end + +""" + paramsmap(network) + +Given a [`ReactionSystem`](@ref), return a Dictionary mapping from all +parameters that appear within the system to their index within +`parameters(network)`. +""" +function paramsmap(network) + Dict(p => i for (i, p) in enumerate(parameters(network))) +end + +""" + reactionparamsmap(network) + +Given a [`ReactionSystem`](@ref), return a Dictionary mapping from parameters that +appear within `Reaction`s to their index within [`reactionparams(network)`](@ref). +""" +function reactionparamsmap(network) + Dict(p => i for (i, p) in enumerate(reactionparams(network))) +end + +""" + numspecies(network) + +Return the total number of species within the given [`ReactionSystem`](@ref) and +subsystems that are `ReactionSystem`s. +""" +function numspecies(network) + numspcs = length(get_species(network)) + for sys in get_systems(network) + (sys isa ReactionSystem) && (numspcs += numspecies(sys)) + end + numspcs +end + +""" + numreactions(network) + +Return the total number of reactions within the given [`ReactionSystem`](@ref) +and subsystems that are `ReactionSystem`s. +""" +function numreactions(network) + nr = length(get_rxs(network)) + for sys in get_systems(network) + (sys isa ReactionSystem) && (nr += numreactions(sys)) + end + nr +end + +""" + numreactionparams(network) + +Return the total number of parameters within the given [`ReactionSystem`](@ref) +and subsystems that are `ReactionSystem`s. + +Notes +- If there are no subsystems this will be fast. +- As this calls [`reactionparams`](@ref), it can be slow and will allocate if + there are any subsystems. +""" +function numreactionparams(network) + length(reactionparams(network)) +end + +""" + dependents(rx, network) + +Given a [`Reaction`](@ref) and a [`ReactionSystem`](@ref), return a vector of the +*non-constant* species and variables the reaction rate law depends on. e.g., for + +`k*W, 2X + 3Y --> 5Z + W` + +the returned vector would be `[W(t),X(t),Y(t)]`. + +Notes: +- Allocates +- Does not check for dependents within any subsystems. +- Constant species are not considered dependents since they are internally treated as + parameters. +- If the rate expression depends on a non-species state variable that will be included in + the dependents, i.e. in + ```julia + @parameters k + @variables t V(t) + @species A(t) B(t) C(t) + rx = Reaction(k*V, [A, B], [C]) + @named rs = ReactionSystem([rx], t) + issetequal(dependents(rx, rs), [A,B,V]) == true + ``` +""" +function dependents(rx, network) + if rx.rate isa Number + return rx.substrates + else + rvars = get_variables(rx.rate, states(network)) + return union!(rvars, rx.substrates) + end +end + +""" + dependents(rx, network) + +See documentation for [`dependents`](@ref). +""" +function dependants(rx, network) + dependents(rx, network) +end + +""" + reactionrates(network) + +Given a [`ReactionSystem`](@ref), returns a vector of the symbolic reaction +rates for each reaction. +""" +function reactionrates(rn) + [r.rate for r in reactions(rn)] +end + +""" + substoichmat(rn; sparse=false) + +Returns the substrate stoichiometry matrix, ``S``, with ``S_{i j}`` the stoichiometric +coefficient of the ith substrate within the jth reaction. + +Note: +- Set sparse=true for a sparse matrix representation +- Note that constant species are not considered substrates, but just components that modify + the associated rate law. +""" +function substoichmat(::Type{SparseMatrixCSC{T, Int}}, + rn::ReactionSystem) where {T <: Number} + Is = Int[] + Js = Int[] + Vs = T[] + smap = speciesmap(rn) + for (k, rx) in enumerate(reactions(rn)) + stoich = rx.substoich + for (i, sub) in enumerate(rx.substrates) + isconstant(sub) && continue + push!(Js, k) + push!(Is, smap[sub]) + push!(Vs, stoich[i]) + end + end + sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) +end +function substoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} + smap = speciesmap(rn) + smat = zeros(T, numspecies(rn), numreactions(rn)) + for (k, rx) in enumerate(reactions(rn)) + stoich = rx.substoich + for (i, sub) in enumerate(rx.substrates) + isconstant(sub) && continue + smat[smap[sub], k] = stoich[i] + end + end + smat +end +function substoichmat(rn::ReactionSystem; sparse::Bool = false) + isempty(get_systems(rn)) || error("substoichmat does not currently support subsystems.") + T = reduce(promote_type, eltype(rx.substoich) for rx in reactions(rn)) + (T == Any) && + error("Stoichiometry matrices with symbolic stoichiometry are not supported") + sparse ? substoichmat(SparseMatrixCSC{T, Int}, rn) : substoichmat(Matrix{T}, rn) +end + +""" + prodstoichmat(rn; sparse=false) + +Returns the product stoichiometry matrix, ``P``, with ``P_{i j}`` the stoichiometric +coefficient of the ith product within the jth reaction. + +Note: +- Set sparse=true for a sparse matrix representation +- Note that constant species are not treated as products, but just components that modify + the associated rate law. +""" +function prodstoichmat(::Type{SparseMatrixCSC{T, Int}}, + rn::ReactionSystem) where {T <: Number} + Is = Int[] + Js = Int[] + Vs = T[] + smap = speciesmap(rn) + for (k, rx) in enumerate(reactions(rn)) + stoich = rx.prodstoich + for (i, prod) in enumerate(rx.products) + isconstant(prod) && continue + push!(Js, k) + push!(Is, smap[prod]) + push!(Vs, stoich[i]) + end + end + sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) +end +function prodstoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} + smap = speciesmap(rn) + pmat = zeros(T, numspecies(rn), numreactions(rn)) + for (k, rx) in enumerate(reactions(rn)) + stoich = rx.prodstoich + for (i, prod) in enumerate(rx.products) + isconstant(prod) && continue + pmat[smap[prod], k] = stoich[i] + end + end + pmat +end +function prodstoichmat(rn::ReactionSystem; sparse = false) + isempty(get_systems(rn)) || + error("prodstoichmat does not currently support subsystems.") + + T = reduce(promote_type, eltype(rx.prodstoich) for rx in reactions(rn)) + (T == Any) && + error("Stoichiometry matrices with symbolic stoichiometry are not supported") + sparse ? prodstoichmat(SparseMatrixCSC{T, Int}, rn) : prodstoichmat(Matrix{T}, rn) +end + +""" + netstoichmat(rn, sparse=false) + +Returns the net stoichiometry matrix, ``N``, with ``N_{i j}`` the net stoichiometric +coefficient of the ith species within the jth reaction. + +Notes: +- Set sparse=true for a sparse matrix representation +- Caches the matrix internally within `rn` so subsequent calls are fast. +- Note that constant species are not treated as reactants, but just components that modify + the associated rate law. As such they do not contribute to the net stoichiometry matrix. +""" +function netstoichmat(::Type{SparseMatrixCSC{T, Int}}, + rn::ReactionSystem) where {T <: Number} + Is = Int[] + Js = Int[] + Vs = Vector{T}() + smap = speciesmap(rn) + for (k, rx) in pairs(reactions(rn)) + for (spec, coef) in rx.netstoich + isconstant(spec) && continue + push!(Js, k) + push!(Is, smap[spec]) + push!(Vs, coef) + end + end + sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) +end +function netstoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} + smap = speciesmap(rn) + nmat = zeros(T, numspecies(rn), numreactions(rn)) + for (k, rx) in pairs(reactions(rn)) + for (spec, coef) in rx.netstoich + isconstant(spec) && continue + nmat[smap[spec], k] = coef + end + end + nmat +end + +netstoichtype(::Vector{Pair{S, T}}) where {S, T} = T + +function netstoichmat(rn::ReactionSystem; sparse = false) + isempty(get_systems(rn)) || + error("netstoichmat does not currently support subsystems, please create a flattened system before calling.") + + nps = get_networkproperties(rn) + + # if it is already calculated and has the right type + !isempty(nps.netstoichmat) && (sparse == issparse(nps.netstoichmat)) && + (return nps.netstoichmat) + + # identify a common stoichiometry type + T = reduce(promote_type, netstoichtype(rx.netstoich) for rx in reactions(rn)) + (T == Any) && + error("Stoichiometry matrices are not supported with symbolic stoichiometry.") + + if sparse + nsmat = netstoichmat(SparseMatrixCSC{T, Int}, rn) + else + nsmat = netstoichmat(Matrix{T}, rn) + end + + # only cache if it is integer + if T == Int + nps.netstoichmat = nsmat + end + + nsmat +end + +# the following function is adapted from SymbolicUtils.jl v.19 +# later on (Spetember 2023) modified by Torkel and Shashi (now assumes input not on polynomial form, which is handled elsewhere, previous version failed in these cases anyway). +# Copyright (c) 2020: Shashi Gowda, Yingbo Ma, Mason Protter, Julia Computing. +# MIT license +""" + to_multivariate_poly(polyeqs::AbstractVector{BasicSymbolic{Real}}) + +Convert the given system of polynomial equations to multivariate polynomial representation. +For example, this can be used in HomotopyContinuation.jl functions. +""" +function to_multivariate_poly(polyeqs::AbstractVector{Symbolics.BasicSymbolic{Real}}) + @assert length(polyeqs)>=1 "At least one expression must be passed to `multivariate_poly`." + + pvar2sym, sym2term = SymbolicUtils.get_pvar2sym(), SymbolicUtils.get_sym2term() + ps = map(polyeqs) do x + if istree(x) && operation(x) == (/) + error("We should not be able to get here, please contact the package authors.") + else + PolyForm(x, pvar2sym, sym2term).p + end + end + + ps +end +""" + setdefaults!(rn, newdefs) + +Sets the default (initial) values of parameters and species in the +`ReactionSystem`, `rn`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +setdefaults!(sir, [:S => 999.0, :I => 1.0, :R => 1.0, :β => 1e-4, :ν => .01]) + +# or +@parameter β ν +@variables t +@species S(t) I(t) R(t) +setdefaults!(sir, [S => 999.0, I => 1.0, R => 0.0, β => 1e-4, ν => .01]) +``` +gives initial/default values to each of `S`, `I` and `β` + +Notes: +- Can not be used to set default values for species, variables or parameters of + subsystems or constraint systems. Either set defaults for those systems + directly, or [`flatten`](@ref) to collate them into one system before setting + defaults. +- Defaults can be specified in any iterable container of symbols to value pairs + or symbolics to value pairs. +""" +function setdefaults!(rn, newdefs) + defs = eltype(newdefs) <: Pair{Symbol} ? symmap_to_varmap(rn, newdefs) : newdefs + rndefs = MT.get_defaults(rn) + for (var, val) in defs + rndefs[value(var)] = value(val) + end + nothing +end + +function __unpacksys(rn) + ex = :(begin end) + for key in keys(get_var_to_name(rn)) + var = MT.getproperty(rn, key, namespace = false) + push!(ex.args, :($key = $var)) + end + ex +end + +""" + @unpacksys sys::ModelingToolkit.AbstractSystem + +Loads all species, variables, parameters, and observables defined in `sys` as +variables within the calling module. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +@unpacksys sir +``` +will load the symbolic variables, `S`, `I`, `R`, `ν` and `β`. + +Notes: +- Can not be used to load species, variables, or parameters of subsystems or + constraints. Either call `@unpacksys` on those systems directly, or + [`flatten`](@ref) to collate them into one system before calling. +- Note that this places symbolic variables within the calling module's scope, so + calling from a function defined in a script or the REPL will still result in + the symbolic variables being defined in the `Main` module. +""" +macro unpacksys(rn) + quote + ex = Catalyst.__unpacksys($(esc(rn))) + Base.eval($(__module__), ex) + end +end + +# convert symbol of the form :sys.a.b.c to a symbolic a.b.c +function _symbol_to_var(sys, sym) + if hasproperty(sys, sym) + var = getproperty(sys, sym, namespace = false) + else + strs = split(String(sym), "₊") # need to check if this should be split of not!!! + if length(strs) > 1 + var = getproperty(sys, Symbol(strs[1]), namespace = false) + for str in view(strs, 2:length(strs)) + var = getproperty(var, Symbol(str), namespace = true) + end + else + throw(ArgumentError("System $(nameof(sys)): variable $sym does not exist")) + end + end + var +end + +""" + symmap_to_varmap(sys, symmap) + +Given a system and map of `Symbol`s to values, generates a map from +corresponding symbolic variables/parameters to the values that can be used to +pass initial conditions and parameter mappings. + +For example, +```julia +sir = @reaction_network sir begin + β, S + I --> 2I + ν, I --> R +end +subsys = @reaction_network subsys begin + k, A --> B +end +@named sys = compose(sir, [subsys]) +``` +gives +``` +Model sys with 3 equations +States (5): + S(t) + I(t) + R(t) + subsys₊A(t) + subsys₊B(t) +Parameters (3): + β + ν + subsys₊k +``` +to specify initial condition and parameter mappings from *symbols* we can use +```julia +symmap = [:S => 1.0, :I => 1.0, :R => 1.0, :subsys₊A => 1.0, :subsys₊B => 1.0] +u0map = symmap_to_varmap(sys, symmap) +pmap = symmap_to_varmap(sys, [:β => 1.0, :ν => 1.0, :subsys₊k => 1.0]) +``` +`u0map` and `pmap` can then be used as input to various problem types. + +Notes: +- Any `Symbol`, `sym`, within `symmap` must be a valid field of `sys`. i.e. + `sys.sym` must be defined. +""" +function symmap_to_varmap(sys, symmap::Tuple) + if all(p -> p isa Pair{Symbol}, symmap) + return ((_symbol_to_var(sys, sym) => val for (sym, val) in symmap)...,) + else # if not all entries map a symbol to value pass through + return symmap + end +end + +function symmap_to_varmap(sys, symmap::AbstractArray{Pair{Symbol, T}}) where {T} + [_symbol_to_var(sys, sym) => val for (sym, val) in symmap] +end + +function symmap_to_varmap(sys, symmap::Dict{Symbol, T}) where {T} + Dict(_symbol_to_var(sys, sym) => val for (sym, val) in symmap) +end + +# don't permute any other types and let varmap_to_vars handle erroring +symmap_to_varmap(sys, symmap) = symmap +#error("symmap_to_varmap requires a Dict, AbstractArray or Tuple to map Symbols to values.") + +######################## reaction complexes and reaction rates ############################### + +""" + reset_networkproperties!(rn::ReactionSystem) + +Clears the cache of various properties (like the netstoichiometry matrix). Use if such +properties need to be recalculated for some reason. +""" +function reset_networkproperties!(rn::ReactionSystem) + reset!(get_networkproperties(rn)) + nothing +end + +# get the species indices and stoichiometry while filtering out constant species. +function filter_constspecs(specs, stoich::AbstractVector{V}, smap) where {V <: Integer} + isempty(specs) && (return Vector{Int}(), Vector{V}()) + + if any(isconstant, specs) + ids = Vector{Int}() + filtered_stoich = Vector{V}() + for (i, s) in enumerate(specs) + if !isconstant(s) + push!(ids, smap[s]) + push!(filtered_stoich, stoich[i]) + end + end + else + ids = map(Base.Fix1(getindex, smap), specs) + filtered_stoich = copy(stoich) + end + ids, filtered_stoich +end + +""" + reactioncomplexmap(rn::ReactionSystem) + +Find each [`ReactionComplex`](@ref) within the specified system, constructing a mapping from +the complex to vectors that indicate which reactions it appears in as substrates and +products. + +Notes: +- Each [`ReactionComplex`](@ref) is mapped to a vector of pairs, with each pair having the + form `reactionidx => ± 1`, where `-1` indicates the complex appears as a substrate and + `+1` as a product in the reaction with integer label `reactionidx`. +- Constant species are ignored as part of a complex. i.e. if species `A` is constant then + the reaction `A + B --> C + D` is considered to consist of the complexes `B` and `C + D`. + Likewise `A --> B` would be treated as the same as `0 --> B`. +""" +function reactioncomplexmap(rn::ReactionSystem) + isempty(get_systems(rn)) || + error("reactioncomplexmap does not currently support subsystems.") + + # check if previously calculated and hence cached + nps = get_networkproperties(rn) + !isempty(nps.complextorxsmap) && return nps.complextorxsmap + complextorxsmap = nps.complextorxsmap + + rxs = reactions(rn) + smap = speciesmap(rn) + numreactions(rn) > 0 || + error("There must be at least one reaction to find reaction complexes.") + for (i, rx) in enumerate(rxs) + subids, substoich = filter_constspecs(rx.substrates, rx.substoich, smap) + subrc = sort!(ReactionComplex(subids, substoich)) + if haskey(complextorxsmap, subrc) + push!(complextorxsmap[subrc], i => -1) + else + complextorxsmap[subrc] = [i => -1] + end + + prodids, prodstoich = filter_constspecs(rx.products, rx.prodstoich, smap) + prodrc = sort!(ReactionComplex(prodids, prodstoich)) + if haskey(complextorxsmap, prodrc) + push!(complextorxsmap[prodrc], i => 1) + else + complextorxsmap[prodrc] = [i => 1] + end + end + complextorxsmap +end + +function reactioncomplexes(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, + complextorxsmap) + complexes = collect(keys(complextorxsmap)) + Is = Int[] + Js = Int[] + Vs = Int[] + for (i, c) in enumerate(complexes) + for (j, σ) in complextorxsmap[c] + push!(Is, i) + push!(Js, j) + push!(Vs, σ) + end + end + B = sparse(Is, Js, Vs, length(complexes), numreactions(rn)) + complexes, B +end +function reactioncomplexes(::Type{Matrix{Int}}, rn::ReactionSystem, complextorxsmap) + complexes = collect(keys(complextorxsmap)) + B = zeros(Int, length(complexes), numreactions(rn)) + for (i, c) in enumerate(complexes) + for (j, σ) in complextorxsmap[c] + B[i, j] = σ + end + end + complexes, B +end + +@doc raw""" + reactioncomplexes(network::ReactionSystem; sparse=false) + +Calculate the reaction complexes and complex incidence matrix for the given +[`ReactionSystem`](@ref). + +Notes: +- returns a pair of a vector of [`ReactionComplex`](@ref)s and the complex incidence matrix. +- An empty [`ReactionComplex`](@ref) denotes the null (∅) state (from reactions like ∅ -> A + or A -> ∅). +- Constant species are ignored in generating a reaction complex. i.e. if A is constant then + A --> B consists of the complexes ∅ and B. +- The complex incidence matrix, ``B``, is number of complexes by number of reactions with +```math +B_{i j} = \begin{cases} +-1, &\text{if the i'th complex is the substrate of the j'th reaction},\\ +1, &\text{if the i'th complex is the product of the j'th reaction},\\ +0, &\text{otherwise.} +\end{cases} +``` +- Set sparse=true for a sparse matrix representation of the incidence matrix +""" +function reactioncomplexes(rn::ReactionSystem; sparse = false) + isempty(get_systems(rn)) || + error("reactioncomplexes does not currently support subsystems.") + nps = get_networkproperties(rn) + if isempty(nps.complexes) || (sparse != issparse(nps.complexes)) + complextorxsmap = reactioncomplexmap(rn) + nps.complexes, nps.incidencemat = if sparse + reactioncomplexes(SparseMatrixCSC{Int, Int}, rn, complextorxsmap) + else + reactioncomplexes(Matrix{Int}, rn, complextorxsmap) + end + end + nps.complexes, nps.incidencemat +end + +""" + incidencemat(rn::ReactionSystem; sparse=false) + +Calculate the incidence matrix of `rn`, see [`reactioncomplexes`](@ref). + +Notes: +- Is cached in `rn` so that future calls, assuming the same sparsity, will also be fast. +""" +incidencemat(rn::ReactionSystem; sparse = false) = reactioncomplexes(rn; sparse)[2] + +function complexstoichmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, rcs) + Is = Int[] + Js = Int[] + Vs = Int[] + for (i, rc) in enumerate(rcs) + for rcel in rc + push!(Is, rcel.speciesid) + push!(Js, i) + push!(Vs, rcel.speciesstoich) + end + end + Z = sparse(Is, Js, Vs, numspecies(rn), length(rcs)) +end +function complexstoichmat(::Type{Matrix{Int}}, rn::ReactionSystem, rcs) + Z = zeros(Int, numspecies(rn), length(rcs)) + for (i, rc) in enumerate(rcs) + for rcel in rc + Z[rcel.speciesid, i] = rcel.speciesstoich + end + end + Z +end + +""" + complexstoichmat(network::ReactionSystem; sparse=false) + +Given a [`ReactionSystem`](@ref) and vector of reaction complexes, return a +matrix with positive entries of size number of species by number of complexes, +where the non-zero positive entries in the kth column denote stoichiometric +coefficients of the species participating in the kth reaction complex. + +Notes: +- Set sparse=true for a sparse matrix representation +""" +function complexstoichmat(rn::ReactionSystem; sparse = false) + isempty(get_systems(rn)) || + error("complexstoichmat does not currently support subsystems.") + nps = get_networkproperties(rn) + if isempty(nps.complexstoichmat) || (sparse != issparse(nps.complexstoichmat)) + nps.complexstoichmat = if sparse + complexstoichmat(SparseMatrixCSC{Int, Int}, rn, keys(reactioncomplexmap(rn))) + else + complexstoichmat(Matrix{Int}, rn, keys(reactioncomplexmap(rn))) + end + end + nps.complexstoichmat +end + +function complexoutgoingmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, B) + n = size(B, 2) + rows = rowvals(B) + vals = nonzeros(B) + Is = Int[] + Js = Int[] + Vs = Int[] + sizehint!(Is, div(length(vals), 2)) + sizehint!(Js, div(length(vals), 2)) + sizehint!(Vs, div(length(vals), 2)) + for j in 1:n + for i in nzrange(B, j) + if vals[i] != one(eltype(vals)) + push!(Is, rows[i]) + push!(Js, j) + push!(Vs, vals[i]) + end + end + end + sparse(Is, Js, Vs, size(B, 1), size(B, 2)) +end +function complexoutgoingmat(::Type{Matrix{Int}}, rn::ReactionSystem, B) + Δ = copy(B) + for (I, b) in pairs(Δ) + (b == 1) && (Δ[I] = 0) + end + Δ +end + +@doc raw""" + complexoutgoingmat(network::ReactionSystem; sparse=false) + +Given a [`ReactionSystem`](@ref) and complex incidence matrix, ``B``, return a +matrix of size num of complexes by num of reactions that identifies substrate +complexes. + +Notes: +- The complex outgoing matrix, ``\Delta``, is defined by +```math +\Delta_{i j} = \begin{cases} + = 0, &\text{if } B_{i j} = 1, \\ + = B_{i j}, &\text{otherwise.} +\end{cases} +``` +- Set sparse=true for a sparse matrix representation +""" +function complexoutgoingmat(rn::ReactionSystem; sparse = false) + isempty(get_systems(rn)) || + error("complexoutgoingmat does not currently support subsystems.") + nps = get_networkproperties(rn) + if isempty(nps.complexoutgoingmat) || (sparse != issparse(nps.complexoutgoingmat)) + B = reactioncomplexes(rn, sparse = sparse)[2] + nps.complexoutgoingmat = if sparse + complexoutgoingmat(SparseMatrixCSC{Int, Int}, rn, B) + else + complexoutgoingmat(Matrix{Int}, rn, B) + end + end + nps.complexoutgoingmat +end + +function incidencematgraph(incidencemat::Matrix{Int}) + @assert all(∈([-1, 0, 1]), incidencemat) + n = size(incidencemat, 1) # no. of nodes/complexes + graph = Graphs.DiGraph(n) + for col in eachcol(incidencemat) + src = 0 + dst = 0 + for i in eachindex(col) + (col[i] == -1) && (src = i) + (col[i] == 1) && (dst = i) + (src != 0) && (dst != 0) && break + end + Graphs.add_edge!(graph, src, dst) + end + return graph +end +function incidencematgraph(incidencemat::SparseMatrixCSC{Int, Int}) + @assert all(∈([-1, 0, 1]), incidencemat) + m, n = size(incidencemat) + graph = Graphs.DiGraph(m) + rows = rowvals(incidencemat) + vals = nonzeros(incidencemat) + for j in 1:n + inds = nzrange(incidencemat, j) + row = rows[inds] + val = vals[inds] + if val[1] == -1 + Graphs.add_edge!(graph, row[1], row[2]) + else + Graphs.add_edge!(graph, row[2], row[1]) + end + end + return graph +end + +""" + incidencematgraph(rn::ReactionSystem) + +Construct a directed simple graph where nodes correspond to reaction complexes and directed +edges to reactions converting between two complexes. + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +complexes,incidencemat = reactioncomplexes(sir) +incidencematgraph(sir) +``` +""" +function incidencematgraph(rn::ReactionSystem) + nps = get_networkproperties(rn) + if Graphs.nv(nps.incidencegraph) == 0 + isempty(nps.incidencemat) && + error("Please call reactioncomplexes(rn) first to construct the incidence matrix.") + nps.incidencegraph = incidencematgraph(nps.incidencemat) + end + nps.incidencegraph +end + +linkageclasses(incidencegraph) = Graphs.connected_components(incidencegraph) + +""" + linkageclasses(rn::ReactionSystem) + +Given the incidence graph of a reaction network, return a vector of the +connected components of the graph (i.e. sub-groups of reaction complexes that +are connected in the incidence graph). + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +complexes,incidencemat = reactioncomplexes(sir) +linkageclasses(sir) +``` +gives +```julia +2-element Vector{Vector{Int64}}: + [1, 2] + [3, 4] +``` +""" +function linkageclasses(rn::ReactionSystem) + nps = get_networkproperties(rn) + if isempty(nps.linkageclasses) + nps.linkageclasses = linkageclasses(incidencematgraph(rn)) + end + nps.linkageclasses +end + +@doc raw""" + deficiency(rn::ReactionSystem) + +Calculate the deficiency of a reaction network. + +Here the deficiency, ``\delta``, of a network with ``n`` reaction complexes, +``\ell`` linkage classes and a rank ``s`` stoichiometric matrix is + +```math +\delta = n - \ell - s +``` + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +rcs,incidencemat = reactioncomplexes(sir) +δ = deficiency(sir) +``` +""" +function deficiency(rn::ReactionSystem) + nps = get_networkproperties(rn) + conservationlaws(rn) + r = nps.rank + ig = incidencematgraph(rn) + lc = linkageclasses(rn) + nps.deficiency = Graphs.nv(ig) - length(lc) - r + nps.deficiency +end + +function subnetworkmapping(linkageclass, allrxs, complextorxsmap, p) + rxinds = sort!(collect(Set(rxidx for rcidx in linkageclass + for rxidx in complextorxsmap[rcidx]))) + rxs = allrxs[rxinds] + specset = Set(s for rx in rxs for s in rx.substrates if !isconstant(s)) + for rx in rxs + for product in rx.products + !isconstant(product) && push!(specset, product) + end + end + specs = collect(specset) + newps = Vector{eltype(p)}() + for rx in rxs + Symbolics.get_variables!(newps, rx.rate, p) + end + rxs, specs, newps # reactions and species involved in reactions of subnetwork +end + +""" + subnetworks(rn::ReactionSystem) + +Find subnetworks corresponding to each linkage class of the reaction network. + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +complexes,incidencemat = reactioncomplexes(sir) +subnetworks(sir) +``` +""" +function subnetworks(rs::ReactionSystem) + isempty(get_systems(rs)) || error("subnetworks does not currently support subsystems.") + lcs = linkageclasses(rs) + rxs = reactions(rs) + p = parameters(rs) + t = get_iv(rs) + spatial_ivs = get_sivs(rs) + complextorxsmap = [map(first, rcmap) for rcmap in values(reactioncomplexmap(rs))] + subnetworks = Vector{ReactionSystem}() + for i in 1:length(lcs) + reacs, specs, newps = subnetworkmapping(lcs[i], rxs, complextorxsmap, p) + newname = Symbol(nameof(rs), "_", i) + push!(subnetworks, + ReactionSystem(reacs, t, specs, newps; name = newname, spatial_ivs)) + end + subnetworks +end + +""" + linkagedeficiencies(network::ReactionSystem) + +Calculates the deficiency of each sub-reaction network within `network`. + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +rcs,incidencemat = reactioncomplexes(sir) +linkage_deficiencies = linkagedeficiencies(sir) +``` +""" +function linkagedeficiencies(rs::ReactionSystem) + lcs = linkageclasses(rs) + subnets = subnetworks(rs) + δ = zeros(Int, length(lcs)) + for (i, subnet) in enumerate(subnets) + conservationlaws(subnet) + nps = get_networkproperties(subnet) + δ[i] = length(lcs[i]) - 1 - nps.rank + end + δ +end + +""" + isreversible(rn::ReactionSystem) + +Given a reaction network, returns if the network is reversible or not. + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +rcs,incidencemat = reactioncomplexes(sir) +isreversible(sir) +``` +""" +function isreversible(rn::ReactionSystem) + ig = incidencematgraph(rn) + Graphs.reverse(ig) == ig +end + +""" + isweaklyreversible(rn::ReactionSystem, subnetworks) + +Determine if the reaction network with the given subnetworks is weakly reversible or not. + +Notes: +- Requires the `incidencemat` to already be cached in `rn` by a previous call to + `reactioncomplexes`. + +For example, +```julia +sir = @reaction_network SIR begin + β, S + I --> 2I + ν, I --> R +end +rcs,incidencemat = reactioncomplexes(sir) +subnets = subnetworks(rn) +isweaklyreversible(rn, subnets) +``` +""" +function isweaklyreversible(rn::ReactionSystem, subnets) + im = get_networkproperties(rn).incidencemat + isempty(im) && + error("Error, please call reactioncomplexes(rn::ReactionSystem) to ensure the incidence matrix has been cached.") + sparseig = issparse(im) + for subnet in subnets + nps = get_networkproperties(subnet) + isempty(nps.incidencemat) && reactioncomplexes(subnet; sparse = sparseig) + end + all(Graphs.is_strongly_connected ∘ incidencematgraph, subnets) +end + +############################################################################################ +######################## conservation laws ############################### + +""" + conservedequations(rn::ReactionSystem) + +Calculate symbolic equations from conservation laws, writing dependent variables as +functions of independent variables and the conservation law constants. + +Notes: +- Caches the resulting equations in `rn`, so will be fast on subsequent calls. + +Examples: +```@repl +rn = @reaction_network begin + k, A + B --> C + k2, C --> A + B + end +conservedequations(rn) +``` +gives +``` +2-element Vector{Equation}: + B(t) ~ A(t) + Γ[1] + C(t) ~ Γ[2] - A(t) +``` +""" +function conservedequations(rn::ReactionSystem) + conservationlaws(rn) + nps = get_networkproperties(rn) + nps.conservedeqs +end + +""" + conservationlaw_constants(rn::ReactionSystem) + +Calculate symbolic equations from conservation laws, writing the conservation law constants +in terms of the dependent and independent variables. + +Notes: +- Caches the resulting equations in `rn`, so will be fast on subsequent calls. + +Examples: +```@julia +rn = @reaction_network begin + k, A + B --> C + k2, C --> A + B + end +conservationlaw_constants(rn) +``` +gives +``` +2-element Vector{Equation}: + Γ[1] ~ B(t) - A(t) + Γ[2] ~ A(t) + C(t) +``` +""" +function conservationlaw_constants(rn::ReactionSystem) + conservationlaws(rn) + nps = get_networkproperties(rn) + nps.constantdefs +end + +""" + conservationlaws(netstoichmat::AbstractMatrix)::Matrix + +Given the net stoichiometry matrix of a reaction system, computes a matrix of +conservation laws, each represented as a row in the output. +""" +function conservationlaws(nsm::T; col_order = nothing) where {T <: AbstractMatrix} + + # compute the left nullspace over the integers + N = MT.nullspace(nsm'; col_order) + + # if all coefficients for a conservation law are negative, make positive + for Nrow in eachcol(N) + all(r -> r <= 0, Nrow) && (Nrow .*= -1) + end + + # check we haven't overflowed + iszero(N' * nsm) || error("Calculation of the conservation law matrix was inaccurate, " + * "likely due to numerical overflow. Please use a larger integer " + * "type like Int128 or BigInt for the net stoichiometry matrix.") + + T(N') +end + +function cache_conservationlaw_eqs!(rn::ReactionSystem, N::AbstractMatrix, col_order) + nullity = size(N, 1) + r = numspecies(rn) - nullity # rank of the netstoichmat + sts = species(rn) + indepidxs = col_order[begin:r] + indepspecs = sts[indepidxs] + depidxs = col_order[(r + 1):end] + depspecs = sts[depidxs] + constants = MT.unwrap.(MT.scalarize((@parameters Γ[1:nullity])[1])) + + conservedeqs = Equation[] + constantdefs = Equation[] + for (i, depidx) in enumerate(depidxs) + scaleby = (N[i, depidx] != 1) ? N[i, depidx] : one(eltype(N)) + (scaleby != 0) || error("Error, found a zero in the conservation law matrix where " + * + "one was not expected.") + coefs = @view N[i, indepidxs] + terms = sum(p -> p[1] / scaleby * p[2], zip(coefs, indepspecs)) + eq = depspecs[i] ~ constants[i] - terms + push!(conservedeqs, eq) + eq = constants[i] ~ depspecs[i] + terms + push!(constantdefs, eq) + end + + # cache in the system + nps = get_networkproperties(rn) + nps.rank = r + nps.nullity = nullity + nps.indepspecs = Set(indepspecs) + nps.depspecs = Set(depspecs) + nps.conservedeqs = conservedeqs + nps.constantdefs = constantdefs + + nothing +end + +""" + conservationlaws(rs::ReactionSystem) + +Return the conservation law matrix of the given `ReactionSystem`, calculating it if it is +not already stored within the system, or returning an alias to it. + +Notes: +- The first time being called it is calculated and cached in `rn`, subsequent calls should + be fast. +""" +function conservationlaws(rs::ReactionSystem) + nps = get_networkproperties(rs) + !isempty(nps.conservationmat) && (return nps.conservationmat) + nsm = netstoichmat(rs) + nps.conservationmat = conservationlaws(nsm; col_order = nps.col_order) + cache_conservationlaw_eqs!(rs, nps.conservationmat, nps.col_order) + nps.conservationmat +end + +""" + conservedquantities(state, cons_laws) + +Compute conserved quantities for a system with the given conservation laws. +""" +conservedquantities(state, cons_laws) = cons_laws * state + + +# If u0s are not given while conservation laws are present, throws an error. +# Used in HomotopyContinuation and BifurcationKit extensions. +# Currently only checks if any u0s are given +# (not whether these are enough for computing conserved quantitites, this will yield a less informative error). +function conservationlaw_errorcheck(rs, pre_varmap) + vars_with_vals = Set(p[1] for p in pre_varmap) + any(s -> s in vars_with_vals, species(rs)) && return + isempty(conservedequations(Catalyst.flatten(rs))) || + error("The system has conservation laws but initial conditions were not provided for some species.") +end + +######################## reaction network operators ####################### + +""" + ==(rx1::Reaction, rx2::Reaction) + +Tests whether two [`Reaction`](@ref)s are identical. + +Notes: +- Ignores the order in which stoichiometry components are listed. +- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be + considered different than `(A+1)^2`. +""" +function (==)(rx1::Reaction, rx2::Reaction) + isequal(rx1.rate, rx2.rate) || return false + issetequal(zip(rx1.substrates, rx1.substoich), zip(rx2.substrates, rx2.substoich)) || + return false + issetequal(zip(rx1.products, rx1.prodstoich), zip(rx2.products, rx2.prodstoich)) || + return false + issetequal(rx1.netstoich, rx2.netstoich) || return false + rx1.only_use_rate == rx2.only_use_rate +end + +function hash(rx::Reaction, h::UInt) + h = Base.hash(rx.rate, h) + for s in Iterators.flatten((rx.substrates, rx.products)) + h ⊻= hash(s) + end + for s in Iterators.flatten((rx.substoich, rx.prodstoich)) + h ⊻= hash(s) + end + for s in rx.netstoich + h ⊻= hash(s) + end + Base.hash(rx.only_use_rate, h) +end + +""" + isequivalent(rn1::ReactionSystem, rn2::ReactionSystem; ignorenames = true) + +Tests whether the underlying species, parameters and reactions are the same in +the two [`ReactionSystem`](@ref)s. Ignores the names of the systems in testing +equality. + +Notes: +- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be + considered different than `(A+1)^2`. +- Does not include `defaults` in determining equality. +""" +function isequivalent(rn1::ReactionSystem, rn2::ReactionSystem; ignorenames = true) + if !ignorenames + (nameof(rn1) == nameof(rn2)) || return false + end + + (get_combinatoric_ratelaws(rn1) == get_combinatoric_ratelaws(rn2)) || return false + isequal(get_iv(rn1), get_iv(rn2)) || return false + issetequal(get_sivs(rn1), get_sivs(rn2)) || return false + issetequal(get_states(rn1), get_states(rn2)) || return false + issetequal(get_ps(rn1), get_ps(rn2)) || return false + issetequal(MT.get_observed(rn1), MT.get_observed(rn2)) || return false + issetequal(get_eqs(rn1), get_eqs(rn2)) || return false + + # subsystems + (length(get_systems(rn1)) == length(get_systems(rn2))) || return false + issetequal(get_systems(rn1), get_systems(rn2)) || return false + + true +end + +function isequal_ignore_names(rn1, rn2) + Base.depwarn("Catalyst.isequal_ignore_names has been deprecated. Use isequivalent(rn1, rn2) instead.", + :isequal_ignore_names; force = true) + isequivalent(rn1, rn2) +end + +""" + ==(rn1::ReactionSystem, rn2::ReactionSystem) + +Tests whether the underlying species, parameters and reactions are the same in +the two [`ReactionSystem`](@ref)s. Requires the systems to have the same names +too. + +Notes: +- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be + considered different than `(A+1)^2`. +- Does not include `defaults` in determining equality. +""" +function (==)(rn1::ReactionSystem, rn2::ReactionSystem) + isequivalent(rn1, rn2; ignorenames = false) +end + +######################## functions to extend a network #################### + +""" + make_empty_network(; iv=DEFAULT_IV, name=gensym(:ReactionSystem)) + +Construct an empty [`ReactionSystem`](@ref). `iv` is the independent variable, +usually time, and `name` is the name to give the `ReactionSystem`. +""" +function make_empty_network(; iv = DEFAULT_IV, name = gensym(:ReactionSystem)) + ReactionSystem(Reaction[], iv, [], []; name = name) +end + +""" + addspecies!(network::ReactionSystem, s::Symbolic; disablechecks=false) + +Given a [`ReactionSystem`](@ref), add the species corresponding to the variable +`s` to the network (if it is not already defined). Returns the integer id of the +species within the system. + +Notes: +- `disablechecks` will disable checking for whether the passed in variable is + already defined, which is useful when adding many new variables to the system. + *Do not disable checks* unless you are sure the passed in variable is a new + variable, as this will potentially leave the system in an undefined state. +""" +function addspecies!(network::ReactionSystem, s::Symbolic; disablechecks = false) + reset_networkproperties!(network) + + isconstant(s) && error("Constant species should be added via addparams!.") + isspecies(s) || + error("$s is not a valid symbolic species. Please use @species to declare it.") + + # we don't check subsystems since we will add it to the top-level system... + curidx = disablechecks ? nothing : findfirst(S -> isequal(S, s), get_states(network)) + if curidx === nothing + push!(get_states(network), s) + sort!(get_states(network); by = !isspecies) + push!(get_species(network), s) + MT.process_variables!(get_var_to_name(network), get_defaults(network), [s]) + return length(get_species(network)) + else + return curidx + end +end + +""" + addspecies!(network::ReactionSystem, s::Num; disablechecks=false) + +Given a [`ReactionSystem`](@ref), add the species corresponding to the +variable `s` to the network (if it is not already defined). Returns the +integer id of the species within the system. + +- `disablechecks` will disable checking for whether the passed in variable is + already defined, which is useful when adding many new variables to the system. + *Do not disable checks* unless you are sure the passed in variable is a new + variable, as this will potentially leave the system in an undefined state. +""" +function addspecies!(network::ReactionSystem, s::Num; disablechecks = false) + addspecies!(network, value(s), disablechecks = disablechecks) +end + +""" + reorder_states!(rn, neworder) + +Given a [`ReactionSystem`](@ref) and a vector `neworder`, reorders the states of `rn`, i.e. +`get_states(rn)`, according to `neworder`. + +Notes: +- Currently only supports `ReactionSystem`s without subsystems. +""" +function reorder_states!(rn, neworder) + reset_networkproperties!(rn) + + permute!(get_states(rn), neworder) + if !issorted(get_states(rn); by = !isspecies) + @warn "New ordering has resulted in a non-species state preceding a species state. This is not allowed so states have been resorted to ensure species precede non-species." + sort!(get_states(rn); by = !isspecies) + end + get_species(rn) .= Iterators.filter(isspecies, get_states(rn)) + nothing +end + +""" + addparam!(network::ReactionSystem, p::Symbolic; disablechecks=false) + +Given a [`ReactionSystem`](@ref), add the parameter corresponding to the +variable `p` to the network (if it is not already defined). Returns the integer +id of the parameter within the system. + +- `disablechecks` will disable checking for whether the passed in variable is + already defined, which is useful when adding many new variables to the system. + *Do not disable checks* unless you are sure the passed in variable is a new + variable, as this will potentially leave the system in an undefined state. +""" +function addparam!(network::ReactionSystem, p::Symbolic; disablechecks = false) + reset_networkproperties!(network) + + # we don't check subsystems since we will add it to the top-level system... + if istree(p) && !(operation(p) isa Symbolic && !istree(operation(p))) + error("If the passed in parameter is an expression, it must correspond to an underlying Variable.") + end + curidx = disablechecks ? nothing : findfirst(S -> isequal(S, p), get_ps(network)) + if curidx === nothing + push!(get_ps(network), p) + MT.process_variables!(get_var_to_name(network), get_defaults(network), [p]) + return length(get_ps(network)) + else + return curidx + end +end + +""" + addparam!(network::ReactionSystem, p::Num; disablechecks=false) + +Given a [`ReactionSystem`](@ref), add the parameter corresponding to the +variable `p` to the network (if it is not already defined). Returns the +integer id of the parameter within the system. + +- `disablechecks` will disable checking for whether the passed in variable is + already defined, which is useful when adding many new variables to the system. + *Do not disable checks* unless you are sure the passed in variable is a new + variable, as this will potentially leave the system in an undefined state. +""" +function addparam!(network::ReactionSystem, p::Num; disablechecks = false) + addparam!(network, value(p); disablechecks = disablechecks) +end + +""" + addreaction!(network::ReactionSystem, rx::Reaction) + +Add the passed in reaction to the [`ReactionSystem`](@ref). Returns the +integer id of `rx` in the list of `Reaction`s within `network`. + +Notes: +- Any new species or parameters used in `rx` should be separately added to + `network` using [`addspecies!`](@ref) and [`addparam!`](@ref). +""" +function addreaction!(network::ReactionSystem, rx::Reaction) + reset_networkproperties!(network) + push!(get_eqs(network), rx) + sort(get_eqs(network); by = eqsortby) + push!(get_rxs(network), rx) + length(get_rxs(network)) +end + +""" + merge!(network1::ReactionSystem, network2::ReactionSystem) + +Merge `network2` into `network1`. + +Notes: +- Duplicate reactions between the two networks are not filtered out. +- [`Reaction`](@ref)s are not deepcopied to minimize allocations, so both + networks will share underlying data arrays. +- Subsystems are not deepcopied between the two networks and will hence be + shared. +- Returns `network1`. +- `combinatoric_ratelaws` is the value of `network1`. +""" +function Base.merge!(network1::ReactionSystem, network2::ReactionSystem) + isequal(get_iv(network1), get_iv(network2)) || + error("Reaction networks must have the same independent variable to be mergable.") + union!(get_sivs(network1), get_sivs(network2)) + + union!(get_eqs(network1), get_eqs(network2)) + sort!(get_eqs(network1), by = eqsortby) + union!(get_rxs(network1), get_rxs(network2)) + (count(eq -> eq isa Reaction, get_eqs(network1)) == length(get_rxs(network1))) || + error("Unequal number of reactions from get_rxs(sys) and get_eqs(sys) after merging.") + + union!(get_states(network1), get_states(network2)) + sort!(get_states(network1), by = !isspecies) + union!(get_species(network1), get_species(network2)) + (count(isspecies, get_states(network1)) == length(get_species(network1))) || + error("Unequal number of species from get_species(sys) and get_states(sys) after merging.") + + union!(get_ps(network1), get_ps(network2)) + union!(get_observed(network1), get_observed(network2)) + union!(get_systems(network1), get_systems(network2)) + merge!(get_defaults(network1), get_defaults(network2)) + union!(MT.get_continuous_events(network1), MT.get_continuous_events(network2)) + union!(MT.get_discrete_events(network1), MT.get_discrete_events(network2)) + + reset_networkproperties!(network1) + network1 +end + +############################### units ##################################### + +""" + validate(rx::Reaction; info::String = "") + +Check that all substrates and products within the given [`Reaction`](@ref) have +the same units, and that the units of the reaction's rate expression are +internally consistent (i.e. if the rate involves sums, each term in the sum has +the same units). + +""" +function validate(rx::Reaction; info::String = "") + validated = MT._validate([rx.rate], [string(rx, ": rate")], info = info) + + subunits = isempty(rx.substrates) ? nothing : get_unit(rx.substrates[1]) + for i in 2:length(rx.substrates) + if get_unit(rx.substrates[i]) != subunits + validated = false + @warn(string("In ", rx, " the substrates have differing units.")) + end + end + + produnits = isempty(rx.products) ? nothing : get_unit(rx.products[1]) + for i in 2:length(rx.products) + if get_unit(rx.products[i]) != produnits + validated = false + @warn(string("In ", rx, " the products have differing units.")) + end + end + + if (subunits !== nothing) && (produnits !== nothing) && (subunits != produnits) + validated = false + @warn(string("in ", rx, + " the substrate units are not consistent with the product units.")) + end + + validated +end + +""" + validate(rs::ReactionSystem, info::String="") + +Check that all species in the [`ReactionSystem`](@ref) have the same units, and +that the rate laws of all reactions reduce to units of (species units) / (time +units). + +Notes: +- Does not check subsystems, constraint equations, or non-species variables. +""" +function validate(rs::ReactionSystem, info::String = "") + specs = get_species(rs) + + # if there are no species we don't check units on the system + isempty(specs) && return true + + specunits = get_unit(specs[1]) + validated = true + for spec in specs + if get_unit(spec) != specunits + validated = false + @warn(string("Species are expected to have units of ", specunits, + " however, species ", spec, " has units ", get_unit(spec), ".")) + end + end + timeunits = get_unit(get_iv(rs)) + + # no units for species, time or parameters then assume validated + (specunits in (MT.unitless, nothing)) && (timeunits in (MT.unitless, nothing)) && + MT.all_dimensionless(get_ps(rs)) && return true + + rateunits = specunits / timeunits + for rx in get_rxs(rs) + rxunits = get_unit(rx.rate) + for (i, sub) in enumerate(rx.substrates) + rxunits *= get_unit(sub)^rx.substoich[i] + end + + if rxunits != rateunits + validated = false + @warn(string("Reaction rate laws are expected to have units of ", rateunits, + " however, ", rx, " has units of ", rxunits, ".")) + end + end + + validated +end + +function iscomplexbalanced(rs::ReactionSystem, conc::Vector) + sm = speciesmap(rs) + cm = reactioncomplexmap(rs) + complexes, incidencemat = reactioncomplexes(rs) + complexbalanced = true + + for c in complexes + if (sum([massactionrate(rs, rxn..., conc) for rxn in cm[c]]) != 0) + return false + end + end + complexbalanced +end + +""" + complexbalanced(rs::ReactionSystem, rates::Vector) + +Constructively compute whether a network will have complex-balanced equilibrium +solutions, following the method in this paper. + +Notes: +- Does not check subsystems, constraint equations, or non-species variables. +""" + +using MetaGraphs, Combinatorics, LinearAlgebra + +function complexbalanced(rs::ReactionSystem, rates::Vector) + if length(rates) != numparams(rs) + error("The number of reaction rates must be equal to the number of parameters") + end + rxm = Dict(zip(reactionparams(rs), rates)) + sm = speciesmap(rs) + cm = reactioncomplexmap(rs) + complexes, D = reactioncomplexes(rs) + rxns = reactions(rs) + nc = length(complexes); nr = numreactions(rs); nm = numspecies(rs) + + # Construct kinetic matrix, K + K = zeros(nr, nc) + for c in 1:nc + complex = complexes[c] + for (r, dir) in cm[complex] + rxn = rxns[r] + if dir == -1 + K[r, c] = rxm[rxn.rate] + end + end + end + + L = -D*K + S = netstoichmat(rs) + + # Compute ρ using the matrix-tree theorem + ρ = zeros(nc) + lcs = linkageclasses(rs) + rwg = rateweightedgraph(rs, rates) + + for lc in lcs + sg, vmap = Graphs.induced_subgraph(rwg, lc) + ρ_j = matrixtree(sg) + ρ[lc] = ρ_j + end + + # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T + if all(x -> x > 0, ρ) + img = D'*log.(ρ) + if rank(S') == rank(hcat(S', img)) + return true + else + return false + end + else + return false + end +end + +function rateweightedgraph(rs::ReactionSystem, rates::Vector) + if length(rates) != numparams(rs) + error("The number of reaction rates must be equal to the number of parameters") + end + rm = Dict(zip(reactionparams(rs), rates)) + + complexes, D = reactioncomplexes(rs) + rxns = reactions(rs) + + g = incidencematgraph(rs) + rwg = MetaDiGraph(g) + + for v in vertices(rwg) + set_prop!(rwg, v, :complex, complexes[v]) + end + + for (i, e) in collect(enumerate(edges(rwg))) + rxn = rxns[i] + set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :reaction, rxn) + set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :rate, rm[rxn.rate]) + end + + rwg +end + +function matrixtree(g::MetaDiGraph) + # generate all spanning trees + # TODO: implement Winter's algorithm for generating spanning trees + n = nv(g) + ug = SimpleGraph(SimpleDiGraph(g)) + trees = collect(Combinatorics.combinations(collect(edges(ug)), n-1)) + trees = SimpleGraph.(trees) + trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) + + π = zeros(n) + + function treeweight(t::SimpleDiGraph) + prod = 1 + for e in edges(t) + rate = Graphs.has_edge(g, Graphs.src(e), Graphs.dst(e)) ? get_prop(g, e, :rate) : 0 + prod *= rate + end + prod + end + + # constructed rooted trees for every edge, compute sum + for v in 1:n + rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir=:in)) for t in trees] + π[v] = sum([treeweight(t) for t in rootedTrees]) + end + + # sum the contributions + return π +end + +function massactionrate(rs::ReactionSystem, rxn_idx::Int, dir::Int, conc::Vector) + if dir != -1 && dir != 1 + error("Direction must be either +1 in the case of production or -1 in the case of consumption") + end + + rxn = reactions(rs)[rxn_idx] + sm = speciesmap(rs) + rate = rxn.rate + + species = rxn.substrates + stoich = rxn.substoich + species_idx = [sm[s] for s in species] + + dir * rate * prod(conc[species_idx].^stoich) +end From 2d36ed2416f77d9beea8ef7f51ee0d8dcc272871 Mon Sep 17 00:00:00 2001 From: vydu Date: Sun, 12 May 2024 00:28:18 -0400 Subject: [PATCH 139/446] Added tests for complex balancing --- test/network_analysis/network_properties.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/network_analysis/network_properties.jl b/test/network_analysis/network_properties.jl index b5d488642a..f474c1bd60 100644 --- a/test/network_analysis/network_properties.jl +++ b/test/network_analysis/network_properties.jl @@ -40,6 +40,9 @@ let @test isweaklyreversible(MAPK, subnetworks(MAPK)) == false cls = conservationlaws(MAPK) @test Catalyst.get_networkproperties(MAPK).rank == 15 + + rates = rand(numparams(MAPK)) + @test Catalyst.complexbalanced(MAPK, rates) == false # i=0; # for lcs in linkageclasses(MAPK) # i=i+1 @@ -77,6 +80,9 @@ let @test isweaklyreversible(rn2, subnetworks(rn2)) == false cls = conservationlaws(rn2) @test Catalyst.get_networkproperties(rn2).rank == 6 + + rates = rand(numparams(rn2)) + @test Catalyst.complexbalanced(rn2, rates) == false # i=0; # for lcs in linkageclasses(rn2) # i=i+1 @@ -117,6 +123,9 @@ let @test isweaklyreversible(rn3, subnetworks(rn3)) == false cls = conservationlaws(rn3) @test Catalyst.get_networkproperties(rn3).rank == 10 + + rates = rand(numparams(rn3)) + @test Catalyst.complexbalanced(rn3, rates) == false # i=0; # for lcs in linkageclasses(rn3) # i=i+1 From 366a96c7f13e3043e01e3c86fca3838525724046 Mon Sep 17 00:00:00 2001 From: vydu Date: Sun, 12 May 2024 17:46:42 -0400 Subject: [PATCH 140/446] added documentation --- Project.toml | 3 +++ src/networkapi.jl | 24 ++++++------------------ 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/Project.toml b/Project.toml index 2b90ff7b2a..920ddcf6d2 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,7 @@ uuid = "479239e8-5488-4da2-87a7-35f2df7eef83" version = "13.5.1" [deps] +Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" @@ -14,10 +15,12 @@ LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +MetaGraphs = "626554b9-1ddb-594c-aa3c-2596fe9399a5" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Requires = "ae029012-a4dd-5104-9daa-d747884805df" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" RuntimeGeneratedFunctions = "7e49a35a-f44a-4d26-94aa-eba1b4ca6b47" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" diff --git a/src/networkapi.jl b/src/networkapi.jl index f0cc164856..61d226de2c 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1653,28 +1653,11 @@ function validate(rs::ReactionSystem, info::String = "") validated end -function iscomplexbalanced(rs::ReactionSystem, conc::Vector) - sm = speciesmap(rs) - cm = reactioncomplexmap(rs) - complexes, incidencemat = reactioncomplexes(rs) - complexbalanced = true - - for c in complexes - if (sum([massactionrate(rs, rxn..., conc) for rxn in cm[c]]) != 0) - return false - end - end - complexbalanced -end - """ complexbalanced(rs::ReactionSystem, rates::Vector) Constructively compute whether a network will have complex-balanced equilibrium -solutions, following the method in this paper. - -Notes: -- Does not check subsystems, constraint equations, or non-species variables. +solutions, following the method in [this paper](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). """ using MetaGraphs, Combinatorics, LinearAlgebra @@ -1729,6 +1712,11 @@ function complexbalanced(rs::ReactionSystem, rates::Vector) end end +""" + rateweightedgraph(rs::ReactionSystem, rates::Vector) + +Generate an annotated reaction complex graph of a reaction system, where the nodes are annotated with the reaction complex they correspond to and the edges are annotated with the reaction they correspond to and the rate of the reaction. +""" function rateweightedgraph(rs::ReactionSystem, rates::Vector) if length(rates) != numparams(rs) error("The number of reaction rates must be equal to the number of parameters") From fe432b52fdf8541e545880aeed0cee5ed15bf8a9 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 16 May 2024 10:59:35 -0400 Subject: [PATCH 141/446] Restructured files --- Project.toml | 2 +- src/Project.toml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 src/Project.toml diff --git a/Project.toml b/Project.toml index 920ddcf6d2..ac53aaee49 100644 --- a/Project.toml +++ b/Project.toml @@ -20,7 +20,6 @@ ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Requires = "ae029012-a4dd-5104-9daa-d747884805df" -Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" RuntimeGeneratedFunctions = "7e49a35a-f44a-4d26-94aa-eba1b4ca6b47" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" @@ -41,6 +40,7 @@ CatalystStructuralIdentifiabilityExtension = "StructuralIdentifiability" [compat] BifurcationKit = "0.3" DataStructures = "0.18" +Combinatorics = "1.0.2" DiffEqBase = "6.83.0" DocStringExtensions = "0.8, 0.9" DynamicQuantities = "0.13.2" diff --git a/src/Project.toml b/src/Project.toml deleted file mode 100644 index e7b949696b..0000000000 --- a/src/Project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[deps] -Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83" From 710b1646b1c400287d866a5a02bd72eda38a8b9d Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 16 May 2024 17:08:40 -0400 Subject: [PATCH 142/446] Refactored complex balance to not use MetaGraphs --- src/networkapi.jl | 143 ++++++++++++-------- test/network_analysis/network_properties.jl | 74 ++++++++-- 2 files changed, 153 insertions(+), 64 deletions(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index 61d226de2c..bf9e31a3dc 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1654,25 +1654,27 @@ function validate(rs::ReactionSystem, info::String = "") end """ - complexbalanced(rs::ReactionSystem, rates::Vector) + iscomplexbalanced(rs::ReactionSystem, rates::Vector) Constructively compute whether a network will have complex-balanced equilibrium -solutions, following the method in [this paper](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). +solutions, following the method in [this paper](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a map of rates [k1 => 1.0, k2 => 2.0,...]k2 """ -using MetaGraphs, Combinatorics, LinearAlgebra +using Combinatorics, LinearAlgebra -function complexbalanced(rs::ReactionSystem, rates::Vector) +function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) if length(rates) != numparams(rs) error("The number of reaction rates must be equal to the number of parameters") end - rxm = Dict(zip(reactionparams(rs), rates)) + sm = speciesmap(rs) cm = reactioncomplexmap(rs) complexes, D = reactioncomplexes(rs) rxns = reactions(rs) nc = length(complexes); nr = numreactions(rs); nm = numspecies(rs) + if !all(r->ismassaction(r, rs), rxns) error("Not mass action") end + # Construct kinetic matrix, K K = zeros(nr, nc) for c in 1:nc @@ -1680,7 +1682,7 @@ function complexbalanced(rs::ReactionSystem, rates::Vector) for (r, dir) in cm[complex] rxn = rxns[r] if dir == -1 - K[r, c] = rxm[rxn.rate] + K[r, c] = rates[rxn.rate] end end end @@ -1689,93 +1691,122 @@ function complexbalanced(rs::ReactionSystem, rates::Vector) S = netstoichmat(rs) # Compute ρ using the matrix-tree theorem - ρ = zeros(nc) - lcs = linkageclasses(rs) - rwg = rateweightedgraph(rs, rates) + g = incidencematgraph(rs); R = ratematrix(rs, rates) + ρ = matrixtree(g, R) + @assert isapprox(L*ρ, zeros(nc), atol=1e-12) - for lc in lcs - sg, vmap = Graphs.induced_subgraph(rwg, lc) - ρ_j = matrixtree(sg) - ρ[lc] = ρ_j - end - # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T if all(x -> x > 0, ρ) img = D'*log.(ρ) - if rank(S') == rank(hcat(S', img)) - return true - else - return false - end + if rank(S') == rank(hcat(S', img)) return true else return false end else return false end end -""" - rateweightedgraph(rs::ReactionSystem, rates::Vector) - -Generate an annotated reaction complex graph of a reaction system, where the nodes are annotated with the reaction complex they correspond to and the edges are annotated with the reaction they correspond to and the rate of the reaction. -""" -function rateweightedgraph(rs::ReactionSystem, rates::Vector) +# """ +# rateweightedgraph(rs::ReactionSystem, rates::Vector) +# +# Generate an annotated reaction complex graph of a reaction system, where the nodes are annotated with the reaction complex they correspond to and the edges are annotated with the reaction they correspond to and the rate of the reaction. +# """ +# +# function rateweightedgraph(rs::ReactionSystem, rates::Dict{Any, Float64}) +# if length(rates) != numparams(rs) +# error("The number of reaction rates must be equal to the number of parameters") +# end +# +# complexes, D = reactioncomplexes(rs) +# rxns = reactions(rs) +# +# g = incidencematgraph(rs) +# rwg = MetaDiGraph(g) +# +# for v in vertices(rwg) +# set_prop!(rwg, v, :complex, complexes[v]) +# end +# +# for (i, e) in collect(enumerate(edges(rwg))) +# rxn = rxns[i] +# set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :reaction, rxn) +# set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :rate, rates[rxn.rate]) +# end +# +# rwg +# end + +function ratematrix(rs::ReactionSystem, rates::Dict{Any, Float64}) if length(rates) != numparams(rs) error("The number of reaction rates must be equal to the number of parameters") end - rm = Dict(zip(reactionparams(rs), rates)) complexes, D = reactioncomplexes(rs) + n = length(complexes) rxns = reactions(rs) + ratematrix = zeros(n, n) - g = incidencematgraph(rs) - rwg = MetaDiGraph(g) - - for v in vertices(rwg) - set_prop!(rwg, v, :complex, complexes[v]) + for r in 1:length(rxns) + rxn = rxns[r] + s = findfirst(x->x==-1, D[:,r]) + p = findfirst(x->x==1, D[:,r]) + ratematrix[s, p] = rates[rxn.rate] end + ratematrix +end - for (i, e) in collect(enumerate(edges(rwg))) - rxn = rxns[i] - set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :reaction, rxn) - set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :rate, rm[rxn.rate]) +""" +""" +function matrixtree(g::SimpleDiGraph, distmx::Matrix) + n = nv(g) + if size(distmx) != (n, n) + error("Size of distance matrix is incorrect") end - rwg -end + π = zeros(n) + + if !Graphs.is_connected(g) + ccs = Graphs.connected_components(g) + for cc in ccs + sg, vmap = Graphs.induced_subgraph(g, cc) + distmx_s = distmx[cc, cc] + π_j = matrixtree(sg, distmx_s) + π[cc] = π_j + end + return π + end -function matrixtree(g::MetaDiGraph) # generate all spanning trees - # TODO: implement Winter's algorithm for generating spanning trees - n = nv(g) ug = SimpleGraph(SimpleDiGraph(g)) trees = collect(Combinatorics.combinations(collect(edges(ug)), n-1)) trees = SimpleGraph.(trees) trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) - - π = zeros(n) - - function treeweight(t::SimpleDiGraph) - prod = 1 - for e in edges(t) - rate = Graphs.has_edge(g, Graphs.src(e), Graphs.dst(e)) ? get_prop(g, e, :rate) : 0 - prod *= rate - end - prod - end + # trees = spanningtrees(g) # constructed rooted trees for every edge, compute sum for v in 1:n rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir=:in)) for t in trees] - π[v] = sum([treeweight(t) for t in rootedTrees]) + π[v] = sum([treeweight(t, g, distmx) for t in rootedTrees]) end # sum the contributions return π end -function massactionrate(rs::ReactionSystem, rxn_idx::Int, dir::Int, conc::Vector) - if dir != -1 && dir != 1 - error("Direction must be either +1 in the case of production or -1 in the case of consumption") +function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) + prod = 1 + for e in edges(t) + s = Graphs.src(e); t = Graphs.dst(e) + prod *= distmx[s, t] end + prod +end + +# TODO: implement Winter's algorithm for generating spanning trees +function spanningtrees(g::SimpleGraph) + +end + +# Checks if a unit consist of exponents with base 1 (and is this unitless). +unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) rxn = reactions(rs)[rxn_idx] sm = speciesmap(rs) diff --git a/test/network_analysis/network_properties.jl b/test/network_analysis/network_properties.jl index f474c1bd60..35f5b40c2a 100644 --- a/test/network_analysis/network_properties.jl +++ b/test/network_analysis/network_properties.jl @@ -1,7 +1,9 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, LinearAlgebra, Test +using Catalyst, LinearAlgebra, Test, StableRNGs + +rng = StableRNG(514) ### Basic Tests ### @@ -41,8 +43,9 @@ let cls = conservationlaws(MAPK) @test Catalyst.get_networkproperties(MAPK).rank == 15 - rates = rand(numparams(MAPK)) - @test Catalyst.complexbalanced(MAPK, rates) == false + k = rand(rng, numparams(MAPK)) + rates = Dict(zip(reactionparams(MAPK), k)) + @test Catalyst.iscomplexbalanced(MAPK, rates) == false # i=0; # for lcs in linkageclasses(MAPK) # i=i+1 @@ -81,8 +84,9 @@ let cls = conservationlaws(rn2) @test Catalyst.get_networkproperties(rn2).rank == 6 - rates = rand(numparams(rn2)) - @test Catalyst.complexbalanced(rn2, rates) == false + k = rand(rng, numparams(rn2)) + rates = Dict(zip(reactionparams(rn2), k)) + @test Catalyst.iscomplexbalanced(rn2, rates) == false # i=0; # for lcs in linkageclasses(rn2) # i=i+1 @@ -123,9 +127,10 @@ let @test isweaklyreversible(rn3, subnetworks(rn3)) == false cls = conservationlaws(rn3) @test Catalyst.get_networkproperties(rn3).rank == 10 - - rates = rand(numparams(rn3)) - @test Catalyst.complexbalanced(rn3, rates) == false + + k = rand(rng, numparams(rn3)) + rates = Dict(zip(reactionparams(rn3), k)) + @test Catalyst.iscomplexbalanced(rn3, rates) == false # i=0; # for lcs in linkageclasses(rn3) # i=i+1 @@ -141,6 +146,18 @@ let # end end +let + rn4 = @reaction_network begin + (k1, k2), C1 <--> C2 + (k3, k4), C2 <--> C3 + (k5, k6), C3 <--> C1 + end + + k = rand(rng, numparams(rn4)) + rates = Dict(zip(reactionparams(rn4), k)) + @test Catalyst.iscomplexbalanced(rn4, rates) == true +end + ### Tests Reversibility ### # Test function. @@ -163,7 +180,12 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end + let rn = @reaction_network begin (k2, k1), A1 <--> A2 + A3 @@ -176,6 +198,10 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end let rn = @reaction_network begin @@ -185,6 +211,9 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end let rn = @reaction_network begin @@ -195,6 +224,10 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end let rn = @reaction_network begin @@ -206,6 +239,11 @@ let rev = false weak_rev = true testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + # Breaks when a reaction has multiple rates + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + # @test Catalyst.iscomplexbalanced(rn, rates) == true end let rn = @reaction_network begin @@ -215,6 +253,10 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end let rn = @reaction_network begin @@ -224,12 +266,20 @@ let rev = true weak_rev = true testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == true end let rn = @reaction_network begin (k2, k1), A + B <--> 2A end rev = true weak_rev = true testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == true end let rn = @reaction_network begin @@ -241,6 +291,10 @@ let rev = false weak_rev = true testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == true end let rn = @reaction_network begin @@ -252,4 +306,8 @@ let rev = false weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == false end \ No newline at end of file From dffc2efde49176d9d7c526947356a8bf58f8ca35 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 23 May 2024 09:44:58 -0400 Subject: [PATCH 143/446] some edits --- src/networkapi.jl | 57 ++++++++--------------------------------------- 1 file changed, 9 insertions(+), 48 deletions(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index bf9e31a3dc..08c01885bd 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1653,6 +1653,9 @@ function validate(rs::ReactionSystem, info::String = "") validated end +# Checks if a unit consist of exponents with base 1 (and is this unitless). +unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) + """ iscomplexbalanced(rs::ReactionSystem, rates::Vector) @@ -1696,44 +1699,14 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) @assert isapprox(L*ρ, zeros(nc), atol=1e-12) # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T - if all(x -> x > 0, ρ) + if all(>(0), ρ) img = D'*log.(ρ) - if rank(S') == rank(hcat(S', img)) return true else return false end + rank(S') == rank(hcat(S', img)) ? return true : return false else return false end end -# """ -# rateweightedgraph(rs::ReactionSystem, rates::Vector) -# -# Generate an annotated reaction complex graph of a reaction system, where the nodes are annotated with the reaction complex they correspond to and the edges are annotated with the reaction they correspond to and the rate of the reaction. -# """ -# -# function rateweightedgraph(rs::ReactionSystem, rates::Dict{Any, Float64}) -# if length(rates) != numparams(rs) -# error("The number of reaction rates must be equal to the number of parameters") -# end -# -# complexes, D = reactioncomplexes(rs) -# rxns = reactions(rs) -# -# g = incidencematgraph(rs) -# rwg = MetaDiGraph(g) -# -# for v in vertices(rwg) -# set_prop!(rwg, v, :complex, complexes[v]) -# end -# -# for (i, e) in collect(enumerate(edges(rwg))) -# rxn = rxns[i] -# set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :reaction, rxn) -# set_prop!(rwg, Graphs.src(e), Graphs.dst(e), :rate, rates[rxn.rate]) -# end -# -# rwg -# end - function ratematrix(rs::ReactionSystem, rates::Dict{Any, Float64}) if length(rates) != numparams(rs) error("The number of reaction rates must be equal to the number of parameters") @@ -1746,8 +1719,8 @@ function ratematrix(rs::ReactionSystem, rates::Dict{Any, Float64}) for r in 1:length(rxns) rxn = rxns[r] - s = findfirst(x->x==-1, D[:,r]) - p = findfirst(x->x==1, D[:,r]) + s = findfirst(==(-1), @view D[:,r]) + p = findfirst(==(1), @view D[:,r]) ratematrix[s, p] = rates[rxn.rate] end ratematrix @@ -1781,7 +1754,7 @@ function matrixtree(g::SimpleDiGraph, distmx::Matrix) trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) # trees = spanningtrees(g) - # constructed rooted trees for every edge, compute sum + # constructed rooted trees for every vertex, compute sum for v in 1:n rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir=:in)) for t in trees] π[v] = sum([treeweight(t, g, distmx) for t in rootedTrees]) @@ -1805,16 +1778,4 @@ function spanningtrees(g::SimpleGraph) end -# Checks if a unit consist of exponents with base 1 (and is this unitless). -unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) - - rxn = reactions(rs)[rxn_idx] - sm = speciesmap(rs) - rate = rxn.rate - - species = rxn.substrates - stoich = rxn.substoich - species_idx = [sm[s] for s in species] - - dir * rate * prod(conc[species_idx].^stoich) -end + sm = speciesmap(rs) \ No newline at end of file From b48676e66e0b48f5d4174c44c8cb6ef7ff2fcc5b Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 23 May 2024 09:54:55 -0400 Subject: [PATCH 144/446] some edits --- src/networkapi.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index 08c01885bd..02cc43983d 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1663,8 +1663,6 @@ Constructively compute whether a network will have complex-balanced equilibrium solutions, following the method in [this paper](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a map of rates [k1 => 1.0, k2 => 2.0,...]k2 """ -using Combinatorics, LinearAlgebra - function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) if length(rates) != numparams(rs) error("The number of reaction rates must be equal to the number of parameters") @@ -1694,7 +1692,8 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) S = netstoichmat(rs) # Compute ρ using the matrix-tree theorem - g = incidencematgraph(rs); R = ratematrix(rs, rates) + g = incidencematgraph(rs) + R = ratematrix(rs, rates) ρ = matrixtree(g, R) @assert isapprox(L*ρ, zeros(nc), atol=1e-12) From 85bda4eaaa44c7841c1ddc59c8e960db958d56e8 Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Thu, 23 May 2024 09:55:08 -0400 Subject: [PATCH 145/446] error update Co-authored-by: Sam Isaacson --- src/networkapi.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index 02cc43983d..db17a28924 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1674,7 +1674,9 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) rxns = reactions(rs) nc = length(complexes); nr = numreactions(rs); nm = numspecies(rs) - if !all(r->ismassaction(r, rs), rxns) error("Not mass action") end + if !all(r->ismassaction(r, rs), rxns) + error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.") + end # Construct kinetic matrix, K K = zeros(nr, nc) From 67f1146307e00e3225af246d96e5075440efb1e4 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 23 May 2024 09:59:25 -0400 Subject: [PATCH 146/446] added deps --- src/Catalyst.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 6521d0afb0..dd0bcd2912 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -6,9 +6,10 @@ module Catalyst using DocStringExtensions using SparseArrays, DiffEqBase, Reexport, Setfield using LaTeXStrings, Latexify, Requires -using JumpProcesses: JumpProcesses, JumpProblem, - MassActionJump, ConstantRateJump, VariableRateJump, - SpatialMassActionJump +using LinearAlgebra, Combinatorics +using JumpProcesses: JumpProcesses, + JumpProblem, MassActionJump, ConstantRateJump, + VariableRateJump # ModelingToolkit imports and convenience functions we use using ModelingToolkit From a9670c4e593d892f967602b5d54ef583a621878e Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 23 May 2024 14:47:05 -0400 Subject: [PATCH 147/446] retooling input map --- src/networkapi.jl | 78 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index db17a28924..9a2278b85a 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1657,17 +1657,20 @@ end unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) """ - iscomplexbalanced(rs::ReactionSystem, rates::Vector) + iscomplexbalanced(rs::ReactionSystem, parametermap) Constructively compute whether a network will have complex-balanced equilibrium -solutions, following the method in [this paper](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a map of rates [k1 => 1.0, k2 => 2.0,...]k2 +solutions, following the method in van der Schaft et al., [2015](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a dictionary, vector, or tuple ofvariable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...]. """ -function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) - if length(rates) != numparams(rs) - error("The number of reaction rates must be equal to the number of parameters") +function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) + if length(parametermap) != numparams(rs) + error("Incorrect number of parameters specified.") end + pmap = symmap_to_varmap(rs, parametermap) + pmap = Dict(ModelingToolkit.value(k) => v for (k,v) in pmap) + sm = speciesmap(rs) cm = reactioncomplexmap(rs) complexes, D = reactioncomplexes(rs) @@ -1678,6 +1681,8 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.") end + rates = [substitute(rate, pmap) for rate in reactionrates(rs)] + # Construct kinetic matrix, K K = zeros(nr, nc) for c in 1:nc @@ -1685,7 +1690,7 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) for (r, dir) in cm[complex] rxn = rxns[r] if dir == -1 - K[r, c] = rates[rxn.rate] + K[r, c] = rates[r] end end end @@ -1697,22 +1702,36 @@ function iscomplexbalanced(rs::ReactionSystem, rates::Dict{Any, Float64}) g = incidencematgraph(rs) R = ratematrix(rs, rates) ρ = matrixtree(g, R) - @assert isapprox(L*ρ, zeros(nc), atol=1e-12) # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T if all(>(0), ρ) img = D'*log.(ρ) - rank(S') == rank(hcat(S', img)) ? return true : return false + if rank(S') == rank(hcat(S', img)) return true else return false end else return false end end -function ratematrix(rs::ReactionSystem, rates::Dict{Any, Float64}) - if length(rates) != numparams(rs) - error("The number of reaction rates must be equal to the number of parameters") - end +function iscomplexbalanced(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + iscomplexbalanced(rs, pdict) +end + +function iscomplexbalanced(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + iscomplexbalanced(rs, pdict) +end +iscomplexbalanced(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") + + +""" + ratematrix(rs::ReactionSystem, parametermap) + + Given a reaction system with n complexes, outputs an n-by-n matrix where R_{ij} is the rate constant of the reaction between complex i and complex j. +""" + +function ratematrix(rs::ReactionSystem, rates::Vector{Float64}) complexes, D = reactioncomplexes(rs) n = length(complexes) rxns = reactions(rs) @@ -1722,13 +1741,34 @@ function ratematrix(rs::ReactionSystem, rates::Dict{Any, Float64}) rxn = rxns[r] s = findfirst(==(-1), @view D[:,r]) p = findfirst(==(1), @view D[:,r]) - ratematrix[s, p] = rates[rxn.rate] + ratematrix[s, p] = rates[r] end ratematrix end -""" -""" +function ratematrix(rs::ReactionSystem, parametermap::Dict{Symbol, Float64}) + if length(parametermap) != numparams(rs) + error("The number of reaction rates must be equal to the number of parameters") + end + pmap = symmap_to_varmap(rs, parametermap) + rates = [substitute(rate, pmap) for rate in reactionrates(rs)] + ratematrix(rs, rates) +end + +function ratematrix(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + ratematrix(rs, pdict) +end + +function ratematrix(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + ratematrix(rs, pdict) +end + +ratematrix(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") + +### BELOW: Helper functions for iscomplexbalanced + function matrixtree(g::SimpleDiGraph, distmx::Matrix) n = nv(g) if size(distmx) != (n, n) @@ -1753,7 +1793,6 @@ function matrixtree(g::SimpleDiGraph, distmx::Matrix) trees = collect(Combinatorics.combinations(collect(edges(ug)), n-1)) trees = SimpleGraph.(trees) trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) - # trees = spanningtrees(g) # constructed rooted trees for every vertex, compute sum for v in 1:n @@ -1773,10 +1812,3 @@ function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) end prod end - -# TODO: implement Winter's algorithm for generating spanning trees -function spanningtrees(g::SimpleGraph) - -end - - sm = speciesmap(rs) \ No newline at end of file From 5dbb72aa7a22e824d152d0c5669b10c861aa16cd Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 23 May 2024 14:49:54 -0400 Subject: [PATCH 148/446] updated test file --- test/network_analysis/network_properties.jl | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/test/network_analysis/network_properties.jl b/test/network_analysis/network_properties.jl index 35f5b40c2a..a716d04c36 100644 --- a/test/network_analysis/network_properties.jl +++ b/test/network_analysis/network_properties.jl @@ -232,7 +232,7 @@ end let rn = @reaction_network begin (k2, k1), A <--> 2B - (k4, k3), A + C --> D + (k4, k3), A + C <--> D k5, D --> B + E k6, B + E --> A + C end @@ -240,10 +240,9 @@ let weak_rev = true testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) - # Breaks when a reaction has multiple rates k = rand(rng, numparams(rn)) rates = Dict(zip(reactionparams(rn), k)) - # @test Catalyst.iscomplexbalanced(rn, rates) == true + @test Catalyst.iscomplexbalanced(rn, rates) == true end let rn = @reaction_network begin @@ -310,4 +309,20 @@ let k = rand(rng, numparams(rn)) rates = Dict(zip(reactionparams(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false -end \ No newline at end of file +end + +let + rn = @reaction_network begin + k1, 3A + 2B --> 3C + k2, B + 4D --> 2E + k3, 2E --> 3C + (k4, k5), B + 4D <--> 3A + 2B + k6, F --> B + 4D + k7, 3C --> F + end + + k = rand(rng, numparams(rn)) + rates = Dict(zip(reactionparams(rn), k)) + @test Catalyst.iscomplexbalanced(rn, rates) == true +end + From fe9e68a08d564af4979b63e9710feb5bbf1e7f00 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 10:26:50 -0400 Subject: [PATCH 149/446] added networkanalysis --- src/network_analysis.jl | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index cc33f9b63e..7dbec6f11c 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -718,3 +718,54 @@ function conservationlaw_errorcheck(rs, pre_varmap) isempty(conservedequations(Catalyst.flatten(rs))) || error("The system has conservation laws but initial conditions were not provided for some species.") end + +""" + iscomplexbalanced(rs::ReactionSystem, parametermap) + +Constructively compute whether a network will have complex-balanced equilibrium +solutions, following the method in van der Schaft et al., [2015](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a dictionary, vector, or tuple ofvariable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...]. +""" + +function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) + if length(parametermap) != numparams(rs) + error("Incorrect number of parameters specified.") + end + + pmap = symmap_to_varmap(rs, parametermap) + pmap = Dict(ModelingToolkit.value(k) => v for (k,v) in pmap) + + sm = speciesmap(rs) + cm = reactioncomplexmap(rs) + complexes, D = reactioncomplexes(rs) + rxns = reactions(rs) + nc = length(complexes); nr = numreactions(rs); nm = numspecies(rs) + + if !all(r->ismassaction(r, rs), rxns) + error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.") + end + + rates = [substitute(rate, pmap) for rate in reactionrates(rs)] + + # Construct kinetic matrix, K + K = zeros(nr, nc) + for c in 1:nc + complex = complexes[c] + for (r, dir) in cm[complex] + rxn = rxns[r] + if dir == -1 + K[r, c] = rates[r] + end + end + end + + L = -D*K + S = netstoichmat(rs) + + # Compute ρ using the matrix-tree theorem + g = incidencematgraph(rs) + R = ratematrix(rs, rates) + ρ = matrixtree(g, R) + + # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T + if all(>(0), ρ) + img = D'*log.(ρ) From a345891cc5de34eeb43000dbdf68c2f7b1834144 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 10:32:47 -0400 Subject: [PATCH 150/446] up --- src/networkapi.jl | 156 ---------------------------------------------- 1 file changed, 156 deletions(-) diff --git a/src/networkapi.jl b/src/networkapi.jl index 9a2278b85a..ac4e76b6ee 100644 --- a/src/networkapi.jl +++ b/src/networkapi.jl @@ -1656,159 +1656,3 @@ end # Checks if a unit consist of exponents with base 1 (and is this unitless). unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) -""" - iscomplexbalanced(rs::ReactionSystem, parametermap) - -Constructively compute whether a network will have complex-balanced equilibrium -solutions, following the method in van der Schaft et al., [2015](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a dictionary, vector, or tuple ofvariable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...]. -""" - -function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) - if length(parametermap) != numparams(rs) - error("Incorrect number of parameters specified.") - end - - pmap = symmap_to_varmap(rs, parametermap) - pmap = Dict(ModelingToolkit.value(k) => v for (k,v) in pmap) - - sm = speciesmap(rs) - cm = reactioncomplexmap(rs) - complexes, D = reactioncomplexes(rs) - rxns = reactions(rs) - nc = length(complexes); nr = numreactions(rs); nm = numspecies(rs) - - if !all(r->ismassaction(r, rs), rxns) - error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.") - end - - rates = [substitute(rate, pmap) for rate in reactionrates(rs)] - - # Construct kinetic matrix, K - K = zeros(nr, nc) - for c in 1:nc - complex = complexes[c] - for (r, dir) in cm[complex] - rxn = rxns[r] - if dir == -1 - K[r, c] = rates[r] - end - end - end - - L = -D*K - S = netstoichmat(rs) - - # Compute ρ using the matrix-tree theorem - g = incidencematgraph(rs) - R = ratematrix(rs, rates) - ρ = matrixtree(g, R) - - # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T - if all(>(0), ρ) - img = D'*log.(ρ) - if rank(S') == rank(hcat(S', img)) return true else return false end - else - return false - end -end - -function iscomplexbalanced(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) - pdict = Dict(parametermap) - iscomplexbalanced(rs, pdict) -end - -function iscomplexbalanced(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) - pdict = Dict(parametermap) - iscomplexbalanced(rs, pdict) -end - -iscomplexbalanced(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") - - -""" - ratematrix(rs::ReactionSystem, parametermap) - - Given a reaction system with n complexes, outputs an n-by-n matrix where R_{ij} is the rate constant of the reaction between complex i and complex j. -""" - -function ratematrix(rs::ReactionSystem, rates::Vector{Float64}) - complexes, D = reactioncomplexes(rs) - n = length(complexes) - rxns = reactions(rs) - ratematrix = zeros(n, n) - - for r in 1:length(rxns) - rxn = rxns[r] - s = findfirst(==(-1), @view D[:,r]) - p = findfirst(==(1), @view D[:,r]) - ratematrix[s, p] = rates[r] - end - ratematrix -end - -function ratematrix(rs::ReactionSystem, parametermap::Dict{Symbol, Float64}) - if length(parametermap) != numparams(rs) - error("The number of reaction rates must be equal to the number of parameters") - end - pmap = symmap_to_varmap(rs, parametermap) - rates = [substitute(rate, pmap) for rate in reactionrates(rs)] - ratematrix(rs, rates) -end - -function ratematrix(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) - pdict = Dict(parametermap) - ratematrix(rs, pdict) -end - -function ratematrix(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) - pdict = Dict(parametermap) - ratematrix(rs, pdict) -end - -ratematrix(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") - -### BELOW: Helper functions for iscomplexbalanced - -function matrixtree(g::SimpleDiGraph, distmx::Matrix) - n = nv(g) - if size(distmx) != (n, n) - error("Size of distance matrix is incorrect") - end - - π = zeros(n) - - if !Graphs.is_connected(g) - ccs = Graphs.connected_components(g) - for cc in ccs - sg, vmap = Graphs.induced_subgraph(g, cc) - distmx_s = distmx[cc, cc] - π_j = matrixtree(sg, distmx_s) - π[cc] = π_j - end - return π - end - - # generate all spanning trees - ug = SimpleGraph(SimpleDiGraph(g)) - trees = collect(Combinatorics.combinations(collect(edges(ug)), n-1)) - trees = SimpleGraph.(trees) - trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) - - # constructed rooted trees for every vertex, compute sum - for v in 1:n - rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir=:in)) for t in trees] - π[v] = sum([treeweight(t, g, distmx) for t in rootedTrees]) - end - - # sum the contributions - return π -end - -function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) - prod = 1 - for e in edges(t) - s = Graphs.src(e); t = Graphs.dst(e) - prod *= distmx[s, t] - end - prod -end From 69725c9ed90749e049dc939966572e2ced883c75 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 10:35:53 -0400 Subject: [PATCH 151/446] added network_analysis --- src/network_analysis.jl | 107 +++ src/networkapi.jl | 1658 --------------------------------------- 2 files changed, 107 insertions(+), 1658 deletions(-) delete mode 100644 src/networkapi.jl diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 7dbec6f11c..4201404602 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -769,3 +769,110 @@ function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T if all(>(0), ρ) img = D'*log.(ρ) + if rank(S') == rank(hcat(S', img)) return true else return false end + else + return false + end +end + +function iscomplexbalanced(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + iscomplexbalanced(rs, pdict) +end + +function iscomplexbalanced(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + iscomplexbalanced(rs, pdict) +end + +iscomplexbalanced(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") + + +""" + ratematrix(rs::ReactionSystem, parametermap) + + Given a reaction system with n complexes, outputs an n-by-n matrix where R_{ij} is the rate constant of the reaction between complex i and complex j. +""" + +function ratematrix(rs::ReactionSystem, rates::Vector{Float64}) + complexes, D = reactioncomplexes(rs) + n = length(complexes) + rxns = reactions(rs) + ratematrix = zeros(n, n) + + for r in 1:length(rxns) + rxn = rxns[r] + s = findfirst(==(-1), @view D[:,r]) + p = findfirst(==(1), @view D[:,r]) + ratematrix[s, p] = rates[r] + end + ratematrix +end + +function ratematrix(rs::ReactionSystem, parametermap::Dict{Symbol, Float64}) + if length(parametermap) != numparams(rs) + error("The number of reaction rates must be equal to the number of parameters") + end + pmap = symmap_to_varmap(rs, parametermap) + rates = [substitute(rate, pmap) for rate in reactionrates(rs)] + ratematrix(rs, rates) +end + +function ratematrix(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + ratematrix(rs, pdict) +end + +function ratematrix(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) + pdict = Dict(parametermap) + ratematrix(rs, pdict) +end + +ratematrix(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") + +### BELOW: Helper functions for iscomplexbalanced + +function matrixtree(g::SimpleDiGraph, distmx::Matrix) + n = nv(g) + if size(distmx) != (n, n) + error("Size of distance matrix is incorrect") + end + + π = zeros(n) + + if !Graphs.is_connected(g) + ccs = Graphs.connected_components(g) + for cc in ccs + sg, vmap = Graphs.induced_subgraph(g, cc) + distmx_s = distmx[cc, cc] + π_j = matrixtree(sg, distmx_s) + π[cc] = π_j + end + return π + end + + # generate all spanning trees + ug = SimpleGraph(SimpleDiGraph(g)) + trees = collect(Combinatorics.combinations(collect(edges(ug)), n-1)) + trees = SimpleGraph.(trees) + trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) + + # constructed rooted trees for every vertex, compute sum + for v in 1:n + rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir=:in)) for t in trees] + π[v] = sum([treeweight(t, g, distmx) for t in rootedTrees]) + end + + # sum the contributions + return π +end + +function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) + prod = 1 + for e in edges(t) + s = Graphs.src(e); t = Graphs.dst(e) + prod *= distmx[s, t] + end + prod +end + diff --git a/src/networkapi.jl b/src/networkapi.jl deleted file mode 100644 index ac4e76b6ee..0000000000 --- a/src/networkapi.jl +++ /dev/null @@ -1,1658 +0,0 @@ - -######### Accessors: ######### - -function filter_nonrxsys(network) - systems = get_systems(network) - rxsystems = ReactionSystem[] - for sys in systems - (sys isa ReactionSystem) && push!(rxsystems, sys) - end - rxsystems -end - -function species(network::ReactionSystem, sts) - [MT.renamespace(network, st) for st in sts] -end - -""" - species(network) - -Given a [`ReactionSystem`](@ref), return a vector of all species defined in the system and -any subsystems that are of type `ReactionSystem`. To get the species and non-species -variables in the system and all subsystems, including non-`ReactionSystem` subsystems, uses -`states(network)`. - -Notes: -- If `ModelingToolkit.get_systems(network)` is non-empty will allocate. -""" -function species(network) - sts = get_species(network) - systems = filter_nonrxsys(network) - isempty(systems) && return sts - unique([sts; reduce(vcat, map(sys -> species(sys, species(sys)), systems))]) -end - -""" - nonspecies(network) - -Return the non-species variables within the network, i.e. those states for which `isspecies -== false`. - -Notes: -- Allocates a new array to store the non-species variables. -""" -function nonspecies(network) - states(network)[(numspecies(network) + 1):end] -end - -""" - reactionparams(network) - -Given a [`ReactionSystem`](@ref), return a vector of all parameters defined -within the system and any subsystems that are of type `ReactionSystem`. To get -the parameters in the system and all subsystems, including non-`ReactionSystem` -subsystems, use `parameters(network)`. - -Notes: -- Allocates and has to calculate these dynamically by comparison for each reaction. -""" -function reactionparams(network) - ps = get_ps(network) - systems = filter_nonrxsys(network) - isempty(systems) && return ps - unique([ps; reduce(vcat, map(sys -> species(sys, reactionparams(sys)), systems))]) -end - -""" - numparams(network) - -Return the total number of parameters within the given system and all subsystems. -""" -function numparams(network) - nps = length(get_ps(network)) - for sys in get_systems(network) - nps += numparams(sys) - end - nps -end - -function namespace_reactions(network::ReactionSystem) - rxs = reactions(network) - isempty(rxs) && return Reaction[] - map(rx -> namespace_equation(rx, network), rxs) -end - -""" - reactions(network) - -Given a [`ReactionSystem`](@ref), return a vector of all `Reactions` in the system. - -Notes: -- If `ModelingToolkit.get_systems(network)` is not empty, will allocate. -""" -function reactions(network) - rxs = get_rxs(network) - systems = filter_nonrxsys(network) - isempty(systems) && (return rxs) - [rxs; reduce(vcat, namespace_reactions.(systems); init = Reaction[])] -end - -""" - speciesmap(network) - -Given a [`ReactionSystem`](@ref), return a Dictionary mapping species that -participate in `Reaction`s to their index within [`species(network)`](@ref). -""" -function speciesmap(network) - nps = get_networkproperties(network) - if isempty(nps.speciesmap) - nps.speciesmap = Dict(S => i for (i, S) in enumerate(species(network))) - end - nps.speciesmap -end - -""" - paramsmap(network) - -Given a [`ReactionSystem`](@ref), return a Dictionary mapping from all -parameters that appear within the system to their index within -`parameters(network)`. -""" -function paramsmap(network) - Dict(p => i for (i, p) in enumerate(parameters(network))) -end - -""" - reactionparamsmap(network) - -Given a [`ReactionSystem`](@ref), return a Dictionary mapping from parameters that -appear within `Reaction`s to their index within [`reactionparams(network)`](@ref). -""" -function reactionparamsmap(network) - Dict(p => i for (i, p) in enumerate(reactionparams(network))) -end - -""" - numspecies(network) - -Return the total number of species within the given [`ReactionSystem`](@ref) and -subsystems that are `ReactionSystem`s. -""" -function numspecies(network) - numspcs = length(get_species(network)) - for sys in get_systems(network) - (sys isa ReactionSystem) && (numspcs += numspecies(sys)) - end - numspcs -end - -""" - numreactions(network) - -Return the total number of reactions within the given [`ReactionSystem`](@ref) -and subsystems that are `ReactionSystem`s. -""" -function numreactions(network) - nr = length(get_rxs(network)) - for sys in get_systems(network) - (sys isa ReactionSystem) && (nr += numreactions(sys)) - end - nr -end - -""" - numreactionparams(network) - -Return the total number of parameters within the given [`ReactionSystem`](@ref) -and subsystems that are `ReactionSystem`s. - -Notes -- If there are no subsystems this will be fast. -- As this calls [`reactionparams`](@ref), it can be slow and will allocate if - there are any subsystems. -""" -function numreactionparams(network) - length(reactionparams(network)) -end - -""" - dependents(rx, network) - -Given a [`Reaction`](@ref) and a [`ReactionSystem`](@ref), return a vector of the -*non-constant* species and variables the reaction rate law depends on. e.g., for - -`k*W, 2X + 3Y --> 5Z + W` - -the returned vector would be `[W(t),X(t),Y(t)]`. - -Notes: -- Allocates -- Does not check for dependents within any subsystems. -- Constant species are not considered dependents since they are internally treated as - parameters. -- If the rate expression depends on a non-species state variable that will be included in - the dependents, i.e. in - ```julia - @parameters k - @variables t V(t) - @species A(t) B(t) C(t) - rx = Reaction(k*V, [A, B], [C]) - @named rs = ReactionSystem([rx], t) - issetequal(dependents(rx, rs), [A,B,V]) == true - ``` -""" -function dependents(rx, network) - if rx.rate isa Number - return rx.substrates - else - rvars = get_variables(rx.rate, states(network)) - return union!(rvars, rx.substrates) - end -end - -""" - dependents(rx, network) - -See documentation for [`dependents`](@ref). -""" -function dependants(rx, network) - dependents(rx, network) -end - -""" - reactionrates(network) - -Given a [`ReactionSystem`](@ref), returns a vector of the symbolic reaction -rates for each reaction. -""" -function reactionrates(rn) - [r.rate for r in reactions(rn)] -end - -""" - substoichmat(rn; sparse=false) - -Returns the substrate stoichiometry matrix, ``S``, with ``S_{i j}`` the stoichiometric -coefficient of the ith substrate within the jth reaction. - -Note: -- Set sparse=true for a sparse matrix representation -- Note that constant species are not considered substrates, but just components that modify - the associated rate law. -""" -function substoichmat(::Type{SparseMatrixCSC{T, Int}}, - rn::ReactionSystem) where {T <: Number} - Is = Int[] - Js = Int[] - Vs = T[] - smap = speciesmap(rn) - for (k, rx) in enumerate(reactions(rn)) - stoich = rx.substoich - for (i, sub) in enumerate(rx.substrates) - isconstant(sub) && continue - push!(Js, k) - push!(Is, smap[sub]) - push!(Vs, stoich[i]) - end - end - sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) -end -function substoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} - smap = speciesmap(rn) - smat = zeros(T, numspecies(rn), numreactions(rn)) - for (k, rx) in enumerate(reactions(rn)) - stoich = rx.substoich - for (i, sub) in enumerate(rx.substrates) - isconstant(sub) && continue - smat[smap[sub], k] = stoich[i] - end - end - smat -end -function substoichmat(rn::ReactionSystem; sparse::Bool = false) - isempty(get_systems(rn)) || error("substoichmat does not currently support subsystems.") - T = reduce(promote_type, eltype(rx.substoich) for rx in reactions(rn)) - (T == Any) && - error("Stoichiometry matrices with symbolic stoichiometry are not supported") - sparse ? substoichmat(SparseMatrixCSC{T, Int}, rn) : substoichmat(Matrix{T}, rn) -end - -""" - prodstoichmat(rn; sparse=false) - -Returns the product stoichiometry matrix, ``P``, with ``P_{i j}`` the stoichiometric -coefficient of the ith product within the jth reaction. - -Note: -- Set sparse=true for a sparse matrix representation -- Note that constant species are not treated as products, but just components that modify - the associated rate law. -""" -function prodstoichmat(::Type{SparseMatrixCSC{T, Int}}, - rn::ReactionSystem) where {T <: Number} - Is = Int[] - Js = Int[] - Vs = T[] - smap = speciesmap(rn) - for (k, rx) in enumerate(reactions(rn)) - stoich = rx.prodstoich - for (i, prod) in enumerate(rx.products) - isconstant(prod) && continue - push!(Js, k) - push!(Is, smap[prod]) - push!(Vs, stoich[i]) - end - end - sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) -end -function prodstoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} - smap = speciesmap(rn) - pmat = zeros(T, numspecies(rn), numreactions(rn)) - for (k, rx) in enumerate(reactions(rn)) - stoich = rx.prodstoich - for (i, prod) in enumerate(rx.products) - isconstant(prod) && continue - pmat[smap[prod], k] = stoich[i] - end - end - pmat -end -function prodstoichmat(rn::ReactionSystem; sparse = false) - isempty(get_systems(rn)) || - error("prodstoichmat does not currently support subsystems.") - - T = reduce(promote_type, eltype(rx.prodstoich) for rx in reactions(rn)) - (T == Any) && - error("Stoichiometry matrices with symbolic stoichiometry are not supported") - sparse ? prodstoichmat(SparseMatrixCSC{T, Int}, rn) : prodstoichmat(Matrix{T}, rn) -end - -""" - netstoichmat(rn, sparse=false) - -Returns the net stoichiometry matrix, ``N``, with ``N_{i j}`` the net stoichiometric -coefficient of the ith species within the jth reaction. - -Notes: -- Set sparse=true for a sparse matrix representation -- Caches the matrix internally within `rn` so subsequent calls are fast. -- Note that constant species are not treated as reactants, but just components that modify - the associated rate law. As such they do not contribute to the net stoichiometry matrix. -""" -function netstoichmat(::Type{SparseMatrixCSC{T, Int}}, - rn::ReactionSystem) where {T <: Number} - Is = Int[] - Js = Int[] - Vs = Vector{T}() - smap = speciesmap(rn) - for (k, rx) in pairs(reactions(rn)) - for (spec, coef) in rx.netstoich - isconstant(spec) && continue - push!(Js, k) - push!(Is, smap[spec]) - push!(Vs, coef) - end - end - sparse(Is, Js, Vs, numspecies(rn), numreactions(rn)) -end -function netstoichmat(::Type{Matrix{T}}, rn::ReactionSystem) where {T <: Number} - smap = speciesmap(rn) - nmat = zeros(T, numspecies(rn), numreactions(rn)) - for (k, rx) in pairs(reactions(rn)) - for (spec, coef) in rx.netstoich - isconstant(spec) && continue - nmat[smap[spec], k] = coef - end - end - nmat -end - -netstoichtype(::Vector{Pair{S, T}}) where {S, T} = T - -function netstoichmat(rn::ReactionSystem; sparse = false) - isempty(get_systems(rn)) || - error("netstoichmat does not currently support subsystems, please create a flattened system before calling.") - - nps = get_networkproperties(rn) - - # if it is already calculated and has the right type - !isempty(nps.netstoichmat) && (sparse == issparse(nps.netstoichmat)) && - (return nps.netstoichmat) - - # identify a common stoichiometry type - T = reduce(promote_type, netstoichtype(rx.netstoich) for rx in reactions(rn)) - (T == Any) && - error("Stoichiometry matrices are not supported with symbolic stoichiometry.") - - if sparse - nsmat = netstoichmat(SparseMatrixCSC{T, Int}, rn) - else - nsmat = netstoichmat(Matrix{T}, rn) - end - - # only cache if it is integer - if T == Int - nps.netstoichmat = nsmat - end - - nsmat -end - -# the following function is adapted from SymbolicUtils.jl v.19 -# later on (Spetember 2023) modified by Torkel and Shashi (now assumes input not on polynomial form, which is handled elsewhere, previous version failed in these cases anyway). -# Copyright (c) 2020: Shashi Gowda, Yingbo Ma, Mason Protter, Julia Computing. -# MIT license -""" - to_multivariate_poly(polyeqs::AbstractVector{BasicSymbolic{Real}}) - -Convert the given system of polynomial equations to multivariate polynomial representation. -For example, this can be used in HomotopyContinuation.jl functions. -""" -function to_multivariate_poly(polyeqs::AbstractVector{Symbolics.BasicSymbolic{Real}}) - @assert length(polyeqs)>=1 "At least one expression must be passed to `multivariate_poly`." - - pvar2sym, sym2term = SymbolicUtils.get_pvar2sym(), SymbolicUtils.get_sym2term() - ps = map(polyeqs) do x - if istree(x) && operation(x) == (/) - error("We should not be able to get here, please contact the package authors.") - else - PolyForm(x, pvar2sym, sym2term).p - end - end - - ps -end -""" - setdefaults!(rn, newdefs) - -Sets the default (initial) values of parameters and species in the -`ReactionSystem`, `rn`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -setdefaults!(sir, [:S => 999.0, :I => 1.0, :R => 1.0, :β => 1e-4, :ν => .01]) - -# or -@parameter β ν -@variables t -@species S(t) I(t) R(t) -setdefaults!(sir, [S => 999.0, I => 1.0, R => 0.0, β => 1e-4, ν => .01]) -``` -gives initial/default values to each of `S`, `I` and `β` - -Notes: -- Can not be used to set default values for species, variables or parameters of - subsystems or constraint systems. Either set defaults for those systems - directly, or [`flatten`](@ref) to collate them into one system before setting - defaults. -- Defaults can be specified in any iterable container of symbols to value pairs - or symbolics to value pairs. -""" -function setdefaults!(rn, newdefs) - defs = eltype(newdefs) <: Pair{Symbol} ? symmap_to_varmap(rn, newdefs) : newdefs - rndefs = MT.get_defaults(rn) - for (var, val) in defs - rndefs[value(var)] = value(val) - end - nothing -end - -function __unpacksys(rn) - ex = :(begin end) - for key in keys(get_var_to_name(rn)) - var = MT.getproperty(rn, key, namespace = false) - push!(ex.args, :($key = $var)) - end - ex -end - -""" - @unpacksys sys::ModelingToolkit.AbstractSystem - -Loads all species, variables, parameters, and observables defined in `sys` as -variables within the calling module. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -@unpacksys sir -``` -will load the symbolic variables, `S`, `I`, `R`, `ν` and `β`. - -Notes: -- Can not be used to load species, variables, or parameters of subsystems or - constraints. Either call `@unpacksys` on those systems directly, or - [`flatten`](@ref) to collate them into one system before calling. -- Note that this places symbolic variables within the calling module's scope, so - calling from a function defined in a script or the REPL will still result in - the symbolic variables being defined in the `Main` module. -""" -macro unpacksys(rn) - quote - ex = Catalyst.__unpacksys($(esc(rn))) - Base.eval($(__module__), ex) - end -end - -# convert symbol of the form :sys.a.b.c to a symbolic a.b.c -function _symbol_to_var(sys, sym) - if hasproperty(sys, sym) - var = getproperty(sys, sym, namespace = false) - else - strs = split(String(sym), "₊") # need to check if this should be split of not!!! - if length(strs) > 1 - var = getproperty(sys, Symbol(strs[1]), namespace = false) - for str in view(strs, 2:length(strs)) - var = getproperty(var, Symbol(str), namespace = true) - end - else - throw(ArgumentError("System $(nameof(sys)): variable $sym does not exist")) - end - end - var -end - -""" - symmap_to_varmap(sys, symmap) - -Given a system and map of `Symbol`s to values, generates a map from -corresponding symbolic variables/parameters to the values that can be used to -pass initial conditions and parameter mappings. - -For example, -```julia -sir = @reaction_network sir begin - β, S + I --> 2I - ν, I --> R -end -subsys = @reaction_network subsys begin - k, A --> B -end -@named sys = compose(sir, [subsys]) -``` -gives -``` -Model sys with 3 equations -States (5): - S(t) - I(t) - R(t) - subsys₊A(t) - subsys₊B(t) -Parameters (3): - β - ν - subsys₊k -``` -to specify initial condition and parameter mappings from *symbols* we can use -```julia -symmap = [:S => 1.0, :I => 1.0, :R => 1.0, :subsys₊A => 1.0, :subsys₊B => 1.0] -u0map = symmap_to_varmap(sys, symmap) -pmap = symmap_to_varmap(sys, [:β => 1.0, :ν => 1.0, :subsys₊k => 1.0]) -``` -`u0map` and `pmap` can then be used as input to various problem types. - -Notes: -- Any `Symbol`, `sym`, within `symmap` must be a valid field of `sys`. i.e. - `sys.sym` must be defined. -""" -function symmap_to_varmap(sys, symmap::Tuple) - if all(p -> p isa Pair{Symbol}, symmap) - return ((_symbol_to_var(sys, sym) => val for (sym, val) in symmap)...,) - else # if not all entries map a symbol to value pass through - return symmap - end -end - -function symmap_to_varmap(sys, symmap::AbstractArray{Pair{Symbol, T}}) where {T} - [_symbol_to_var(sys, sym) => val for (sym, val) in symmap] -end - -function symmap_to_varmap(sys, symmap::Dict{Symbol, T}) where {T} - Dict(_symbol_to_var(sys, sym) => val for (sym, val) in symmap) -end - -# don't permute any other types and let varmap_to_vars handle erroring -symmap_to_varmap(sys, symmap) = symmap -#error("symmap_to_varmap requires a Dict, AbstractArray or Tuple to map Symbols to values.") - -######################## reaction complexes and reaction rates ############################### - -""" - reset_networkproperties!(rn::ReactionSystem) - -Clears the cache of various properties (like the netstoichiometry matrix). Use if such -properties need to be recalculated for some reason. -""" -function reset_networkproperties!(rn::ReactionSystem) - reset!(get_networkproperties(rn)) - nothing -end - -# get the species indices and stoichiometry while filtering out constant species. -function filter_constspecs(specs, stoich::AbstractVector{V}, smap) where {V <: Integer} - isempty(specs) && (return Vector{Int}(), Vector{V}()) - - if any(isconstant, specs) - ids = Vector{Int}() - filtered_stoich = Vector{V}() - for (i, s) in enumerate(specs) - if !isconstant(s) - push!(ids, smap[s]) - push!(filtered_stoich, stoich[i]) - end - end - else - ids = map(Base.Fix1(getindex, smap), specs) - filtered_stoich = copy(stoich) - end - ids, filtered_stoich -end - -""" - reactioncomplexmap(rn::ReactionSystem) - -Find each [`ReactionComplex`](@ref) within the specified system, constructing a mapping from -the complex to vectors that indicate which reactions it appears in as substrates and -products. - -Notes: -- Each [`ReactionComplex`](@ref) is mapped to a vector of pairs, with each pair having the - form `reactionidx => ± 1`, where `-1` indicates the complex appears as a substrate and - `+1` as a product in the reaction with integer label `reactionidx`. -- Constant species are ignored as part of a complex. i.e. if species `A` is constant then - the reaction `A + B --> C + D` is considered to consist of the complexes `B` and `C + D`. - Likewise `A --> B` would be treated as the same as `0 --> B`. -""" -function reactioncomplexmap(rn::ReactionSystem) - isempty(get_systems(rn)) || - error("reactioncomplexmap does not currently support subsystems.") - - # check if previously calculated and hence cached - nps = get_networkproperties(rn) - !isempty(nps.complextorxsmap) && return nps.complextorxsmap - complextorxsmap = nps.complextorxsmap - - rxs = reactions(rn) - smap = speciesmap(rn) - numreactions(rn) > 0 || - error("There must be at least one reaction to find reaction complexes.") - for (i, rx) in enumerate(rxs) - subids, substoich = filter_constspecs(rx.substrates, rx.substoich, smap) - subrc = sort!(ReactionComplex(subids, substoich)) - if haskey(complextorxsmap, subrc) - push!(complextorxsmap[subrc], i => -1) - else - complextorxsmap[subrc] = [i => -1] - end - - prodids, prodstoich = filter_constspecs(rx.products, rx.prodstoich, smap) - prodrc = sort!(ReactionComplex(prodids, prodstoich)) - if haskey(complextorxsmap, prodrc) - push!(complextorxsmap[prodrc], i => 1) - else - complextorxsmap[prodrc] = [i => 1] - end - end - complextorxsmap -end - -function reactioncomplexes(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, - complextorxsmap) - complexes = collect(keys(complextorxsmap)) - Is = Int[] - Js = Int[] - Vs = Int[] - for (i, c) in enumerate(complexes) - for (j, σ) in complextorxsmap[c] - push!(Is, i) - push!(Js, j) - push!(Vs, σ) - end - end - B = sparse(Is, Js, Vs, length(complexes), numreactions(rn)) - complexes, B -end -function reactioncomplexes(::Type{Matrix{Int}}, rn::ReactionSystem, complextorxsmap) - complexes = collect(keys(complextorxsmap)) - B = zeros(Int, length(complexes), numreactions(rn)) - for (i, c) in enumerate(complexes) - for (j, σ) in complextorxsmap[c] - B[i, j] = σ - end - end - complexes, B -end - -@doc raw""" - reactioncomplexes(network::ReactionSystem; sparse=false) - -Calculate the reaction complexes and complex incidence matrix for the given -[`ReactionSystem`](@ref). - -Notes: -- returns a pair of a vector of [`ReactionComplex`](@ref)s and the complex incidence matrix. -- An empty [`ReactionComplex`](@ref) denotes the null (∅) state (from reactions like ∅ -> A - or A -> ∅). -- Constant species are ignored in generating a reaction complex. i.e. if A is constant then - A --> B consists of the complexes ∅ and B. -- The complex incidence matrix, ``B``, is number of complexes by number of reactions with -```math -B_{i j} = \begin{cases} --1, &\text{if the i'th complex is the substrate of the j'th reaction},\\ -1, &\text{if the i'th complex is the product of the j'th reaction},\\ -0, &\text{otherwise.} -\end{cases} -``` -- Set sparse=true for a sparse matrix representation of the incidence matrix -""" -function reactioncomplexes(rn::ReactionSystem; sparse = false) - isempty(get_systems(rn)) || - error("reactioncomplexes does not currently support subsystems.") - nps = get_networkproperties(rn) - if isempty(nps.complexes) || (sparse != issparse(nps.complexes)) - complextorxsmap = reactioncomplexmap(rn) - nps.complexes, nps.incidencemat = if sparse - reactioncomplexes(SparseMatrixCSC{Int, Int}, rn, complextorxsmap) - else - reactioncomplexes(Matrix{Int}, rn, complextorxsmap) - end - end - nps.complexes, nps.incidencemat -end - -""" - incidencemat(rn::ReactionSystem; sparse=false) - -Calculate the incidence matrix of `rn`, see [`reactioncomplexes`](@ref). - -Notes: -- Is cached in `rn` so that future calls, assuming the same sparsity, will also be fast. -""" -incidencemat(rn::ReactionSystem; sparse = false) = reactioncomplexes(rn; sparse)[2] - -function complexstoichmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, rcs) - Is = Int[] - Js = Int[] - Vs = Int[] - for (i, rc) in enumerate(rcs) - for rcel in rc - push!(Is, rcel.speciesid) - push!(Js, i) - push!(Vs, rcel.speciesstoich) - end - end - Z = sparse(Is, Js, Vs, numspecies(rn), length(rcs)) -end -function complexstoichmat(::Type{Matrix{Int}}, rn::ReactionSystem, rcs) - Z = zeros(Int, numspecies(rn), length(rcs)) - for (i, rc) in enumerate(rcs) - for rcel in rc - Z[rcel.speciesid, i] = rcel.speciesstoich - end - end - Z -end - -""" - complexstoichmat(network::ReactionSystem; sparse=false) - -Given a [`ReactionSystem`](@ref) and vector of reaction complexes, return a -matrix with positive entries of size number of species by number of complexes, -where the non-zero positive entries in the kth column denote stoichiometric -coefficients of the species participating in the kth reaction complex. - -Notes: -- Set sparse=true for a sparse matrix representation -""" -function complexstoichmat(rn::ReactionSystem; sparse = false) - isempty(get_systems(rn)) || - error("complexstoichmat does not currently support subsystems.") - nps = get_networkproperties(rn) - if isempty(nps.complexstoichmat) || (sparse != issparse(nps.complexstoichmat)) - nps.complexstoichmat = if sparse - complexstoichmat(SparseMatrixCSC{Int, Int}, rn, keys(reactioncomplexmap(rn))) - else - complexstoichmat(Matrix{Int}, rn, keys(reactioncomplexmap(rn))) - end - end - nps.complexstoichmat -end - -function complexoutgoingmat(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, B) - n = size(B, 2) - rows = rowvals(B) - vals = nonzeros(B) - Is = Int[] - Js = Int[] - Vs = Int[] - sizehint!(Is, div(length(vals), 2)) - sizehint!(Js, div(length(vals), 2)) - sizehint!(Vs, div(length(vals), 2)) - for j in 1:n - for i in nzrange(B, j) - if vals[i] != one(eltype(vals)) - push!(Is, rows[i]) - push!(Js, j) - push!(Vs, vals[i]) - end - end - end - sparse(Is, Js, Vs, size(B, 1), size(B, 2)) -end -function complexoutgoingmat(::Type{Matrix{Int}}, rn::ReactionSystem, B) - Δ = copy(B) - for (I, b) in pairs(Δ) - (b == 1) && (Δ[I] = 0) - end - Δ -end - -@doc raw""" - complexoutgoingmat(network::ReactionSystem; sparse=false) - -Given a [`ReactionSystem`](@ref) and complex incidence matrix, ``B``, return a -matrix of size num of complexes by num of reactions that identifies substrate -complexes. - -Notes: -- The complex outgoing matrix, ``\Delta``, is defined by -```math -\Delta_{i j} = \begin{cases} - = 0, &\text{if } B_{i j} = 1, \\ - = B_{i j}, &\text{otherwise.} -\end{cases} -``` -- Set sparse=true for a sparse matrix representation -""" -function complexoutgoingmat(rn::ReactionSystem; sparse = false) - isempty(get_systems(rn)) || - error("complexoutgoingmat does not currently support subsystems.") - nps = get_networkproperties(rn) - if isempty(nps.complexoutgoingmat) || (sparse != issparse(nps.complexoutgoingmat)) - B = reactioncomplexes(rn, sparse = sparse)[2] - nps.complexoutgoingmat = if sparse - complexoutgoingmat(SparseMatrixCSC{Int, Int}, rn, B) - else - complexoutgoingmat(Matrix{Int}, rn, B) - end - end - nps.complexoutgoingmat -end - -function incidencematgraph(incidencemat::Matrix{Int}) - @assert all(∈([-1, 0, 1]), incidencemat) - n = size(incidencemat, 1) # no. of nodes/complexes - graph = Graphs.DiGraph(n) - for col in eachcol(incidencemat) - src = 0 - dst = 0 - for i in eachindex(col) - (col[i] == -1) && (src = i) - (col[i] == 1) && (dst = i) - (src != 0) && (dst != 0) && break - end - Graphs.add_edge!(graph, src, dst) - end - return graph -end -function incidencematgraph(incidencemat::SparseMatrixCSC{Int, Int}) - @assert all(∈([-1, 0, 1]), incidencemat) - m, n = size(incidencemat) - graph = Graphs.DiGraph(m) - rows = rowvals(incidencemat) - vals = nonzeros(incidencemat) - for j in 1:n - inds = nzrange(incidencemat, j) - row = rows[inds] - val = vals[inds] - if val[1] == -1 - Graphs.add_edge!(graph, row[1], row[2]) - else - Graphs.add_edge!(graph, row[2], row[1]) - end - end - return graph -end - -""" - incidencematgraph(rn::ReactionSystem) - -Construct a directed simple graph where nodes correspond to reaction complexes and directed -edges to reactions converting between two complexes. - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -complexes,incidencemat = reactioncomplexes(sir) -incidencematgraph(sir) -``` -""" -function incidencematgraph(rn::ReactionSystem) - nps = get_networkproperties(rn) - if Graphs.nv(nps.incidencegraph) == 0 - isempty(nps.incidencemat) && - error("Please call reactioncomplexes(rn) first to construct the incidence matrix.") - nps.incidencegraph = incidencematgraph(nps.incidencemat) - end - nps.incidencegraph -end - -linkageclasses(incidencegraph) = Graphs.connected_components(incidencegraph) - -""" - linkageclasses(rn::ReactionSystem) - -Given the incidence graph of a reaction network, return a vector of the -connected components of the graph (i.e. sub-groups of reaction complexes that -are connected in the incidence graph). - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -complexes,incidencemat = reactioncomplexes(sir) -linkageclasses(sir) -``` -gives -```julia -2-element Vector{Vector{Int64}}: - [1, 2] - [3, 4] -``` -""" -function linkageclasses(rn::ReactionSystem) - nps = get_networkproperties(rn) - if isempty(nps.linkageclasses) - nps.linkageclasses = linkageclasses(incidencematgraph(rn)) - end - nps.linkageclasses -end - -@doc raw""" - deficiency(rn::ReactionSystem) - -Calculate the deficiency of a reaction network. - -Here the deficiency, ``\delta``, of a network with ``n`` reaction complexes, -``\ell`` linkage classes and a rank ``s`` stoichiometric matrix is - -```math -\delta = n - \ell - s -``` - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -rcs,incidencemat = reactioncomplexes(sir) -δ = deficiency(sir) -``` -""" -function deficiency(rn::ReactionSystem) - nps = get_networkproperties(rn) - conservationlaws(rn) - r = nps.rank - ig = incidencematgraph(rn) - lc = linkageclasses(rn) - nps.deficiency = Graphs.nv(ig) - length(lc) - r - nps.deficiency -end - -function subnetworkmapping(linkageclass, allrxs, complextorxsmap, p) - rxinds = sort!(collect(Set(rxidx for rcidx in linkageclass - for rxidx in complextorxsmap[rcidx]))) - rxs = allrxs[rxinds] - specset = Set(s for rx in rxs for s in rx.substrates if !isconstant(s)) - for rx in rxs - for product in rx.products - !isconstant(product) && push!(specset, product) - end - end - specs = collect(specset) - newps = Vector{eltype(p)}() - for rx in rxs - Symbolics.get_variables!(newps, rx.rate, p) - end - rxs, specs, newps # reactions and species involved in reactions of subnetwork -end - -""" - subnetworks(rn::ReactionSystem) - -Find subnetworks corresponding to each linkage class of the reaction network. - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -complexes,incidencemat = reactioncomplexes(sir) -subnetworks(sir) -``` -""" -function subnetworks(rs::ReactionSystem) - isempty(get_systems(rs)) || error("subnetworks does not currently support subsystems.") - lcs = linkageclasses(rs) - rxs = reactions(rs) - p = parameters(rs) - t = get_iv(rs) - spatial_ivs = get_sivs(rs) - complextorxsmap = [map(first, rcmap) for rcmap in values(reactioncomplexmap(rs))] - subnetworks = Vector{ReactionSystem}() - for i in 1:length(lcs) - reacs, specs, newps = subnetworkmapping(lcs[i], rxs, complextorxsmap, p) - newname = Symbol(nameof(rs), "_", i) - push!(subnetworks, - ReactionSystem(reacs, t, specs, newps; name = newname, spatial_ivs)) - end - subnetworks -end - -""" - linkagedeficiencies(network::ReactionSystem) - -Calculates the deficiency of each sub-reaction network within `network`. - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -rcs,incidencemat = reactioncomplexes(sir) -linkage_deficiencies = linkagedeficiencies(sir) -``` -""" -function linkagedeficiencies(rs::ReactionSystem) - lcs = linkageclasses(rs) - subnets = subnetworks(rs) - δ = zeros(Int, length(lcs)) - for (i, subnet) in enumerate(subnets) - conservationlaws(subnet) - nps = get_networkproperties(subnet) - δ[i] = length(lcs[i]) - 1 - nps.rank - end - δ -end - -""" - isreversible(rn::ReactionSystem) - -Given a reaction network, returns if the network is reversible or not. - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -rcs,incidencemat = reactioncomplexes(sir) -isreversible(sir) -``` -""" -function isreversible(rn::ReactionSystem) - ig = incidencematgraph(rn) - Graphs.reverse(ig) == ig -end - -""" - isweaklyreversible(rn::ReactionSystem, subnetworks) - -Determine if the reaction network with the given subnetworks is weakly reversible or not. - -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - -For example, -```julia -sir = @reaction_network SIR begin - β, S + I --> 2I - ν, I --> R -end -rcs,incidencemat = reactioncomplexes(sir) -subnets = subnetworks(rn) -isweaklyreversible(rn, subnets) -``` -""" -function isweaklyreversible(rn::ReactionSystem, subnets) - im = get_networkproperties(rn).incidencemat - isempty(im) && - error("Error, please call reactioncomplexes(rn::ReactionSystem) to ensure the incidence matrix has been cached.") - sparseig = issparse(im) - for subnet in subnets - nps = get_networkproperties(subnet) - isempty(nps.incidencemat) && reactioncomplexes(subnet; sparse = sparseig) - end - all(Graphs.is_strongly_connected ∘ incidencematgraph, subnets) -end - -############################################################################################ -######################## conservation laws ############################### - -""" - conservedequations(rn::ReactionSystem) - -Calculate symbolic equations from conservation laws, writing dependent variables as -functions of independent variables and the conservation law constants. - -Notes: -- Caches the resulting equations in `rn`, so will be fast on subsequent calls. - -Examples: -```@repl -rn = @reaction_network begin - k, A + B --> C - k2, C --> A + B - end -conservedequations(rn) -``` -gives -``` -2-element Vector{Equation}: - B(t) ~ A(t) + Γ[1] - C(t) ~ Γ[2] - A(t) -``` -""" -function conservedequations(rn::ReactionSystem) - conservationlaws(rn) - nps = get_networkproperties(rn) - nps.conservedeqs -end - -""" - conservationlaw_constants(rn::ReactionSystem) - -Calculate symbolic equations from conservation laws, writing the conservation law constants -in terms of the dependent and independent variables. - -Notes: -- Caches the resulting equations in `rn`, so will be fast on subsequent calls. - -Examples: -```@julia -rn = @reaction_network begin - k, A + B --> C - k2, C --> A + B - end -conservationlaw_constants(rn) -``` -gives -``` -2-element Vector{Equation}: - Γ[1] ~ B(t) - A(t) - Γ[2] ~ A(t) + C(t) -``` -""" -function conservationlaw_constants(rn::ReactionSystem) - conservationlaws(rn) - nps = get_networkproperties(rn) - nps.constantdefs -end - -""" - conservationlaws(netstoichmat::AbstractMatrix)::Matrix - -Given the net stoichiometry matrix of a reaction system, computes a matrix of -conservation laws, each represented as a row in the output. -""" -function conservationlaws(nsm::T; col_order = nothing) where {T <: AbstractMatrix} - - # compute the left nullspace over the integers - N = MT.nullspace(nsm'; col_order) - - # if all coefficients for a conservation law are negative, make positive - for Nrow in eachcol(N) - all(r -> r <= 0, Nrow) && (Nrow .*= -1) - end - - # check we haven't overflowed - iszero(N' * nsm) || error("Calculation of the conservation law matrix was inaccurate, " - * "likely due to numerical overflow. Please use a larger integer " - * "type like Int128 or BigInt for the net stoichiometry matrix.") - - T(N') -end - -function cache_conservationlaw_eqs!(rn::ReactionSystem, N::AbstractMatrix, col_order) - nullity = size(N, 1) - r = numspecies(rn) - nullity # rank of the netstoichmat - sts = species(rn) - indepidxs = col_order[begin:r] - indepspecs = sts[indepidxs] - depidxs = col_order[(r + 1):end] - depspecs = sts[depidxs] - constants = MT.unwrap.(MT.scalarize((@parameters Γ[1:nullity])[1])) - - conservedeqs = Equation[] - constantdefs = Equation[] - for (i, depidx) in enumerate(depidxs) - scaleby = (N[i, depidx] != 1) ? N[i, depidx] : one(eltype(N)) - (scaleby != 0) || error("Error, found a zero in the conservation law matrix where " - * - "one was not expected.") - coefs = @view N[i, indepidxs] - terms = sum(p -> p[1] / scaleby * p[2], zip(coefs, indepspecs)) - eq = depspecs[i] ~ constants[i] - terms - push!(conservedeqs, eq) - eq = constants[i] ~ depspecs[i] + terms - push!(constantdefs, eq) - end - - # cache in the system - nps = get_networkproperties(rn) - nps.rank = r - nps.nullity = nullity - nps.indepspecs = Set(indepspecs) - nps.depspecs = Set(depspecs) - nps.conservedeqs = conservedeqs - nps.constantdefs = constantdefs - - nothing -end - -""" - conservationlaws(rs::ReactionSystem) - -Return the conservation law matrix of the given `ReactionSystem`, calculating it if it is -not already stored within the system, or returning an alias to it. - -Notes: -- The first time being called it is calculated and cached in `rn`, subsequent calls should - be fast. -""" -function conservationlaws(rs::ReactionSystem) - nps = get_networkproperties(rs) - !isempty(nps.conservationmat) && (return nps.conservationmat) - nsm = netstoichmat(rs) - nps.conservationmat = conservationlaws(nsm; col_order = nps.col_order) - cache_conservationlaw_eqs!(rs, nps.conservationmat, nps.col_order) - nps.conservationmat -end - -""" - conservedquantities(state, cons_laws) - -Compute conserved quantities for a system with the given conservation laws. -""" -conservedquantities(state, cons_laws) = cons_laws * state - - -# If u0s are not given while conservation laws are present, throws an error. -# Used in HomotopyContinuation and BifurcationKit extensions. -# Currently only checks if any u0s are given -# (not whether these are enough for computing conserved quantitites, this will yield a less informative error). -function conservationlaw_errorcheck(rs, pre_varmap) - vars_with_vals = Set(p[1] for p in pre_varmap) - any(s -> s in vars_with_vals, species(rs)) && return - isempty(conservedequations(Catalyst.flatten(rs))) || - error("The system has conservation laws but initial conditions were not provided for some species.") -end - -######################## reaction network operators ####################### - -""" - ==(rx1::Reaction, rx2::Reaction) - -Tests whether two [`Reaction`](@ref)s are identical. - -Notes: -- Ignores the order in which stoichiometry components are listed. -- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be - considered different than `(A+1)^2`. -""" -function (==)(rx1::Reaction, rx2::Reaction) - isequal(rx1.rate, rx2.rate) || return false - issetequal(zip(rx1.substrates, rx1.substoich), zip(rx2.substrates, rx2.substoich)) || - return false - issetequal(zip(rx1.products, rx1.prodstoich), zip(rx2.products, rx2.prodstoich)) || - return false - issetequal(rx1.netstoich, rx2.netstoich) || return false - rx1.only_use_rate == rx2.only_use_rate -end - -function hash(rx::Reaction, h::UInt) - h = Base.hash(rx.rate, h) - for s in Iterators.flatten((rx.substrates, rx.products)) - h ⊻= hash(s) - end - for s in Iterators.flatten((rx.substoich, rx.prodstoich)) - h ⊻= hash(s) - end - for s in rx.netstoich - h ⊻= hash(s) - end - Base.hash(rx.only_use_rate, h) -end - -""" - isequivalent(rn1::ReactionSystem, rn2::ReactionSystem; ignorenames = true) - -Tests whether the underlying species, parameters and reactions are the same in -the two [`ReactionSystem`](@ref)s. Ignores the names of the systems in testing -equality. - -Notes: -- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be - considered different than `(A+1)^2`. -- Does not include `defaults` in determining equality. -""" -function isequivalent(rn1::ReactionSystem, rn2::ReactionSystem; ignorenames = true) - if !ignorenames - (nameof(rn1) == nameof(rn2)) || return false - end - - (get_combinatoric_ratelaws(rn1) == get_combinatoric_ratelaws(rn2)) || return false - isequal(get_iv(rn1), get_iv(rn2)) || return false - issetequal(get_sivs(rn1), get_sivs(rn2)) || return false - issetequal(get_states(rn1), get_states(rn2)) || return false - issetequal(get_ps(rn1), get_ps(rn2)) || return false - issetequal(MT.get_observed(rn1), MT.get_observed(rn2)) || return false - issetequal(get_eqs(rn1), get_eqs(rn2)) || return false - - # subsystems - (length(get_systems(rn1)) == length(get_systems(rn2))) || return false - issetequal(get_systems(rn1), get_systems(rn2)) || return false - - true -end - -function isequal_ignore_names(rn1, rn2) - Base.depwarn("Catalyst.isequal_ignore_names has been deprecated. Use isequivalent(rn1, rn2) instead.", - :isequal_ignore_names; force = true) - isequivalent(rn1, rn2) -end - -""" - ==(rn1::ReactionSystem, rn2::ReactionSystem) - -Tests whether the underlying species, parameters and reactions are the same in -the two [`ReactionSystem`](@ref)s. Requires the systems to have the same names -too. - -Notes: -- *Does not* currently simplify rates, so a rate of `A^2+2*A+1` would be - considered different than `(A+1)^2`. -- Does not include `defaults` in determining equality. -""" -function (==)(rn1::ReactionSystem, rn2::ReactionSystem) - isequivalent(rn1, rn2; ignorenames = false) -end - -######################## functions to extend a network #################### - -""" - make_empty_network(; iv=DEFAULT_IV, name=gensym(:ReactionSystem)) - -Construct an empty [`ReactionSystem`](@ref). `iv` is the independent variable, -usually time, and `name` is the name to give the `ReactionSystem`. -""" -function make_empty_network(; iv = DEFAULT_IV, name = gensym(:ReactionSystem)) - ReactionSystem(Reaction[], iv, [], []; name = name) -end - -""" - addspecies!(network::ReactionSystem, s::Symbolic; disablechecks=false) - -Given a [`ReactionSystem`](@ref), add the species corresponding to the variable -`s` to the network (if it is not already defined). Returns the integer id of the -species within the system. - -Notes: -- `disablechecks` will disable checking for whether the passed in variable is - already defined, which is useful when adding many new variables to the system. - *Do not disable checks* unless you are sure the passed in variable is a new - variable, as this will potentially leave the system in an undefined state. -""" -function addspecies!(network::ReactionSystem, s::Symbolic; disablechecks = false) - reset_networkproperties!(network) - - isconstant(s) && error("Constant species should be added via addparams!.") - isspecies(s) || - error("$s is not a valid symbolic species. Please use @species to declare it.") - - # we don't check subsystems since we will add it to the top-level system... - curidx = disablechecks ? nothing : findfirst(S -> isequal(S, s), get_states(network)) - if curidx === nothing - push!(get_states(network), s) - sort!(get_states(network); by = !isspecies) - push!(get_species(network), s) - MT.process_variables!(get_var_to_name(network), get_defaults(network), [s]) - return length(get_species(network)) - else - return curidx - end -end - -""" - addspecies!(network::ReactionSystem, s::Num; disablechecks=false) - -Given a [`ReactionSystem`](@ref), add the species corresponding to the -variable `s` to the network (if it is not already defined). Returns the -integer id of the species within the system. - -- `disablechecks` will disable checking for whether the passed in variable is - already defined, which is useful when adding many new variables to the system. - *Do not disable checks* unless you are sure the passed in variable is a new - variable, as this will potentially leave the system in an undefined state. -""" -function addspecies!(network::ReactionSystem, s::Num; disablechecks = false) - addspecies!(network, value(s), disablechecks = disablechecks) -end - -""" - reorder_states!(rn, neworder) - -Given a [`ReactionSystem`](@ref) and a vector `neworder`, reorders the states of `rn`, i.e. -`get_states(rn)`, according to `neworder`. - -Notes: -- Currently only supports `ReactionSystem`s without subsystems. -""" -function reorder_states!(rn, neworder) - reset_networkproperties!(rn) - - permute!(get_states(rn), neworder) - if !issorted(get_states(rn); by = !isspecies) - @warn "New ordering has resulted in a non-species state preceding a species state. This is not allowed so states have been resorted to ensure species precede non-species." - sort!(get_states(rn); by = !isspecies) - end - get_species(rn) .= Iterators.filter(isspecies, get_states(rn)) - nothing -end - -""" - addparam!(network::ReactionSystem, p::Symbolic; disablechecks=false) - -Given a [`ReactionSystem`](@ref), add the parameter corresponding to the -variable `p` to the network (if it is not already defined). Returns the integer -id of the parameter within the system. - -- `disablechecks` will disable checking for whether the passed in variable is - already defined, which is useful when adding many new variables to the system. - *Do not disable checks* unless you are sure the passed in variable is a new - variable, as this will potentially leave the system in an undefined state. -""" -function addparam!(network::ReactionSystem, p::Symbolic; disablechecks = false) - reset_networkproperties!(network) - - # we don't check subsystems since we will add it to the top-level system... - if istree(p) && !(operation(p) isa Symbolic && !istree(operation(p))) - error("If the passed in parameter is an expression, it must correspond to an underlying Variable.") - end - curidx = disablechecks ? nothing : findfirst(S -> isequal(S, p), get_ps(network)) - if curidx === nothing - push!(get_ps(network), p) - MT.process_variables!(get_var_to_name(network), get_defaults(network), [p]) - return length(get_ps(network)) - else - return curidx - end -end - -""" - addparam!(network::ReactionSystem, p::Num; disablechecks=false) - -Given a [`ReactionSystem`](@ref), add the parameter corresponding to the -variable `p` to the network (if it is not already defined). Returns the -integer id of the parameter within the system. - -- `disablechecks` will disable checking for whether the passed in variable is - already defined, which is useful when adding many new variables to the system. - *Do not disable checks* unless you are sure the passed in variable is a new - variable, as this will potentially leave the system in an undefined state. -""" -function addparam!(network::ReactionSystem, p::Num; disablechecks = false) - addparam!(network, value(p); disablechecks = disablechecks) -end - -""" - addreaction!(network::ReactionSystem, rx::Reaction) - -Add the passed in reaction to the [`ReactionSystem`](@ref). Returns the -integer id of `rx` in the list of `Reaction`s within `network`. - -Notes: -- Any new species or parameters used in `rx` should be separately added to - `network` using [`addspecies!`](@ref) and [`addparam!`](@ref). -""" -function addreaction!(network::ReactionSystem, rx::Reaction) - reset_networkproperties!(network) - push!(get_eqs(network), rx) - sort(get_eqs(network); by = eqsortby) - push!(get_rxs(network), rx) - length(get_rxs(network)) -end - -""" - merge!(network1::ReactionSystem, network2::ReactionSystem) - -Merge `network2` into `network1`. - -Notes: -- Duplicate reactions between the two networks are not filtered out. -- [`Reaction`](@ref)s are not deepcopied to minimize allocations, so both - networks will share underlying data arrays. -- Subsystems are not deepcopied between the two networks and will hence be - shared. -- Returns `network1`. -- `combinatoric_ratelaws` is the value of `network1`. -""" -function Base.merge!(network1::ReactionSystem, network2::ReactionSystem) - isequal(get_iv(network1), get_iv(network2)) || - error("Reaction networks must have the same independent variable to be mergable.") - union!(get_sivs(network1), get_sivs(network2)) - - union!(get_eqs(network1), get_eqs(network2)) - sort!(get_eqs(network1), by = eqsortby) - union!(get_rxs(network1), get_rxs(network2)) - (count(eq -> eq isa Reaction, get_eqs(network1)) == length(get_rxs(network1))) || - error("Unequal number of reactions from get_rxs(sys) and get_eqs(sys) after merging.") - - union!(get_states(network1), get_states(network2)) - sort!(get_states(network1), by = !isspecies) - union!(get_species(network1), get_species(network2)) - (count(isspecies, get_states(network1)) == length(get_species(network1))) || - error("Unequal number of species from get_species(sys) and get_states(sys) after merging.") - - union!(get_ps(network1), get_ps(network2)) - union!(get_observed(network1), get_observed(network2)) - union!(get_systems(network1), get_systems(network2)) - merge!(get_defaults(network1), get_defaults(network2)) - union!(MT.get_continuous_events(network1), MT.get_continuous_events(network2)) - union!(MT.get_discrete_events(network1), MT.get_discrete_events(network2)) - - reset_networkproperties!(network1) - network1 -end - -############################### units ##################################### - -""" - validate(rx::Reaction; info::String = "") - -Check that all substrates and products within the given [`Reaction`](@ref) have -the same units, and that the units of the reaction's rate expression are -internally consistent (i.e. if the rate involves sums, each term in the sum has -the same units). - -""" -function validate(rx::Reaction; info::String = "") - validated = MT._validate([rx.rate], [string(rx, ": rate")], info = info) - - subunits = isempty(rx.substrates) ? nothing : get_unit(rx.substrates[1]) - for i in 2:length(rx.substrates) - if get_unit(rx.substrates[i]) != subunits - validated = false - @warn(string("In ", rx, " the substrates have differing units.")) - end - end - - produnits = isempty(rx.products) ? nothing : get_unit(rx.products[1]) - for i in 2:length(rx.products) - if get_unit(rx.products[i]) != produnits - validated = false - @warn(string("In ", rx, " the products have differing units.")) - end - end - - if (subunits !== nothing) && (produnits !== nothing) && (subunits != produnits) - validated = false - @warn(string("in ", rx, - " the substrate units are not consistent with the product units.")) - end - - validated -end - -""" - validate(rs::ReactionSystem, info::String="") - -Check that all species in the [`ReactionSystem`](@ref) have the same units, and -that the rate laws of all reactions reduce to units of (species units) / (time -units). - -Notes: -- Does not check subsystems, constraint equations, or non-species variables. -""" -function validate(rs::ReactionSystem, info::String = "") - specs = get_species(rs) - - # if there are no species we don't check units on the system - isempty(specs) && return true - - specunits = get_unit(specs[1]) - validated = true - for spec in specs - if get_unit(spec) != specunits - validated = false - @warn(string("Species are expected to have units of ", specunits, - " however, species ", spec, " has units ", get_unit(spec), ".")) - end - end - timeunits = get_unit(get_iv(rs)) - - # no units for species, time or parameters then assume validated - (specunits in (MT.unitless, nothing)) && (timeunits in (MT.unitless, nothing)) && - MT.all_dimensionless(get_ps(rs)) && return true - - rateunits = specunits / timeunits - for rx in get_rxs(rs) - rxunits = get_unit(rx.rate) - for (i, sub) in enumerate(rx.substrates) - rxunits *= get_unit(sub)^rx.substoich[i] - end - - if rxunits != rateunits - validated = false - @warn(string("Reaction rate laws are expected to have units of ", rateunits, - " however, ", rx, " has units of ", rxunits, ".")) - end - end - - validated -end - -# Checks if a unit consist of exponents with base 1 (and is this unitless). -unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) - From b725c4f416f1d03968e8917057b55db5f01a1f57 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 10:36:46 -0400 Subject: [PATCH 152/446] removed metagraph dep --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index ac53aaee49..df531adca8 100644 --- a/Project.toml +++ b/Project.toml @@ -15,7 +15,6 @@ LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" -MetaGraphs = "626554b9-1ddb-594c-aa3c-2596fe9399a5" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" From 6e86ab2e639359cbf35670a3ce9a5e90c46a12f7 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 16:32:25 -0400 Subject: [PATCH 153/446] changed reactionparams to parameters --- test/network_analysis/network_properties.jl | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/network_analysis/network_properties.jl b/test/network_analysis/network_properties.jl index a716d04c36..5507a9f655 100644 --- a/test/network_analysis/network_properties.jl +++ b/test/network_analysis/network_properties.jl @@ -44,7 +44,7 @@ let @test Catalyst.get_networkproperties(MAPK).rank == 15 k = rand(rng, numparams(MAPK)) - rates = Dict(zip(reactionparams(MAPK), k)) + rates = Dict(zip(parameters(MAPK), k)) @test Catalyst.iscomplexbalanced(MAPK, rates) == false # i=0; # for lcs in linkageclasses(MAPK) @@ -85,7 +85,7 @@ let @test Catalyst.get_networkproperties(rn2).rank == 6 k = rand(rng, numparams(rn2)) - rates = Dict(zip(reactionparams(rn2), k)) + rates = Dict(zip(parameters(rn2), k)) @test Catalyst.iscomplexbalanced(rn2, rates) == false # i=0; # for lcs in linkageclasses(rn2) @@ -129,7 +129,7 @@ let @test Catalyst.get_networkproperties(rn3).rank == 10 k = rand(rng, numparams(rn3)) - rates = Dict(zip(reactionparams(rn3), k)) + rates = Dict(zip(parameters(rn3), k)) @test Catalyst.iscomplexbalanced(rn3, rates) == false # i=0; # for lcs in linkageclasses(rn3) @@ -154,7 +154,7 @@ let end k = rand(rng, numparams(rn4)) - rates = Dict(zip(reactionparams(rn4), k)) + rates = Dict(zip(parameters(rn4), k)) @test Catalyst.iscomplexbalanced(rn4, rates) == true end @@ -182,7 +182,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end @@ -200,7 +200,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end let @@ -212,7 +212,7 @@ let weak_rev = false testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end let @@ -226,7 +226,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end let @@ -241,7 +241,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == true end let @@ -254,7 +254,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end let @@ -267,7 +267,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == true end let @@ -277,7 +277,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == true end let @@ -292,7 +292,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == true end let @@ -307,7 +307,7 @@ let testreversibility(rn, reactioncomplexes(rn)[2], rev, weak_rev) k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == false end @@ -322,7 +322,7 @@ let end k = rand(rng, numparams(rn)) - rates = Dict(zip(reactionparams(rn), k)) + rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == true end From ca39bfcdf46cf7646b8cf3cf05ed5020b5fbefb2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jun 2024 14:47:01 -0400 Subject: [PATCH 154/446] up --- .../model_simulation/sde_simulation_performance.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/src/model_simulation/sde_simulation_performance.md b/docs/src/model_simulation/sde_simulation_performance.md index 1d697f2d59..94a7584235 100644 --- a/docs/src/model_simulation/sde_simulation_performance.md +++ b/docs/src/model_simulation/sde_simulation_performance.md @@ -1,18 +1,20 @@ # [Advice for performant SDE simulations](@id sde_simulation_performance) -While there exist relatively straightforward approaches to improve performance for [ODE](@ref ref) and [Jump](@ref ref) simulations, this is generally not the case for SDE simulations. Below, we briefly describe some options. However, as one starts to investigate these, one quickly reaches what is (or could be) active areas of research. +While there exist relatively straightforward approaches to manage performance for [ODE](@ref ode_simulation_performance) and [Jump](@ref ref) simulations, this is generally not the case for SDE simulations. Below, we briefly describe some options. However, as one starts to investigate these, one quickly reaches what is (or could be) active areas of research. ## [SDE solver selection](@id sde_simulation_performance_solvers) -We have previously described [how ODE solver selection](@ref ref) can impact simulation performance. Again, it can be worthwhile to investigate this for SDE simulations. Throughout this documentation, we generally use the `STrapezoid` solver as the default choice. However, if the `DifferentialEquations` package is loaded +We have previously described how [ODE solver selection](@ref ode_simulation_performance_solvers) can impact simulation performance. Again, it can be worthwhile to investigate solver selection's impact on performance for SDE simulations. Throughout this documentation, we generally use the `STrapezoid` solver as the default choice. However, if the `DifferentialEquations` package is loaded ```julia using DifferentialEquations ``` -automatic SDE solver selection is automatically enabled (just like is the case for ODEs by default). Generally, the automatic SDe solver choice enabled by `DifferentialEquations` is better than just using `STrapezoid`. Next, if performance is critical, it can be worthwhile to check the [list of available SDE solvers](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) to find one with advantageous performance for a given problem. When doing so, it is important to pick a solver compatible with *non-diagonal noise* and with [*Ito problems*](https://en.wikipedia.org/wiki/It%C3%B4_calculus). +automatic SDE solver selection enabled (just like is the case for ODEs by default). Generally, the automatic SDE solver choice enabled by `DifferentialEquations` is better than just using `STrapezoid`. Next, if performance is critical, it can be worthwhile to check the [list of available SDE solvers](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/) to find one with advantageous performance for a given problem. When doing so, it is important to pick a solver compatible with *non-diagonal noise* and with [*Ito problems*](https://en.wikipedia.org/wiki/It%C3%B4_calculus). ## [Options for Jacobian computation](@id sde_simulation_performance_jacobian) -In [the section on ODE simulation performance](@ref ref) we describe various options for computing the system Jacobian, and how these could be used to improve performance for [implicit solvers](@ref ref). These can be used in tandem with implicit SDE solvers (such as `ImplicitEM`). However, due to additional considerations during SDE simulations, it is much less certain whether these will actually have any impact on performance. So while these options might be worth reading about and trialling, there is no guarantee that they will be beneficial. +In the section on ODE simulation performance, we describe various [options for computing the system Jacobian](@ref ode_simulation_performance_jacobian), and how these could be used to improve performance for [implicit solvers](@ref ode_simulation_performance_stiffness). These can be used in tandem with implicit SDE solvers (such as `STrapezoid`). However, due to additional considerations during SDE simulations, it is much less certain whether these will actually have any impact on performance. So while these options might be worth reading about and trialling, there is no guarantee that they will be beneficial. ## [Parallelisation on CPUs and GPUs](@id sde_simulation_performance_parallelisation) -We have previously described how simulation parallelisation can be used to [improve performance when multiple ODE simulations are carried out](@ref ref). The same approaches can be used for SDE simulations. Indeed, it is often more relevant for SDEs, as these often are re-simulated using identical simulation conditions (to investigate their typical behaviour across many samples). CPU parallelisation of SDE simulations uses the same approach as for ODEs. For GPU parallelisation we +We have previously described how simulation parallelisation can be used to [improve performance when multiple ODE simulations are carried out](@ref ode_simulation_performance_parallelisation). The same approaches can be used for SDE simulations. Indeed, it is often more relevant for SDEs, as these are often re-simulated using identical simulation conditions (to investigate their typical behaviour across many samples). + +CPU parallelisation of SDE simulations uses the [same approach as ODEs](@ref ode_simulation_performance_parallelisation_CPU). GPU parallelisation is carried out very similarly to [GPU parallelisation of ODE simulations](@ref ode_simulation_performance_parallelisation_GPU). The only difference is that a GPU-compatible SDE solver is required, with the only alternative currently available being `GPUEM`. ### [Multilevel Monte Carlo](@id sde_simulation_performance_parallelisation_mlmc) -An approach for speeding up parallel stochastic simulations are so-called [*multilevel Monte Carlo approaches*](https://en.wikipedia.org/wiki/Multilevel_Monte_Carlo_method) (MLMC). These are used when a stochastic process is simulated repeatedly using identical simulation conditions. Here, instead of performing all simulations using identical tolerance, the ensemble is simulated using a range of tolerances (primarily lower ones, which yields faster simulations). Currently, [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) do not have a native implementation for performing MLMC simulations (this will hopefully be added in the future). However, if high performance of parallel SDE simulations is required, these approaches may be worth investigating. \ No newline at end of file +An approach for speeding up parallel stochastic simulations is so-called [*multilevel Monte Carlo approaches*](https://en.wikipedia.org/wiki/Multilevel_Monte_Carlo_method) (MLMC). These are used when a stochastic process is simulated repeatedly using identical simulation conditions. Here, instead of performing all simulations using identical [tolerance](@ref ode_simulation_performance_error), the ensemble is simulated using a range of tolerances (primarily lower ones, which yields faster simulations). Currently, [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) do not have a native implementation for performing MLMC simulations (this will hopefully be added in the future). However, if high performance of parallel SDE simulations is required, these approaches may be worth investigating. \ No newline at end of file From 8394755ba2e4a657d445840c32f3821f771e3268 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jun 2024 15:19:40 -0400 Subject: [PATCH 155/446] init --- docs/Project.toml | 2 +- .../steady_state_functionality/bifurcation_diagrams.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 246727a23e..83ecae3cc6 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -37,7 +37,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [compat] BenchmarkTools = "1.5" -BifurcationKit = "0.3" +BifurcationKit = "0.3.4" CairoMakie = "0.12" Catalyst = "13" DataFrames = "1.6" diff --git a/docs/src/steady_state_functionality/bifurcation_diagrams.md b/docs/src/steady_state_functionality/bifurcation_diagrams.md index cebe51dc1c..37ea73dfa2 100644 --- a/docs/src/steady_state_functionality/bifurcation_diagrams.md +++ b/docs/src/steady_state_functionality/bifurcation_diagrams.md @@ -43,7 +43,7 @@ nothing # hide Finally, we compute our bifurcation diagram using: ```@example ex1 -bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true) +bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside = true) nothing # hide ``` Where `PALC()` designates that we wish to use the pseudo arclength continuation method to track our solution. The third argument (`2`) designates the maximum number of recursions when branches of branches are computed (branches appear as continuation encounters certain bifurcation points). For diagrams with highly branched structures (rare for many common small chemical reaction networks) this input is important. Finally, `bothside = true` designates that we wish to perform continuation on both sides of the initial point (which is typically the case). @@ -69,7 +69,7 @@ opt_newton = NewtonPar(tol = 1e-9, max_iterations = 1000) opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], dsmin = 0.001, dsmax = 0.01, max_steps = 1000, newton_options = opt_newton) -bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true) +bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside = true) nothing # hide ``` (however, in this case these additional settings have no significant effect on the result) @@ -79,7 +79,7 @@ Let's consider the previous case, but instead compute the bifurcation diagram ov ```@example ex1 p_span = (2.0, 15.0) opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000) -bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true) +bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside = true) plot(bif_dia; xguide = "k1", yguide = "X") ``` Here, in the bistable region, we only see a single branch. The reason is that the continuation algorithm starts at our initial guess (here made at $k1 = 4.0$ for $(X,Y) = (5.0,2.0)$) and tracks the diagram from there. However, with the upper bound set at $k1=15.0$ the bifurcation diagram has a disjoint branch structure, preventing the full diagram from being computed by continuation alone. In this case it could be solved by increasing the bound from $k1=15.0$, however, this is not possible in all cases. In these cases, *deflation* can be used. This is described in the [BifurcationKit documentation](https://bifurcationkit.github.io/BifurcationKitDocs.jl/dev/tutorials/tutorials2/#Snaking-computed-with-deflation). @@ -103,7 +103,7 @@ bprob = BifurcationProblem(kinase_model, u_guess, p_start, :d; plot_var = :Xp, u p_span = (0.1, 10.0) opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000) -bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true) +bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside = true) plot(bif_dia; xguide = "d", yguide = "Xp") ``` This bifurcation diagram does not contain any interesting features (such as bifurcation points), and only shows how the steady state concentration of $Xp$ is reduced as $d$ increases. From 9298ca190d6369b9d12c256835be6cc9924c8ed2 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 31 May 2024 11:29:56 -0400 Subject: [PATCH 156/446] cycle wip --- src/network_analysis.jl | 40 ++++++++++++++++++++++++++++++++++++++++ src/reactionsystem.jl | 3 ++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index cc33f9b63e..dcac8e0855 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -718,3 +718,43 @@ function conservationlaw_errorcheck(rs, pre_varmap) isempty(conservedequations(Catalyst.flatten(rs))) || error("The system has conservation laws but initial conditions were not provided for some species.") end + +""" + cycles(rs::ReactionSystem) + + Returns the matrix of cycles, or right eigenvectors of the stoichiometric matrix. +""" + +function cycles(rs::ReactionSystem) + # nps = get_networkproperties(rs) + nsm = netstoichmat(rs) + cycles(nsm) +end + +function cycles(nsm::T; col_order = nothing) where {T <: AbstractMatrix} + + # compute the left nullspace over the integers + N = MT.nullspace(nsm; col_order) + + # if all coefficients for a conservation law are negative, make positive + for Nrow in eachcol(N) + all(r -> r <= 0, Nrow) && (Nrow .*= -1) + end + + # check we haven't overflowed + iszero(nsm * N) || error("Calculation of the cycle matrix was inaccurate, " + * "likely due to numerical overflow. Please use a larger integer " + * "type like Int128 or BigInt for the net stoichiometry matrix.") + + T(N) +end + +""" + fluxmodes(rs::ReactionSystem) + + See documentation for [`cycles`](@ref). +""" + +function fluxmodes(rs::ReactionSystem) + cycles(rs) +end diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index edb0f9fedf..32b7107ea6 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -80,6 +80,7 @@ Base.@kwdef mutable struct NetworkProperties{I <: Integer, V <: BasicSymbolic{Re isempty::Bool = true netstoichmat::Union{Matrix{Int}, SparseMatrixCSC{Int, Int}} = Matrix{Int}(undef, 0, 0) conservationmat::Matrix{I} = Matrix{I}(undef, 0, 0) + cyclemat::Matrix{I} = Matrix{I}(undef, 0, 0) col_order::Vector{Int} = Int[] rank::Int = 0 nullity::Int = 0 @@ -1473,4 +1474,4 @@ function validate(rs::ReactionSystem, info::String = "") end # Checks if a unit consist of exponents with base 1 (and is this unitless). -unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) \ No newline at end of file +unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) From 3f2230eb44aebf6948fd6a27307d1ec4450854cf Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 3 Jun 2024 19:11:06 -0400 Subject: [PATCH 157/446] up --- src/network_analysis.jl | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index dcac8e0855..b7a049033a 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -722,13 +722,15 @@ end """ cycles(rs::ReactionSystem) - Returns the matrix of cycles, or right eigenvectors of the stoichiometric matrix. + Returns the matrix of cycles (or flux vectors), or reaction fluxes at steady state. These correspond to right eigenvectors of the stoichiometric matrix. Equivalent to [`fluxmodebasis`](@ref). """ function cycles(rs::ReactionSystem) - # nps = get_networkproperties(rs) + nps = get_networkproperties(rs) nsm = netstoichmat(rs) - cycles(nsm) + !isempty(nps.cyclemat) && return nps.cyclemat + nps.cyclemat = cycles(nsm; col_order = nps.col_order) + nps.cyclemat end function cycles(nsm::T; col_order = nothing) where {T <: AbstractMatrix} @@ -736,7 +738,7 @@ function cycles(nsm::T; col_order = nothing) where {T <: AbstractMatrix} # compute the left nullspace over the integers N = MT.nullspace(nsm; col_order) - # if all coefficients for a conservation law are negative, make positive + # if all coefficients for a cycle are negative, make positive for Nrow in eachcol(N) all(r -> r <= 0, Nrow) && (Nrow .*= -1) end @@ -750,11 +752,12 @@ function cycles(nsm::T; col_order = nothing) where {T <: AbstractMatrix} end """ - fluxmodes(rs::ReactionSystem) + fluxvectors(rs::ReactionSystem) See documentation for [`cycles`](@ref). """ -function fluxmodes(rs::ReactionSystem) +function fluxvectors(rs::ReactionSystem) cycles(rs) end + From a5dda3dfc9c308a945d5c05b8cf2a40127fdfb09 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jun 2024 20:02:16 -0400 Subject: [PATCH 158/446] init --- test/dsl/dsl_basic_model_construction.jl | 16 +- test/network_analysis/conservation_laws.jl | 183 +++++++++++++----- test/simulation_and_solving/simulate_ODEs.jl | 2 +- test/simulation_and_solving/simulate_SDEs.jl | 2 +- test/simulation_and_solving/simulate_jumps.jl | 2 +- test/test_networks.jl | 46 ++--- 6 files changed, 171 insertions(+), 80 deletions(-) diff --git a/test/dsl/dsl_basic_model_construction.jl b/test/dsl/dsl_basic_model_construction.jl index 6c3d5466c3..3d6510a8ae 100644 --- a/test/dsl/dsl_basic_model_construction.jl +++ b/test/dsl/dsl_basic_model_construction.jl @@ -71,7 +71,7 @@ let Set([:p, :k1, :k2, :k3, :k4, :k5, :k6, :d]) basic_test(reaction_networks_hill[1], 4, [:X1, :X2], [:v1, :v2, :K1, :K2, :n1, :n2, :d1, :d2]) - basic_test(reaction_networks_constraint[1], 6, [:X1, :X2, :X3], + basic_test(reaction_networks_conserved[1], 6, [:X1, :X2, :X3], [:k1, :k2, :k3, :k4, :k5, :k6]) basic_test(reaction_networks_real[1], 4, [:X, :Y], [:A, :B]) basic_test(reaction_networks_weird[1], 2, [:X], [:p, :d]) @@ -281,7 +281,7 @@ let Reaction(p + k5 * X2 * X3, nothing, [X5], nothing, [1]), Reaction(d, [X5], nothing, [1], nothing)] @named rs_2 = ReactionSystem(rxs_2, t, [X1, X2, X3, X4, X5], [k1, k2, k3, k4, p, k5, d]) - push!(identical_networks_4, reaction_networks_constraint[3] => rs_2) + push!(identical_networks_4, reaction_networks_conserved[3] => rs_2) rxs_3 = [Reaction(k1, [X1], [X2], [1], [1]), Reaction(0, [X2], [X3], [1], [1]), @@ -321,14 +321,14 @@ let for factor in [1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3] τ = rand(rng) - u = rnd_u0(reaction_networks_constraint[1], rng; factor) + u = rnd_u0(reaction_networks_conserved[1], rng; factor) p_2 = rnd_ps(time_network, rng; factor) - p_1 = [p_2; reaction_networks_constraint[1].k1 => τ; - reaction_networks_constraint[1].k4 => τ; reaction_networks_constraint[1].k5 => τ] + p_1 = [p_2; reaction_networks_conserved[1].k1 => τ; + reaction_networks_conserved[1].k4 => τ; reaction_networks_conserved[1].k5 => τ] - @test f_eval(reaction_networks_constraint[1], u, p_1, τ) ≈ f_eval(time_network, u, p_2, τ) - @test jac_eval(reaction_networks_constraint[1], u, p_1, τ) ≈ jac_eval(time_network, u, p_2, τ) - @test g_eval(reaction_networks_constraint[1], u, p_1, τ) ≈ g_eval(time_network, u, p_2, τ) + @test f_eval(reaction_networks_conserved[1], u, p_1, τ) ≈ f_eval(time_network, u, p_2, τ) + @test jac_eval(reaction_networks_conserved[1], u, p_1, τ) ≈ jac_eval(time_network, u, p_2, τ) + @test g_eval(reaction_networks_conserved[1], u, p_1, τ) ≈ g_eval(time_network, u, p_2, τ) end end diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index d7fa4dc9a4..0bc563642c 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -1,7 +1,12 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, LinearAlgebra, NonlinearSolve, OrdinaryDiffEq +using Catalyst, JumpProcesses, LinearAlgebra, NonlinearSolve, OrdinaryDiffEq, SteadyStateDiffEq, StochasticDiffEq, Test + +# Sets stable rng number. +using StableRNGs +rng = StableRNG(123456) +seed = rand(rng, 1:100) # Fetch test networks. include("../test_networks.jl") @@ -42,17 +47,18 @@ end # Tests conservation law computation on large number of networks where we know which have conservation laws. let + # networks for whch we know there is no conservation laws. Cs_standard = map(conservationlaws, reaction_networks_standard) - @test all(size(C, 1) == 0 for C in Cs_standard) - Cs_hill = map(conservationlaws, reaction_networks_hill) + @test all(size(C, 1) == 0 for C in Cs_standard) @test all(size(C, 1) == 0 for C in Cs_hill) + # Networks for which there are known conservation laws (stored in `reaction_network_conslaws`). function consequiv(A, B) rank([A; B]) == rank(A) == rank(B) end - Cs_constraint = map(conservationlaws, reaction_networks_constraint) - @test all(consequiv.(Matrix{Int}.(Cs_constraint), reaction_network_constraints)) + Cs_constraint = map(conservationlaws, reaction_networks_conserved) + @test all(consequiv.(Matrix{Int}.(Cs_constraint), reaction_network_conslaws)) end # Tests additional conservation law-related functions. @@ -74,8 +80,25 @@ let @test count(isequal.(conserved_quantity, Num(0))) == 2 end +# Tests that `conservationlaws`'s caches something. +let + # Creates network with/without cached conservation laws. + rn = @reaction_network rn begin + (k1,k2), X1 <--> X2 + end + rn_cached = deepcopy(rn) + conservationlaws(rn_cached) + + # Checks that equality is correct (currently equality does not consider network property caching). + @test rn_cached == rn + @test Catalyst.get_networkproperties(rn_cached) != Catalyst.get_networkproperties(rn) +end + +### Simulation & Solving Tests ### + # Test conservation law elimination. let + # Declares the model rn = @reaction_network begin (k1, k2), A + B <--> C (m1, m2), D <--> E @@ -83,47 +106,52 @@ let b23, F2 --> F3 b31, F3 --> F1 end - osys = complete(convert(ODESystem, rn; remove_conserved = true)) - @unpack A, B, C, D, E, F1, F2, F3, k1, k2, m1, m2, b12, b23, b31 = osys + @unpack A, B, C, D, E, F1, F2, F3, k1, k2, m1, m2, b12, b23, b31 = rn + sps = species(rn) u0 = [A => 10.0, B => 10.0, C => 0.0, D => 10.0, E => 0.0, F1 => 8.0, F2 => 0.0, F3 => 0.0] p = [k1 => 1.0, k2 => 0.1, m1 => 1.0, m2 => 2.0, b12 => 1.0, b23 => 2.0, b31 => 0.1] tspan = (0.0, 20.0) - oprob = ODEProblem(osys, u0, tspan, p) - sol = solve(oprob, Tsit5(); abstol = 1e-10, reltol = 1e-10) + + # Simulates model using ODEs and checks that simulations are identical. + osys = complete(convert(ODESystem, rn; remove_conserved = true)) + oprob1 = ODEProblem(osys, u0, tspan, p) oprob2 = ODEProblem(rn, u0, tspan, p) - sol2 = solve(oprob2, Tsit5(); abstol = 1e-10, reltol = 1e-10) oprob3 = ODEProblem(rn, u0, tspan, p; remove_conserved = true) - sol3 = solve(oprob3, Tsit5(); abstol = 1e-10, reltol = 1e-10) - - tv = range(tspan[1], tspan[2], length = 101) - for s in species(rn) - @test isapprox(sol(tv, idxs = s), sol2(tv, idxs = s)) - @test isapprox(sol2(tv, idxs = s), sol2(tv, idxs = s)) - end + osol1 = solve(oprob1, Tsit5(); abstol = 1e-8, reltol = 1e-8, saveat= 0.2) + osol2 = solve(oprob2, Tsit5(); abstol = 1e-8, reltol = 1e-8, saveat= 0.2) + osol3 = solve(oprob3, Tsit5(); abstol = 1e-8, reltol = 1e-8, saveat= 0.2) + @test osol1[sps] ≈ osol2[sps] ≈ osol3[sps] + # Checks that steady states found using nonlinear solving and steady state simulations are identical. nsys = complete(convert(NonlinearSystem, rn; remove_conserved = true)) - nprob = NonlinearProblem{true}(nsys, u0, p) - nsol = solve(nprob, NewtonRaphson(); abstol = 1e-10) - nprob2 = ODEProblem(rn, u0, (0.0, 100.0 * tspan[2]), p) - nsol2 = solve(nprob2, Tsit5(); abstol = 1e-10, reltol = 1e-10) + nprob1 = NonlinearProblem{true}(nsys, u0, p) + nprob2 = NonlinearProblem(rn, u0, p) nprob3 = NonlinearProblem(rn, u0, p; remove_conserved = true) - nsol3 = solve(nprob3, NewtonRaphson(); abstol = 1e-10) - for s in species(rn) - @test isapprox(nsol[s], nsol2(tspan[2], idxs = s)) - @test isapprox(nsol2(tspan[2], idxs = s), nsol3[s]) - end - - u0 = [A => 100.0, B => 20.0, C => 5.0, D => 10.0, E => 3.0, F1 => 8.0, F2 => 2.0, + ssprob1 = SteadyStateProblem{true}(osys, u0, p) + ssprob2 = SteadyStateProblem(rn, u0, p) + ssprob3 = SteadyStateProblem(rn, u0, p; remove_conserved = true) + nsol1 = solve(nprob1, NewtonRaphson(); abstol = 1e-8) + # Nonlinear problems cannot find steady states properly without removing conserved species. + nsol3 = solve(nprob3, NewtonRaphson(); abstol = 1e-8) + sssol1 = solve(ssprob1, DynamicSS(Tsit5()); abstol = 1e-8, reltol = 1e-8) + sssol2 = solve(ssprob2, DynamicSS(Tsit5()); abstol = 1e-8, reltol = 1e-8) + sssol3 = solve(ssprob3, DynamicSS(Tsit5()); abstol = 1e-8, reltol = 1e-8) + @test nsol1[sps] ≈ nsol3[sps] ≈ sssol1[sps] ≈ sssol2[sps] ≈ sssol3[sps] + + # Creates SDEProblems using various approaches. + u0_sde = [A => 100.0, B => 20.0, C => 5.0, D => 10.0, E => 3.0, F1 => 8.0, F2 => 2.0, F3 => 20.0] ssys = complete(convert(SDESystem, rn; remove_conserved = true)) - sprob = SDEProblem(ssys, u0, tspan, p) - sprob2 = SDEProblem(rn, u0, tspan, p) - sprob3 = SDEProblem(rn, u0, tspan, p; remove_conserved = true) - ists = ModelingToolkit.get_unknowns(ssys) - sts = ModelingToolkit.get_unknowns(rn) - istsidxs = findall(in(ists), sts) - u1 = copy(sprob.u0) + sprob1 = SDEProblem(ssys, u0_sde, tspan, p) + sprob2 = SDEProblem(rn, u0_sde, tspan, p) + sprob3 = SDEProblem(rn, u0_sde, tspan, p; remove_conserved = true) + + # Checks that the SDEs f and g function evaluates to the same thing. + ind_us = ModelingToolkit.get_unknowns(ssys) + us = ModelingToolkit.get_unknowns(rn) + ind_uidxs = findall(in(ind_us), us) + u1 = copy(sprob1.u0) u2 = sprob2.u0 u3 = copy(sprob3.u0) du1 = similar(u1) @@ -132,19 +160,82 @@ let g1 = zeros(length(u1), numreactions(rn)) g2 = zeros(length(u2), numreactions(rn)) g3 = zeros(length(u3), numreactions(rn)) - sprob.f(du1, u1, sprob.p, 1.0) - sprob2.f(du2, u2, sprob2.p, 1.0) - sprob3.f(du3, u3, sprob3.p, 1.0) - @test isapprox(du1, du2[istsidxs]) - @test isapprox(du2[istsidxs], du3) - sprob.g(g1, u1, sprob.p, 1.0) - sprob2.g(g2, u2, sprob2.p, 1.0) - sprob3.g(g3, u3, sprob3.p, 1.0) - @test isapprox(g1, g2[istsidxs, :]) - @test isapprox(g2[istsidxs, :], g3) + sprob1.f(du1, sprob1.u0, sprob1.p, 1.0) + sprob2.f(du2, sprob2.u0, sprob2.p, 1.0) + sprob3.f(du3, sprob3.u0, sprob3.p, 1.0) + @test du1 ≈ du2[ind_uidxs] ≈ du3 + sprob1.g(g1, sprob1.u0, sprob1.p, 1.0) + sprob2.g(g2, sprob2.u0, sprob2.p, 1.0) + sprob3.g(g3, sprob3.u0, sprob3.p, 1.0) + @test g1 ≈ g2[ind_uidxs, :] ≈ g3 +end + +# Tests simulations for various input types (using X, rn.X, and :X forms). +# Tests that conservation laws can be generated for system with non-default parameter types. +let + # Prepares the model. + rn = @reaction_network rn begin + @parameters kB::Int64 + (kB,kD), X + Y <--> XY + end + sps = species(rn) + @unpack kB, kD, X, Y, XY = rn + + # Creates `ODEProblem`s using three types of inputs. Checks that solutions are identical. + u0_1 = [X => 2.0, Y => 3.0, XY => 4.0] + u0_2 = [rn.X => 2.0, rn.Y => 3.0, rn.XY => 4.0] + u0_3 = [:X => 2.0, :Y => 3.0, :XY => 4.0] + ps = (kB => 2, kD => 1.5) + oprob1 = ODEProblem(rn, u0_1, 10.0, ps; remove_conserved = true) + oprob2 = ODEProblem(rn, u0_2, 10.0, ps; remove_conserved = true) + oprob3 = ODEProblem(rn, u0_3, 10.0, ps; remove_conserved = true) + @test solve(oprob1)[sps] ≈ solve(oprob2)[sps] ≈ solve(oprob3)[sps] end -### ConservedParameter Metadata Tests ### +# Tests conservation laws in SDE simulation. +let + # Creates `SDEProblem`s. + rn = @reaction_network begin + (k1,k2), X1 <--> X2 + end + u0 = Dict([:X1 => 100.0, :X2 => 120.0]) + ps = [:k1 => 0.2, :k2 => 0.15] + sprob = SDEProblem(rn, u0, 10.0, ps; remove_conserved = true) + + # Checks that conservation laws hold in all simulations. + sol = solve(sprob, ImplicitEM(); seed) + @test sol[:X1] + sol[:X2] ≈ sol[rn.X1 + rn.X2] ≈ fill(u0[:X1] + u0[:X2], length(sol.t)) +end + +# Checks that the conservation law parameter's value can be changed in simulations. +let + # Prepares `ODEProblem`s. + rn = @reaction_network begin + (k1,k2), X1 <--> X2 + end + osys = complete(convert(ODESystem, rn; remove_conserved = true)) + u0 = [osys.X1 => 1.0, osys.X2 => 1.0] + ps_1 = [osys.k1 => 2.0, osys.k2 => 3.0] + ps_2 = [osys.k1 => 2.0, osys.k2 => 3.0, osys.Γ[1] => 4.0] + oprob1 = ODEProblem(osys, u0, 10.0, ps_1) + oprob2 = ODEProblem(osys, u0, 10.0, ps_2) + + # Checks that the solutions generates the correct conserved quantities. + sol1 = solve(oprob1; saveat = 1.0) + sol2 = solve(oprob2; saveat = 1.0) + @test all(sol1[osys.X1 + osys.X2] .== 2.0) + @test all(sol2[osys.X1 + osys.X2] .== 4.0) +end + +### Other Tests ### + +# Checks that `JumpSystem`s with conservation laws cannot be generated. +let + rn = @reaction_network begin + (k1,k2), X1 <--> X2 + end + @test_throws ArgumentError convert(JumpSystem, rn; remove_conserved = true) +end # Checks that `conserved` metadata is added correctly to parameters. # Checks that the `isconserved` getter function works correctly. diff --git a/test/simulation_and_solving/simulate_ODEs.jl b/test/simulation_and_solving/simulate_ODEs.jl index b706b187e3..ecb2a468ce 100644 --- a/test/simulation_and_solving/simulate_ODEs.jl +++ b/test/simulation_and_solving/simulate_ODEs.jl @@ -104,7 +104,7 @@ let du[2] = -k3 * X2 + k4 * X3 + k1 * X1 - k2 * X2 du[3] = -k5 * X3 + k6 * X1 + k3 * X2 - k4 * X3 end - push!(catalyst_networks, reaction_networks_constraint[1]) + push!(catalyst_networks, reaction_networks_conserved[1]) push!(manual_networks, real_functions_4) push!(u0_syms, [:X1, :X2, :X3]) push!(ps_syms, [:k1, :k2, :k3, :k4, :k5, :k6]) diff --git a/test/simulation_and_solving/simulate_SDEs.jl b/test/simulation_and_solving/simulate_SDEs.jl index 3e71947495..d3504a3cd6 100644 --- a/test/simulation_and_solving/simulate_SDEs.jl +++ b/test/simulation_and_solving/simulate_SDEs.jl @@ -107,7 +107,7 @@ let du[7, 5] = sqrt(k5 * X5 * X6) du[7, 6] = -sqrt(k6 * X7) end - push!(catalyst_networks, reaction_networks_constraint[9]) + push!(catalyst_networks, reaction_networks_conserved[9]) push!(manual_networks, (f = real_f_3, g = real_g_3, nrp = zeros(7, 6))) push!(u0_syms, [:X1, :X2, :X3, :X4, :X5, :X6, :X7]) push!(ps_syms, [:k1, :k2, :k3, :k4, :k5, :k6]) diff --git a/test/simulation_and_solving/simulate_jumps.jl b/test/simulation_and_solving/simulate_jumps.jl index 9c8c02db8a..370b51036e 100644 --- a/test/simulation_and_solving/simulate_jumps.jl +++ b/test/simulation_and_solving/simulate_jumps.jl @@ -117,7 +117,7 @@ let jump_3_5 = ConstantRateJump(rate_3_5, affect_3_5!) jump_3_6 = ConstantRateJump(rate_3_6, affect_3_6!) jumps_3 = (jump_3_1, jump_3_2, jump_3_3, jump_3_4, jump_3_5, jump_3_6) - push!(catalyst_networks, reaction_networks_constraint[5]) + push!(catalyst_networks, reaction_networks_conserved[5]) push!(manual_networks, jumps_3) push!(u0_syms, [:X1, :X2, :X3, :X4]) push!(ps_syms, [:k1, :k2, :k3, :k4, :k5, :k6]) diff --git a/test/test_networks.jl b/test/test_networks.jl index 45b2ea5e59..ab2afad1ad 100644 --- a/test/test_networks.jl +++ b/test/test_networks.jl @@ -3,8 +3,8 @@ # Declares the vectors which contains the various test sets. reaction_networks_standard = Vector{ReactionSystem}(undef, 10) reaction_networks_hill = Vector{ReactionSystem}(undef, 10) -reaction_networks_constraint = Vector{ReactionSystem}(undef, 10) -reaction_network_constraints = Vector{Matrix{Int}}(undef, 10) +reaction_networks_conserved = Vector{ReactionSystem}(undef, 10) +reaction_network_conslaws = Vector{Matrix{Int}}(undef, 10) reaction_networks_real = Vector{ReactionSystem}(undef, 4) reaction_networks_weird = Vector{ReactionSystem}(undef, 10) @@ -160,69 +160,69 @@ end ### Reaction networks were some linear combination concentrations remain fixed (steady state values depends on initial conditions). ### -reaction_networks_constraint[1] = @reaction_network rnc1 begin +reaction_networks_conserved[1] = @reaction_network rnc1 begin (k1, k2), X1 ↔ X2 (k3, k4), X2 ↔ X3 (k5, k6), X3 ↔ X1 end -reaction_network_constraints[1] = [1 1 1] +reaction_network_conslaws[1] = [1 1 1] -reaction_networks_constraint[2] = @reaction_network rnc2 begin +reaction_networks_conserved[2] = @reaction_network rnc2 begin (k1, k2), X1 ↔ 2X1 (k3, k4), X1 + X2 ↔ X3 (k5, k6), X3 ↔ X2 end -reaction_network_constraints[2] = [0 1 1] +reaction_network_conslaws[2] = [0 1 1] -reaction_networks_constraint[3] = @reaction_network rnc3 begin +reaction_networks_conserved[3] = @reaction_network rnc3 begin (k1, k2 * X5), X1 ↔ X2 (k3 * X5, k4), X3 ↔ X4 (p + k5 * X2 * X3, d), ∅ ↔ X5 end -reaction_network_constraints[3] = [0 0 1 1 0; 1 1 0 0 0] +reaction_network_conslaws[3] = [0 0 1 1 0; 1 1 0 0 0] -reaction_networks_constraint[4] = @reaction_network rnc4 begin +reaction_networks_conserved[4] = @reaction_network rnc4 begin (k1, k2), X1 + X2 ↔ X3 (mm(X3, v, K), d), ∅ ↔ X4 end -reaction_network_constraints[4] = [0 1 1 0; -1 1 0 0] +reaction_network_conslaws[4] = [0 1 1 0; -1 1 0 0] -reaction_networks_constraint[5] = @reaction_network rnc5 begin +reaction_networks_conserved[5] = @reaction_network rnc5 begin (k1, k2), X1 ↔ 2X2 (k3, k4), 2X2 ↔ 3X3 (k5, k6), 3X3 ↔ 4X4 end -reaction_network_constraints[5] = [12 6 4 3] +reaction_network_conslaws[5] = [12 6 4 3] -reaction_networks_constraint[6] = @reaction_network rnc6 begin +reaction_networks_conserved[6] = @reaction_network rnc6 begin mmr(X1, v1, K1), X1 → X2 mmr(X2, v2, K2), X2 → X3 mmr(X3, v3, K3), X3 → X1 end -reaction_network_constraints[6] = [1 1 1] +reaction_network_conslaws[6] = [1 1 1] -reaction_networks_constraint[7] = @reaction_network rnc7 begin +reaction_networks_conserved[7] = @reaction_network rnc7 begin (k1, k2), X1 + X2 ↔ X3 (mm(X3, v, K), d), ∅ ↔ X2 (k3, k4), X2 ↔ X4 end -reaction_network_constraints[7] = [1 0 1 0] +reaction_network_conslaws[7] = [1 0 1 0] -reaction_networks_constraint[8] = @reaction_network rnc8 begin +reaction_networks_conserved[8] = @reaction_network rnc8 begin (k1, k2), X1 + X2 ↔ X3 (mm(X3, v1, K1), mm(X4, v2, K2)), X3 ↔ X4 end -reaction_network_constraints[8] = [-1 1 0 0; 0 1 1 1] +reaction_network_conslaws[8] = [-1 1 0 0; 0 1 1 1] -reaction_networks_constraint[9] = @reaction_network rnc9 begin +reaction_networks_conserved[9] = @reaction_network rnc9 begin (k1, k2), X1 + X2 ↔ X3 (k3, k4), X3 + X4 ↔ X5 (k5, k6), X5 + X6 ↔ X7 end -reaction_network_constraints[9] = [1 0 1 0 1 0 1; -1 1 0 0 0 0 0; 0 0 0 1 1 0 1; +reaction_network_conslaws[9] = [1 0 1 0 1 0 1; -1 1 0 0 0 0 0; 0 0 0 1 1 0 1; 0 0 0 0 0 1 1] -reaction_networks_constraint[10] = @reaction_network rnc10 begin +reaction_networks_conserved[10] = @reaction_network rnc10 begin kDeg, (w, w2, w2v, v, w2v2, vP, σB, w2σB) ⟶ ∅ kDeg, vPp ⟶ phos (kBw, kDw), 2w ⟷ w2 @@ -238,7 +238,7 @@ reaction_networks_constraint[10] = @reaction_network rnc10 begin λW * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ w λV * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ v end; -reaction_network_constraints[10] = [0 0 0 0 0 0 0 0 1 1] +reaction_network_conslaws[10] = [0 0 0 0 0 0 0 0 1 1] ### Reaction networks that are actual models that have been used ### @@ -350,6 +350,6 @@ end ### Gathers all netowkrs in a simgle array ### reaction_networks_all = [reaction_networks_standard..., reaction_networks_hill..., - reaction_networks_constraint..., + reaction_networks_conserved..., reaction_networks_real..., reaction_networks_weird...] From c002dac4abfc22f4594eaa25115457faba016f2e Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jun 2024 20:03:17 -0400 Subject: [PATCH 159/446] up --- src/reactionsystem_conversions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 2a9688cd73..b8f5771ad2 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -653,7 +653,7 @@ function Base.convert(::Type{<:JumpSystem}, rs::ReactionSystem; name = nameof(rs spatial_convert_err(rs::ReactionSystem, JumpSystem) (remove_conserved !== nothing) && - error("Catalyst does not support removing conserved species when converting to JumpSystems.") + throw(ArgumentError("Catalyst does not support removing conserved species when converting to JumpSystems.")) flatrs = Catalyst.flatten(rs) error_if_constraints(JumpSystem, flatrs) From 5fa9ba3e8b3bcfbd62ea690b2cea05e94f4f562b Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Tue, 4 Jun 2024 10:44:57 -0400 Subject: [PATCH 160/446] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 2b90ff7b2a..b9313411b2 100644 --- a/Project.toml +++ b/Project.toml @@ -57,7 +57,7 @@ StructuralIdentifiability = "0.5.1" SymbolicUtils = "1.0.3" Symbolics = "5.27" Unitful = "1.12.4" -julia = "1.9" +julia = "1.10" [extras] BifurcationKit = "0f109fa4-8a5d-4b75-95aa-f515264e7665" From 67b048ba4c8a6fab14c79b8bc384ef6acf00b1f5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 31 May 2024 10:51:17 -0400 Subject: [PATCH 161/446] `0.0` to `0` --- src/reactionsystem_conversions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index b8f5771ad2..6348e553a9 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -449,7 +449,7 @@ function remove_diffs(expr) return expr end end -diff_2_zero(expr) = (Symbolics.is_derivative(expr) ? 0.0 : expr) +diff_2_zero(expr) = (Symbolics.is_derivative(expr) ? 0 : expr) COMPLETENESS_ERROR = "A ReactionSystem must be complete before it can be converted to other system types. A ReactionSystem can be marked as complete using the `complete` function." From a1aa3dad7ba790c5e80bde4fdf4682844115cced Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 20 May 2024 21:14:31 -0400 Subject: [PATCH 162/446] init --- src/reaction.jl | 120 +++++++++++++-------------- test/reactionsystem_core/reaction.jl | 86 ++++++++++++++++++- 2 files changed, 141 insertions(+), 65 deletions(-) diff --git a/src/reaction.jl b/src/reaction.jl index 23538214aa..785aea43ea 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -72,6 +72,13 @@ function metadata_only_use_rate_check(metadata) return Bool(metadata[only_use_rate_idx][2]) end +# Used to promote a vector to the appropriate type. Takes the `vec == Any[]` case into account by +# returning an empty vector of the appropriate type. +function promote_reaction_vector(vec, type) + isempty(vec) && (return type[]) + type[value(v) for v in vec] +end + # calculates the net stoichiometry of a reaction as a vector of pairs (sub,substoich) function get_netstoich(subs, prods, sstoich, pstoich) # stoichiometry as a Dictionary @@ -85,9 +92,6 @@ function get_netstoich(subs, prods, sstoich, pstoich) [el for el in nsdict if !_iszero(el[2])] end -# Get the net stoichiometries' type. -netstoich_stoichtype(::Vector{Pair{S, T}}) where {S, T} = T - ### Reaction Structure ### """ @@ -134,19 +138,19 @@ Notes: - The three-argument form assumes all reactant and product stoichiometric coefficients are one. """ -struct Reaction{S, T} +struct Reaction{T} """The rate function (excluding mass action terms).""" rate::Any """Reaction substrates.""" - substrates::Vector + substrates::Vector{BasicSymbolic{Real}} """Reaction products.""" - products::Vector + products::Vector{BasicSymbolic{Real}} """The stoichiometric coefficients of the reactants.""" substoich::Vector{T} """The stoichiometric coefficients of the products.""" prodstoich::Vector{T} """The net stoichiometric coefficients of all species changed by the reaction.""" - netstoich::Vector{Pair{S, T}} + netstoich::Vector{Pair{BasicSymbolic{Real}, T}} """ `false` (default) if `rate` should be multiplied by mass action terms to give the rate law. `true` if `rate` represents the full reaction rate law. @@ -160,83 +164,77 @@ struct Reaction{S, T} end # Five-argument constructor accepting rate, substrates, and products, and their stoichiometries. -function Reaction(rate, subs, prods, substoich, prodstoich; - netstoich = nothing, metadata = Pair{Symbol, Any}[], - only_use_rate = metadata_only_use_rate_check(metadata), kwargs...) - (isnothing(prods) && isnothing(subs)) && - throw(ArgumentError("A reaction requires a non-nothing substrate or product vector.")) - (isnothing(prodstoich) && isnothing(substoich)) && - throw(ArgumentError("Both substrate and product stochiometry inputs cannot be nothing.")) - - if isnothing(subs) - prodtype = typeof(value(first(prods))) - subs = Vector{prodtype}() - !isnothing(substoich) && - throw(ArgumentError("If substrates are nothing, substrate stoichiometries have to be so too.")) - substoich = typeof(prodstoich)() - else - subs = value.(subs) - end +function Reaction(rate, subs::Vector, prods::Vector, substoich::Vector{S}, prodstoich::Vector{T}; + netstoich = nothing, metadata = Pair{Symbol, Any}[], + only_use_rate = metadata_only_use_rate_check(metadata), kwargs...) where {S,T} + # Error checks. + isempty(subs) && isempty(prods) && + throw(ArgumentError("A reaction requires either a non-empty substrate or product vector.")) + length(subs) != length(substoich) && + throw(ArgumentError("The substrate vector ($(subs)) and the substrate stoichiometry vector ($(substoich)) must have equal length.")) + length(prods) != length(prodstoich) && + throw(ArgumentError("The product vector ($(prods)) and the product stoichiometry vector ($(prodstoich)) must have equal length.")) allunique(subs) || throw(ArgumentError("Substrates can not be repeated in the list provided to `Reaction`, please modify the stoichiometry for any repeated substrates instead.")) - S = eltype(substoich) - - if isnothing(prods) - prods = Vector{eltype(subs)}() - !isnothing(prodstoich) && - throw(ArgumentError("If products are nothing, product stoichiometries have to be so too.")) - prodstoich = typeof(substoich)() - else - prods = value.(prods) - end allunique(prods) || throw(ArgumentError("Products can not be repeated in the list provided to `Reaction`, please modify the stoichiometry for any repeated products instead.")) - T = eltype(prodstoich) - # try to get a common type for stoichiometry, using Any if have Syms + # Ensures everything have uniform and correct types. + subs = promote_reaction_vector(subs, BasicSymbolic{Real}) + prods = promote_reaction_vector(prods, BasicSymbolic{Real}) stoich_type = promote_type(S, T) - if stoich_type <: Num - stoich_type = Any - substoich′ = Any[value(s) for s in substoich] - prodstoich′ = Any[value(p) for p in prodstoich] - else - substoich′ = (S == stoich_type) ? substoich : convert.(stoich_type, substoich) - prodstoich′ = (T == stoich_type) ? prodstoich : convert.(stoich_type, prodstoich) - end + (stoich_type <: Num) && (stoich_type = Any) + substoich = promote_reaction_vector(substoich, stoich_type) + prodstoich = promote_reaction_vector(prodstoich, stoich_type) + # Checks that all reactants are valid. if !(all(isvalidreactant, subs) && all(isvalidreactant, prods)) - badsts = union(filter(!isvalidreactant, subs), filter(!isvalidreactant, prods)) + badsts = union(filter(isvalidreactant, subs), filter(isvalidreactant, prods)) throw(ArgumentError("""To be a valid substrate or product, non-constant species must be declared via @species, while constant species must be parameters with the isconstantspecies metadata. The following reactants do not follow this convention:\n $badsts""")) end - - ns = if netstoich === nothing - get_netstoich(subs, prods, substoich′, prodstoich′) + + # Computes the net stoichiometries. + netstoich = if netstoich === nothing + get_netstoich(subs, prods, substoich, prodstoich) else - (netstoich_stoichtype(netstoich) != stoich_type) ? - convert.(stoich_type, netstoich) : netstoich + if typeof(netstoich) != Vector{Pair{BasicSymbolic{Real}, stoich_type}} + netstoich = Pair{BasicSymbolic{Real}, stoich_type}[ + value(ns[1]) => convert(stoich_type, ns[2]) for ns in netstoich] + end + netstoich end - # Check that all metadata entries are unique. (cannot use `in` since some entries may be symbolics). + # Handles metadata (check that all entries are unique, remove potential `only_use_rate` + # entries, and converts to the required type. if !allunique(entry[1] for entry in metadata) error("Repeated entries for the same metadata encountered in the following metadata set: $([entry[1] for entry in metadata]).") end - - # Deletes potential `:only_use_rate => ` entries from the metadata. if any(:only_use_rate == entry[1] for entry in metadata) deleteat!(metadata, findfirst(:only_use_rate == entry[1] for entry in metadata)) end - - # Ensures metadata have the correct type. metadata = convert(Vector{Pair{Symbol, Any}}, metadata) - Reaction(value(rate), subs, prods, substoich′, prodstoich′, ns, only_use_rate, metadata) + Reaction{stoich_type}(value(rate), subs, prods, substoich, prodstoich, netstoich, only_use_rate, metadata) end -# Three argument constructor assumes stoichiometric coefs are one and integers. -function Reaction(rate, subs, prods; kwargs...) - sstoich = isnothing(subs) ? nothing : ones(Int, length(subs)) - pstoich = isnothing(prods) ? nothing : ones(Int, length(prods)) - Reaction(rate, subs, prods, sstoich, pstoich; kwargs...) +# Three-argument constructor. Handles the case where no stoichiometries is given +# (by assuming that all stoichiometries are `1`). +function Reaction(rate, subs::Vector, prods::Vector; kwargs...) + Reaction(rate, subs, prods, ones(Int, length(subs)), ones(Int, length(prods)); kwargs...) +end + +# Handles cases where `nothing` is given (instead of an empty vector). +function Reaction(rate, subs::Vector, prods::Nothing, substoich::Vector, prodstoich::Nothing; kwargs...) + Reaction(rate, subs, Int64[], substoich, Int64[]; kwargs...) +end +function Reaction(rate, subs::Nothing, prods::Vector, substoich::Nothing, prodstoich::Vector; kwargs...) + Reaction(rate, Int64[], prods, Int64[], prodstoich; kwargs...) +end +function Reaction(rate, subs::Vector, prods::Nothing; kwargs...) + Reaction(rate, subs, Int64[]; kwargs...) +end +function Reaction(rate, subs::Nothing, prods::Vector; kwargs...) + Reaction(rate, Int64[], prods; kwargs...) end # Union type for `Reaction`s and `Equation`s. diff --git a/test/reactionsystem_core/reaction.jl b/test/reactionsystem_core/reaction.jl index 4760641c4b..0f551323ce 100644 --- a/test/reactionsystem_core/reaction.jl +++ b/test/reactionsystem_core/reaction.jl @@ -36,13 +36,94 @@ let @test issetequal(get_symbolics(rx4), [X, t, Y, η1, η2]) end +### Reaction Constructor Tests ### + +# Sets the default `t` to use. +t = default_t() + +# Checks that `Reaction`s can be sucesfully created using various complicated inputs. +# Checks that the `Reaction`s have the correct type, and the correct net stoichiometries are generated. +let + # Declare symbolic variables. + @parameters k n1 n2::Int32 x [isconstantspecies=true] + @species X(t) Y(t) Z(t) + @variables A(t) + + # Creates `Reaction`s. + rx1 = Reaction(k*A, [X], []) + rx2 = Reaction(k*A, [x], [Y], [1.5], [1]) + rx3 = Reaction(k*A, [x, X], [], [n1 + n2, n2], []) + rx4 = Reaction(k*A, [X, Y], [X, Y, Z], [2//3, 3], [1//3, 1, 2]) + rx5 = Reaction(k*A, [X, Y], [X, Y, Z], [2, 3], [1, n1, n2]) + rx6 = Reaction(k*A, [X], [x], [n1], [1]) + + # Check `Reaction` types. + @test rx1 isa Reaction{Int64} + @test rx2 isa Reaction{Float64} + @test rx3 isa Reaction{Any} + @test rx4 isa Reaction{Rational{Int64}} + @test rx5 isa Reaction{Any} + @test rx6 isa Reaction{Any} + + # Check `Reaction` net stoichiometries. + issetequal(rx1.netstoich, [X => -1]) + issetequal(rx2.netstoich, [x => -1.5, Y => 1.0]) + issetequal(rx3.netstoich, [x => -n1 - n2, X => -n2]) + issetequal(rx4.netstoich, [X => -1//3, Y => -2//1, Z => 2//1]) + issetequal(rx5.netstoich, [X => -1, Y => n1 - 3, Z => n2]) + issetequal(rx6.netstoich, [X => -n1, x => 1]) +end + +# Tests that various `Reaction` constructors gives identical inputs. +let + # Declare symbolic variables. + @parameters k n1 n2::Int32 + @species X(t) Y(t) Z(t) + @variables A(t) + + # Tests that the three-argument constructor generates correct result. + @test Reaction(k*A, [X], [Y, Z]) == Reaction(k*A, [X], [Y, Z], [1], [1, 1]) + + # Tests that `[]` and `nothing` can be used interchangeably. + @test Reaction(k*A, [X, Z], nothing) == Reaction(k*A, [X, Z], []) + @test Reaction(k*A, nothing, [Y, Z]) == Reaction(k*A, [], [Y, Z]) + @test Reaction(k*A, [X, Z], nothing, [n1 + n2, 2], nothing) == Reaction(k*A, [X, Z], [], [n1 + n2, 2], []) + @test Reaction(k*A, nothing, [Y, Z], nothing, [n1 + n2, 2]) == Reaction(k*A, [], [Y, Z], [], [n1 + n2, 2]) +end + +# Tests that various incorrect inputs yields errors. +let + # Declare symbolic variables. + @parameters k n1 n2::Int32 + @species X(t) Y(t) Z(t) + @variables A(t) + + # Neither substrates nor products. + @test_throws ArgumentError Reaction(k*A, [], []) + + # Substrate vector not of equal length to substrate stoichiometry vector. + @test_throws ArgumentError Reaction(k*A, [X, X, Z], [], [1, 2], []) + + # Product vector not of equal length to product stoichiometry vector. + @test_throws ArgumentError Reaction(k*A, [], [X, X, Z], [], [1, 2]) + + # Repeated substrates. + @test_throws ArgumentError Reaction(k*A, [X, X, Z], []) + + # Repeated products. + @test_throws ArgumentError Reaction(k*A, [], [Y, Z, Z]) + + # Non-valid reactants (parameter or variable). + @test_throws ArgumentError Reaction(k*A, [], [A]) + @test_throws ArgumentError Reaction(k*A, [], [k]) +end + ### Tests Metadata ### # Tests creation. # Tests basic accessor functions. # Tests that repeated metadata entries are not permitted. let - @variables t @parameters k @species X(t) X2(t) @@ -60,7 +141,6 @@ end # Tests accessors for system without metadata. let - @variables t @parameters k @species X(t) X2(t) @@ -77,7 +157,6 @@ end # Tests basic accessor functions. # Tests various metadata types. let - @variables t @parameters k @species X(t) X2(t) @@ -109,7 +188,6 @@ end # Tests the noise scaling metadata. let - @variables t @parameters k η @species X(t) X2(t) From 096c8d6c625d7b2b8fe34f125c7a8a5789df1238 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 21 May 2024 10:33:41 -0400 Subject: [PATCH 163/446] up --- src/reaction.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/reaction.jl b/src/reaction.jl index 785aea43ea..104d4212b9 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -165,7 +165,7 @@ end # Five-argument constructor accepting rate, substrates, and products, and their stoichiometries. function Reaction(rate, subs::Vector, prods::Vector, substoich::Vector{S}, prodstoich::Vector{T}; - netstoich = nothing, metadata = Pair{Symbol, Any}[], + netstoich = [], metadata = Pair{Symbol, Any}[], only_use_rate = metadata_only_use_rate_check(metadata), kwargs...) where {S,T} # Error checks. isempty(subs) && isempty(prods) && @@ -189,12 +189,12 @@ function Reaction(rate, subs::Vector, prods::Vector, substoich::Vector{S}, prods # Checks that all reactants are valid. if !(all(isvalidreactant, subs) && all(isvalidreactant, prods)) - badsts = union(filter(isvalidreactant, subs), filter(isvalidreactant, prods)) - throw(ArgumentError("""To be a valid substrate or product, non-constant species must be declared via @species, while constant species must be parameters with the isconstantspecies metadata. The following reactants do not follow this convention:\n $badsts""")) + badsts = union(filter(!isvalidreactant, subs), filter(!isvalidreactant, prods)) + throw(ArgumentError("To be a valid substrate or product, non-constant species must be declared via @species, while constant species must be parameters with the isconstantspecies metadata. The following reactants do not follow this convention:\n $badsts")) end # Computes the net stoichiometries. - netstoich = if netstoich === nothing + netstoich = if isnothing(netstoich) get_netstoich(subs, prods, substoich, prodstoich) else if typeof(netstoich) != Vector{Pair{BasicSymbolic{Real}, stoich_type}} From 059837255d6547669f12bf2facb1787524906d7f Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 21 May 2024 20:18:17 -0400 Subject: [PATCH 164/446] up --- src/reaction.jl | 13 +++++-------- test/reactionsystem_core/reactionsystem.jl | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/reaction.jl b/src/reaction.jl index 104d4212b9..21c6977cfa 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -194,14 +194,11 @@ function Reaction(rate, subs::Vector, prods::Vector, substoich::Vector{S}, prods end # Computes the net stoichiometries. - netstoich = if isnothing(netstoich) - get_netstoich(subs, prods, substoich, prodstoich) - else - if typeof(netstoich) != Vector{Pair{BasicSymbolic{Real}, stoich_type}} - netstoich = Pair{BasicSymbolic{Real}, stoich_type}[ - value(ns[1]) => convert(stoich_type, ns[2]) for ns in netstoich] - end - netstoich + if isempty(netstoich) + netstoich = get_netstoich(subs, prods, substoich, prodstoich) + elseif typeof(netstoich) != Vector{Pair{BasicSymbolic{Real}, stoich_type}} + netstoich = Pair{BasicSymbolic{Real}, stoich_type}[ + value(ns[1]) => convert(stoich_type, ns[2]) for ns in netstoich] end # Handles metadata (check that all entries are unique, remove potential `only_use_rate` diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index 6876026e0b..28e8a743a0 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -140,7 +140,6 @@ end ### Check ODE, SDE, and Jump Functions ### # Test by evaluating drift and diffusion terms. - let u = rnd_u0(rs, rng) p = rnd_ps(rs, rng) From 82c7730b7060df2f3ac1c095e393d8a84fe0eaf4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 4 Jun 2024 16:11:04 -0400 Subject: [PATCH 165/446] don't enforce reactant type --- src/reaction.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/reaction.jl b/src/reaction.jl index 21c6977cfa..c40c99cb77 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -142,15 +142,15 @@ struct Reaction{T} """The rate function (excluding mass action terms).""" rate::Any """Reaction substrates.""" - substrates::Vector{BasicSymbolic{Real}} + substrates::Vector{Any} """Reaction products.""" - products::Vector{BasicSymbolic{Real}} + products::Vector{Any} """The stoichiometric coefficients of the reactants.""" substoich::Vector{T} """The stoichiometric coefficients of the products.""" prodstoich::Vector{T} """The net stoichiometric coefficients of all species changed by the reaction.""" - netstoich::Vector{Pair{BasicSymbolic{Real}, T}} + netstoich::Vector{Pair{Any, T}} """ `false` (default) if `rate` should be multiplied by mass action terms to give the rate law. `true` if `rate` represents the full reaction rate law. From af9f801c6332a843ef2fcf688c135c520eb915e8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 4 Jun 2024 16:13:59 -0400 Subject: [PATCH 166/446] up --- src/reaction.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reaction.jl b/src/reaction.jl index c40c99cb77..f23d56f91e 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -146,9 +146,9 @@ struct Reaction{T} """Reaction products.""" products::Vector{Any} """The stoichiometric coefficients of the reactants.""" - substoich::Vector{T} + substoich::Vector """The stoichiometric coefficients of the products.""" - prodstoich::Vector{T} + prodstoich::Vector """The net stoichiometric coefficients of all species changed by the reaction.""" netstoich::Vector{Pair{Any, T}} """ From d671621e5dbb71f92eb4e72ea1bb6d24a575ebee Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 23 May 2024 14:53:15 -0400 Subject: [PATCH 167/446] up --- docs/src/faqs.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/faqs.md b/docs/src/faqs.md index dafeac35da..680767d051 100644 --- a/docs/src/faqs.md +++ b/docs/src/faqs.md @@ -18,6 +18,7 @@ Let's convert it to a system of ODEs, using the conservation laws of the system to eliminate two of the species: ```@example faq1 osys = convert(ODESystem, rn; remove_conserved = true) +osys = complete(osys) ``` Notice the resulting ODE system has just one ODE, while algebraic observables have been added for the two removed species (in terms of the conservation law @@ -221,6 +222,7 @@ rx1 = @reaction k, A --> 0 rx2 = @reaction $f, 0 --> A eq = f ~ (1 + sin(t)) @named rs = ReactionSystem([rx1, rx2, eq], t) +rs = complete(rs) osys = convert(ODESystem, rs) ``` In the final ODE model, `f` can be eliminated by using From e968c93ad57ffece410cba29832a64aea76f25fc Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 29 May 2024 18:10:40 -0400 Subject: [PATCH 168/446] up --- docs/pages.jl | 2 +- docs/src/faqs.md | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index a8a26ed1d0..bb0de4f678 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -66,6 +66,6 @@ pages = Any[ # # Contributor's guide. # # Repository structure. # ], - #"FAQs" => "faqs.md", + "FAQs" => "faqs.md", "API" => "api.md" ] diff --git a/docs/src/faqs.md b/docs/src/faqs.md index 680767d051..588ac7f4fb 100644 --- a/docs/src/faqs.md +++ b/docs/src/faqs.md @@ -5,13 +5,10 @@ One can directly use symbolic variables to index into SciML solution objects. Moreover, observables can also be evaluated in this way. For example, consider the system ```@example faq1 -using Catalyst, DifferentialEquations, Plots +using Catalyst, OrdinaryDiffEq, Plots rn = @reaction_network ABtoC begin (k₊,k₋), A + B <--> C end - -# initial condition and parameter values -setdefaults!(rn, [:A => 1.0, :B => 2.0, :C => 0.0, :k₊ => 1.0, :k₋ => 1.0]) nothing # hide ``` Let's convert it to a system of ODEs, using the conservation laws of the system @@ -29,33 +26,35 @@ observed(osys) Let's solve the system and see how to index the solution using our symbolic variables ```@example faq1 +u0 = [rn.A => 1.0, rn.B => 2.0, rn.C => 0.0] +ps = [rn.k₊ => 1.0, rn.k₋ => 1.0] oprob = ODEProblem(osys, [], (0.0, 10.0), []) sol = solve(oprob, Tsit5()) ``` Suppose we want to plot just species `C`, without having to know its integer index in the unknown vector. We can do this using the symbolic variable `C`, which we can get at in several ways -```@example faq1 +```julia sol[osys.C] ``` or -```@example faq1 +```julia @unpack C = osys sol[C] ``` To evaluate `C` at specific times and plot it we can just do -```@example faq1 +```julia t = range(0.0, 10.0, length=101) plot(t, sol(t, idxs = C), label = "C(t)", xlabel = "t") ``` If we want to get multiple variables we can just do -```@example faq1 +```julia @unpack A, B = osys sol(t, idxs = [A, B]) ``` Plotting multiple variables using the SciML plot recipe can be achieved like -```@example faq1 +```julia plot(sol; idxs = [A, B]) ``` @@ -66,7 +65,7 @@ constant, giving `k*X^2/2` instead of `k*X^2` for ODEs and `k*X*(X-1)/2` instead of `k*X*(X-1)` for jumps. This can be disabled when directly `convert`ing a [`ReactionSystem`](@ref). If `rn` is a generated [`ReactionSystem`](@ref), we can do -```julia +```@example faq1 osys = convert(ODESystem, rn; combinatoric_ratelaws=false) ``` Disabling these rescalings should work for all conversions of `ReactionSystem`s @@ -89,6 +88,7 @@ rx2 = Reaction(2*k, [B], [D], [1], [2.5]) rx3 = Reaction(2*k, [B], [D], [2.5], [2]) @named mixedsys = ReactionSystem([rx1, rx2, rx3], t, [A, B, C, D], [k, b]) osys = convert(ODESystem, mixedsys; combinatoric_ratelaws = false) +osys = complete(osys) ``` Note, when using `convert(ODESystem, mixedsys; combinatoric_ratelaws=false)` the `combinatoric_ratelaws=false` parameter must be passed. This is also true when @@ -128,6 +128,7 @@ t = default_t() rx1 = Reaction(β, [S, I], [I], [1,1], [2]) rx2 = Reaction(ν, [I], [R]) @named sir = ReactionSystem([rx1, rx2], t) +sir = complete(sir) oprob = ODEProblem(sir, [], (0.0, 250.0)) sol = solve(oprob, Tsit5()) plot(sol) @@ -163,7 +164,7 @@ Julia `Symbol`s corresponding to each variable/parameter to their values, or from ModelingToolkit symbolic variables/parameters to their values. Using `Symbol`s we have ```@example faq4 -using Catalyst, DifferentialEquations +using Catalyst, OrdinaryDiffEq rn = @reaction_network begin α, S + I --> 2I β, I --> R @@ -200,6 +201,7 @@ the second example, or one can use the `symmap_to_varmap` function to convert th `Symbol` mapping to a symbolic mapping. I.e. this works ```@example faq4 osys = convert(ODESystem, rn) +osys = complete(osys) # this works u0 = symmap_to_varmap(rn, [:S => 999.0, :I => 1.0, :R => 0.0]) From 44d539b575c2d65d241de5099508e58a9e935973 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 4 Jun 2024 16:17:13 -0400 Subject: [PATCH 169/446] up --- docs/src/faqs.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/faqs.md b/docs/src/faqs.md index 588ac7f4fb..6562418d0f 100644 --- a/docs/src/faqs.md +++ b/docs/src/faqs.md @@ -34,27 +34,27 @@ sol = solve(oprob, Tsit5()) Suppose we want to plot just species `C`, without having to know its integer index in the unknown vector. We can do this using the symbolic variable `C`, which we can get at in several ways -```julia +```@example faq1 sol[osys.C] ``` or -```julia +```@example faq1 @unpack C = osys sol[C] ``` To evaluate `C` at specific times and plot it we can just do -```julia +```@example faq1 t = range(0.0, 10.0, length=101) plot(t, sol(t, idxs = C), label = "C(t)", xlabel = "t") ``` If we want to get multiple variables we can just do -```julia +```@example faq1 @unpack A, B = osys sol(t, idxs = [A, B]) ``` Plotting multiple variables using the SciML plot recipe can be achieved like -```julia +```@example faq1 plot(sol; idxs = [A, B]) ``` From d8028aa0d88b8ddbc29121f6e5aea65a57e3995e Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 4 Jun 2024 16:48:31 -0400 Subject: [PATCH 170/446] up --- docs/src/faqs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/faqs.md b/docs/src/faqs.md index 6562418d0f..a4604f0a8a 100644 --- a/docs/src/faqs.md +++ b/docs/src/faqs.md @@ -28,7 +28,7 @@ variables ```@example faq1 u0 = [rn.A => 1.0, rn.B => 2.0, rn.C => 0.0] ps = [rn.k₊ => 1.0, rn.k₋ => 1.0] -oprob = ODEProblem(osys, [], (0.0, 10.0), []) +oprob = ODEProblem(osys, u0, (0.0, 10.0), ps) sol = solve(oprob, Tsit5()) ``` Suppose we want to plot just species `C`, without having to know its integer From 78f44eaa8b70497703042228cfcf00df5808cffc Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 4 Jun 2024 17:00:21 -0400 Subject: [PATCH 171/446] up --- docs/src/faqs.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/src/faqs.md b/docs/src/faqs.md index a4604f0a8a..d9faa38a73 100644 --- a/docs/src/faqs.md +++ b/docs/src/faqs.md @@ -26,8 +26,8 @@ observed(osys) Let's solve the system and see how to index the solution using our symbolic variables ```@example faq1 -u0 = [rn.A => 1.0, rn.B => 2.0, rn.C => 0.0] -ps = [rn.k₊ => 1.0, rn.k₋ => 1.0] +u0 = [osys.A => 1.0, osys.B => 2.0, osys.C => 0.0] +ps = [osys.k₊ => 1.0, osys.k₋ => 1.0] oprob = ODEProblem(osys, u0, (0.0, 10.0), ps) sol = solve(oprob, Tsit5()) ``` @@ -44,8 +44,8 @@ sol[C] ``` To evaluate `C` at specific times and plot it we can just do ```@example faq1 -t = range(0.0, 10.0, length=101) -plot(t, sol(t, idxs = C), label = "C(t)", xlabel = "t") +t = range(0.0, 10.0, length = 101) +plot(sol(t, idxs = C), label = "C(t)", xlabel = "t") ``` If we want to get multiple variables we can just do ```@example faq1 @@ -87,6 +87,7 @@ rx1 = Reaction(k,[B,C],[B,D], [2.5,1],[3.5, 2.5]) rx2 = Reaction(2*k, [B], [D], [1], [2.5]) rx3 = Reaction(2*k, [B], [D], [2.5], [2]) @named mixedsys = ReactionSystem([rx1, rx2, rx3], t, [A, B, C, D], [k, b]) +mixedsys = complete(mixedsys) osys = convert(ODESystem, mixedsys; combinatoric_ratelaws = false) osys = complete(osys) ``` From de79d3046be6816b6af88f591a34d048255d9b8d Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 4 Jun 2024 17:46:51 -0400 Subject: [PATCH 172/446] init --- .github/workflows/CI.yml | 2 +- .github/workflows/Documentation.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b53320ffbf..74708a6e41 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -14,7 +14,7 @@ jobs: group: - Core version: - - '1.10.2' + - '1' steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v2 diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index fc1871b19a..7e9af3e1dd 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: - version: '1.10.2' + version: '1' - name: Install dependencies run: julia --project=docs/ -e 'ENV["JULIA_PKG_SERVER"] = ""; using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy From ea99c8b5d5a85d1b534e6b7e5b697d190c9b8af9 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Wed, 5 Jun 2024 03:52:44 +0200 Subject: [PATCH 173/446] Revert "Update `Reaction` constructor" --- src/reaction.jl | 123 +++++++++++---------- test/reactionsystem_core/reaction.jl | 86 +------------- test/reactionsystem_core/reactionsystem.jl | 1 + 3 files changed, 69 insertions(+), 141 deletions(-) diff --git a/src/reaction.jl b/src/reaction.jl index f23d56f91e..23538214aa 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -72,13 +72,6 @@ function metadata_only_use_rate_check(metadata) return Bool(metadata[only_use_rate_idx][2]) end -# Used to promote a vector to the appropriate type. Takes the `vec == Any[]` case into account by -# returning an empty vector of the appropriate type. -function promote_reaction_vector(vec, type) - isempty(vec) && (return type[]) - type[value(v) for v in vec] -end - # calculates the net stoichiometry of a reaction as a vector of pairs (sub,substoich) function get_netstoich(subs, prods, sstoich, pstoich) # stoichiometry as a Dictionary @@ -92,6 +85,9 @@ function get_netstoich(subs, prods, sstoich, pstoich) [el for el in nsdict if !_iszero(el[2])] end +# Get the net stoichiometries' type. +netstoich_stoichtype(::Vector{Pair{S, T}}) where {S, T} = T + ### Reaction Structure ### """ @@ -138,19 +134,19 @@ Notes: - The three-argument form assumes all reactant and product stoichiometric coefficients are one. """ -struct Reaction{T} +struct Reaction{S, T} """The rate function (excluding mass action terms).""" rate::Any """Reaction substrates.""" - substrates::Vector{Any} + substrates::Vector """Reaction products.""" - products::Vector{Any} + products::Vector """The stoichiometric coefficients of the reactants.""" - substoich::Vector + substoich::Vector{T} """The stoichiometric coefficients of the products.""" - prodstoich::Vector + prodstoich::Vector{T} """The net stoichiometric coefficients of all species changed by the reaction.""" - netstoich::Vector{Pair{Any, T}} + netstoich::Vector{Pair{S, T}} """ `false` (default) if `rate` should be multiplied by mass action terms to give the rate law. `true` if `rate` represents the full reaction rate law. @@ -164,74 +160,83 @@ struct Reaction{T} end # Five-argument constructor accepting rate, substrates, and products, and their stoichiometries. -function Reaction(rate, subs::Vector, prods::Vector, substoich::Vector{S}, prodstoich::Vector{T}; - netstoich = [], metadata = Pair{Symbol, Any}[], - only_use_rate = metadata_only_use_rate_check(metadata), kwargs...) where {S,T} - # Error checks. - isempty(subs) && isempty(prods) && - throw(ArgumentError("A reaction requires either a non-empty substrate or product vector.")) - length(subs) != length(substoich) && - throw(ArgumentError("The substrate vector ($(subs)) and the substrate stoichiometry vector ($(substoich)) must have equal length.")) - length(prods) != length(prodstoich) && - throw(ArgumentError("The product vector ($(prods)) and the product stoichiometry vector ($(prodstoich)) must have equal length.")) +function Reaction(rate, subs, prods, substoich, prodstoich; + netstoich = nothing, metadata = Pair{Symbol, Any}[], + only_use_rate = metadata_only_use_rate_check(metadata), kwargs...) + (isnothing(prods) && isnothing(subs)) && + throw(ArgumentError("A reaction requires a non-nothing substrate or product vector.")) + (isnothing(prodstoich) && isnothing(substoich)) && + throw(ArgumentError("Both substrate and product stochiometry inputs cannot be nothing.")) + + if isnothing(subs) + prodtype = typeof(value(first(prods))) + subs = Vector{prodtype}() + !isnothing(substoich) && + throw(ArgumentError("If substrates are nothing, substrate stoichiometries have to be so too.")) + substoich = typeof(prodstoich)() + else + subs = value.(subs) + end allunique(subs) || throw(ArgumentError("Substrates can not be repeated in the list provided to `Reaction`, please modify the stoichiometry for any repeated substrates instead.")) + S = eltype(substoich) + + if isnothing(prods) + prods = Vector{eltype(subs)}() + !isnothing(prodstoich) && + throw(ArgumentError("If products are nothing, product stoichiometries have to be so too.")) + prodstoich = typeof(substoich)() + else + prods = value.(prods) + end allunique(prods) || throw(ArgumentError("Products can not be repeated in the list provided to `Reaction`, please modify the stoichiometry for any repeated products instead.")) + T = eltype(prodstoich) - # Ensures everything have uniform and correct types. - subs = promote_reaction_vector(subs, BasicSymbolic{Real}) - prods = promote_reaction_vector(prods, BasicSymbolic{Real}) + # try to get a common type for stoichiometry, using Any if have Syms stoich_type = promote_type(S, T) - (stoich_type <: Num) && (stoich_type = Any) - substoich = promote_reaction_vector(substoich, stoich_type) - prodstoich = promote_reaction_vector(prodstoich, stoich_type) + if stoich_type <: Num + stoich_type = Any + substoich′ = Any[value(s) for s in substoich] + prodstoich′ = Any[value(p) for p in prodstoich] + else + substoich′ = (S == stoich_type) ? substoich : convert.(stoich_type, substoich) + prodstoich′ = (T == stoich_type) ? prodstoich : convert.(stoich_type, prodstoich) + end - # Checks that all reactants are valid. if !(all(isvalidreactant, subs) && all(isvalidreactant, prods)) badsts = union(filter(!isvalidreactant, subs), filter(!isvalidreactant, prods)) - throw(ArgumentError("To be a valid substrate or product, non-constant species must be declared via @species, while constant species must be parameters with the isconstantspecies metadata. The following reactants do not follow this convention:\n $badsts")) + throw(ArgumentError("""To be a valid substrate or product, non-constant species must be declared via @species, while constant species must be parameters with the isconstantspecies metadata. The following reactants do not follow this convention:\n $badsts""")) end - - # Computes the net stoichiometries. - if isempty(netstoich) - netstoich = get_netstoich(subs, prods, substoich, prodstoich) - elseif typeof(netstoich) != Vector{Pair{BasicSymbolic{Real}, stoich_type}} - netstoich = Pair{BasicSymbolic{Real}, stoich_type}[ - value(ns[1]) => convert(stoich_type, ns[2]) for ns in netstoich] + + ns = if netstoich === nothing + get_netstoich(subs, prods, substoich′, prodstoich′) + else + (netstoich_stoichtype(netstoich) != stoich_type) ? + convert.(stoich_type, netstoich) : netstoich end - # Handles metadata (check that all entries are unique, remove potential `only_use_rate` - # entries, and converts to the required type. + # Check that all metadata entries are unique. (cannot use `in` since some entries may be symbolics). if !allunique(entry[1] for entry in metadata) error("Repeated entries for the same metadata encountered in the following metadata set: $([entry[1] for entry in metadata]).") end + + # Deletes potential `:only_use_rate => ` entries from the metadata. if any(:only_use_rate == entry[1] for entry in metadata) deleteat!(metadata, findfirst(:only_use_rate == entry[1] for entry in metadata)) end - metadata = convert(Vector{Pair{Symbol, Any}}, metadata) - Reaction{stoich_type}(value(rate), subs, prods, substoich, prodstoich, netstoich, only_use_rate, metadata) -end + # Ensures metadata have the correct type. + metadata = convert(Vector{Pair{Symbol, Any}}, metadata) -# Three-argument constructor. Handles the case where no stoichiometries is given -# (by assuming that all stoichiometries are `1`). -function Reaction(rate, subs::Vector, prods::Vector; kwargs...) - Reaction(rate, subs, prods, ones(Int, length(subs)), ones(Int, length(prods)); kwargs...) + Reaction(value(rate), subs, prods, substoich′, prodstoich′, ns, only_use_rate, metadata) end -# Handles cases where `nothing` is given (instead of an empty vector). -function Reaction(rate, subs::Vector, prods::Nothing, substoich::Vector, prodstoich::Nothing; kwargs...) - Reaction(rate, subs, Int64[], substoich, Int64[]; kwargs...) -end -function Reaction(rate, subs::Nothing, prods::Vector, substoich::Nothing, prodstoich::Vector; kwargs...) - Reaction(rate, Int64[], prods, Int64[], prodstoich; kwargs...) -end -function Reaction(rate, subs::Vector, prods::Nothing; kwargs...) - Reaction(rate, subs, Int64[]; kwargs...) -end -function Reaction(rate, subs::Nothing, prods::Vector; kwargs...) - Reaction(rate, Int64[], prods; kwargs...) +# Three argument constructor assumes stoichiometric coefs are one and integers. +function Reaction(rate, subs, prods; kwargs...) + sstoich = isnothing(subs) ? nothing : ones(Int, length(subs)) + pstoich = isnothing(prods) ? nothing : ones(Int, length(prods)) + Reaction(rate, subs, prods, sstoich, pstoich; kwargs...) end # Union type for `Reaction`s and `Equation`s. diff --git a/test/reactionsystem_core/reaction.jl b/test/reactionsystem_core/reaction.jl index 0f551323ce..4760641c4b 100644 --- a/test/reactionsystem_core/reaction.jl +++ b/test/reactionsystem_core/reaction.jl @@ -36,94 +36,13 @@ let @test issetequal(get_symbolics(rx4), [X, t, Y, η1, η2]) end -### Reaction Constructor Tests ### - -# Sets the default `t` to use. -t = default_t() - -# Checks that `Reaction`s can be sucesfully created using various complicated inputs. -# Checks that the `Reaction`s have the correct type, and the correct net stoichiometries are generated. -let - # Declare symbolic variables. - @parameters k n1 n2::Int32 x [isconstantspecies=true] - @species X(t) Y(t) Z(t) - @variables A(t) - - # Creates `Reaction`s. - rx1 = Reaction(k*A, [X], []) - rx2 = Reaction(k*A, [x], [Y], [1.5], [1]) - rx3 = Reaction(k*A, [x, X], [], [n1 + n2, n2], []) - rx4 = Reaction(k*A, [X, Y], [X, Y, Z], [2//3, 3], [1//3, 1, 2]) - rx5 = Reaction(k*A, [X, Y], [X, Y, Z], [2, 3], [1, n1, n2]) - rx6 = Reaction(k*A, [X], [x], [n1], [1]) - - # Check `Reaction` types. - @test rx1 isa Reaction{Int64} - @test rx2 isa Reaction{Float64} - @test rx3 isa Reaction{Any} - @test rx4 isa Reaction{Rational{Int64}} - @test rx5 isa Reaction{Any} - @test rx6 isa Reaction{Any} - - # Check `Reaction` net stoichiometries. - issetequal(rx1.netstoich, [X => -1]) - issetequal(rx2.netstoich, [x => -1.5, Y => 1.0]) - issetequal(rx3.netstoich, [x => -n1 - n2, X => -n2]) - issetequal(rx4.netstoich, [X => -1//3, Y => -2//1, Z => 2//1]) - issetequal(rx5.netstoich, [X => -1, Y => n1 - 3, Z => n2]) - issetequal(rx6.netstoich, [X => -n1, x => 1]) -end - -# Tests that various `Reaction` constructors gives identical inputs. -let - # Declare symbolic variables. - @parameters k n1 n2::Int32 - @species X(t) Y(t) Z(t) - @variables A(t) - - # Tests that the three-argument constructor generates correct result. - @test Reaction(k*A, [X], [Y, Z]) == Reaction(k*A, [X], [Y, Z], [1], [1, 1]) - - # Tests that `[]` and `nothing` can be used interchangeably. - @test Reaction(k*A, [X, Z], nothing) == Reaction(k*A, [X, Z], []) - @test Reaction(k*A, nothing, [Y, Z]) == Reaction(k*A, [], [Y, Z]) - @test Reaction(k*A, [X, Z], nothing, [n1 + n2, 2], nothing) == Reaction(k*A, [X, Z], [], [n1 + n2, 2], []) - @test Reaction(k*A, nothing, [Y, Z], nothing, [n1 + n2, 2]) == Reaction(k*A, [], [Y, Z], [], [n1 + n2, 2]) -end - -# Tests that various incorrect inputs yields errors. -let - # Declare symbolic variables. - @parameters k n1 n2::Int32 - @species X(t) Y(t) Z(t) - @variables A(t) - - # Neither substrates nor products. - @test_throws ArgumentError Reaction(k*A, [], []) - - # Substrate vector not of equal length to substrate stoichiometry vector. - @test_throws ArgumentError Reaction(k*A, [X, X, Z], [], [1, 2], []) - - # Product vector not of equal length to product stoichiometry vector. - @test_throws ArgumentError Reaction(k*A, [], [X, X, Z], [], [1, 2]) - - # Repeated substrates. - @test_throws ArgumentError Reaction(k*A, [X, X, Z], []) - - # Repeated products. - @test_throws ArgumentError Reaction(k*A, [], [Y, Z, Z]) - - # Non-valid reactants (parameter or variable). - @test_throws ArgumentError Reaction(k*A, [], [A]) - @test_throws ArgumentError Reaction(k*A, [], [k]) -end - ### Tests Metadata ### # Tests creation. # Tests basic accessor functions. # Tests that repeated metadata entries are not permitted. let + @variables t @parameters k @species X(t) X2(t) @@ -141,6 +60,7 @@ end # Tests accessors for system without metadata. let + @variables t @parameters k @species X(t) X2(t) @@ -157,6 +77,7 @@ end # Tests basic accessor functions. # Tests various metadata types. let + @variables t @parameters k @species X(t) X2(t) @@ -188,6 +109,7 @@ end # Tests the noise scaling metadata. let + @variables t @parameters k η @species X(t) X2(t) diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index 28e8a743a0..6876026e0b 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -140,6 +140,7 @@ end ### Check ODE, SDE, and Jump Functions ### # Test by evaluating drift and diffusion terms. + let u = rnd_u0(rs, rng) p = rnd_ps(rs, rng) From 84ee99d9eebcdf0921be5866aa4d273e23ecf23c Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 5 Jun 2024 09:37:11 -0400 Subject: [PATCH 174/446] init --- docs/src/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 81053b8b2c..8ad0ea19fe 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -89,7 +89,7 @@ Pkg.add("Catalyst") To solve Catalyst models and visualize solutions, it is also recommended to install DifferentialEquations.jl and Plots.jl ```julia -Pkg.add("DifferentialEquations") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` @@ -114,7 +114,7 @@ which in Jupyter notebooks will give the figure To generate and solve a mass action ODE version of the model we use ```@example ind1 -using DifferentialEquations +using OrdinaryDiffEq p = [:α => .1/1000, :β => .01] tspan = (0.0,250.0) u0 = [:S => 999.0, :I => 1.0, :R => 0.0] From 64a25a5c4996a11e179a542d3710406f8727ae7e Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 5 Jun 2024 10:15:13 -0400 Subject: [PATCH 175/446] updated ratematrix to take symbolics --- src/network_analysis.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 4201404602..0dde061ec3 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -723,7 +723,7 @@ end iscomplexbalanced(rs::ReactionSystem, parametermap) Constructively compute whether a network will have complex-balanced equilibrium -solutions, following the method in van der Schaft et al., [2015](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a dictionary, vector, or tuple ofvariable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...]. +solutions, following the method in van der Schaft et al., [2015](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a dictionary, vector, or tuple of variable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...]. """ function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) @@ -791,7 +791,7 @@ iscomplexbalanced(rs::ReactionSystem, parametermap) = error("Parameter map must """ ratematrix(rs::ReactionSystem, parametermap) - Given a reaction system with n complexes, outputs an n-by-n matrix where R_{ij} is the rate constant of the reaction between complex i and complex j. + Given a reaction system with n complexes, outputs an n-by-n matrix where R_{ij} is the rate constant of the reaction between complex i and complex j. Accepts a dictionary, vector, or tuple of variable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...]. """ function ratematrix(rs::ReactionSystem, rates::Vector{Float64}) @@ -809,11 +809,14 @@ function ratematrix(rs::ReactionSystem, rates::Vector{Float64}) ratematrix end -function ratematrix(rs::ReactionSystem, parametermap::Dict{Symbol, Float64}) +function ratematrix(rs::ReactionSystem, parametermap::Dict) if length(parametermap) != numparams(rs) - error("The number of reaction rates must be equal to the number of parameters") + error("Incorrect number of parameters specified.") end + pmap = symmap_to_varmap(rs, parametermap) + pmap = Dict(ModelingToolkit.value(k) => v for (k,v) in pmap) + rates = [substitute(rate, pmap) for rate in reactionrates(rs)] ratematrix(rs, rates) end From 107668758cfb6a1b8d6dda8e9a993ebf4f1167a9 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Wed, 5 Jun 2024 10:16:56 -0400 Subject: [PATCH 176/446] Update FormatCheck.yml --- .github/workflows/FormatCheck.yml | 36 ++++--------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml index 9cca2b9011..ce3eadc2b5 100644 --- a/.github/workflows/FormatCheck.yml +++ b/.github/workflows/FormatCheck.yml @@ -1,4 +1,4 @@ -name: format-check +name: "Format Check" on: push: @@ -9,34 +9,6 @@ on: pull_request: jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - julia-version: [1] - julia-arch: [x86] - os: [ubuntu-latest] - steps: - - uses: julia-actions/setup-julia@latest - with: - version: ${{ matrix.julia-version }} - - - uses: actions/checkout@v4 - - name: Install JuliaFormatter and format - # This will use the latest version by default but you can set the version like so: - # - # julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.13.0"))' - run: | - julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="1.0.32"))' - julia -e 'using JuliaFormatter; format(".", verbose=true)' - - name: Format check - run: | - julia -e ' - out = Cmd(`git diff --name-only`) |> read |> String - if out == "" - exit(0) - else - @error "Some files have not been formatted !!!" - write(stdout, out) - exit(1) - end' + format-check: + name: "Format Check" + uses: "SciML/.github/.github/workflows/format-check.yml@v1" From 25a69e45ab931cd1ba7c80f3abcbd413b2eea759 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 5 Jun 2024 12:22:09 -0400 Subject: [PATCH 177/446] up --- src/Catalyst.jl | 6 +- .../lattice_jump_systems.jl | 42 +- .../lattice_reaction_systems.jl | 11 +- .../spatial_ODE_systems.jl | 2 +- src/spatial_reaction_systems/utility.jl | 8 +- ...attice_reaction_systems_ODE_performance.jl | 52 +-- test/runtests.jl | 48 +-- .../lattice_reaction_systems.jl | 367 ------------------ .../lattice_reaction_systems_ODEs.jl | 0 .../lattice_reaction_systems_jumps.jl | 28 +- .../simulate_PDEs.jl | 0 11 files changed, 74 insertions(+), 490 deletions(-) delete mode 100644 test/spatial_modelling/lattice_reaction_systems.jl rename test/{spatial_modelling => spatial_reaction_systems}/lattice_reaction_systems_ODEs.jl (100%) rename test/{spatial_modelling => spatial_reaction_systems}/simulate_PDEs.jl (100%) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 39752d6901..cc4fd9c141 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -179,11 +179,11 @@ export CartesianGrid, CartesianGridReJ # (Implemented in JumpProcesses) export has_cartesian_lattice, has_masked_lattice, has_grid_lattice, has_graph_lattice, grid_dims, grid_size export make_edge_p_values, make_directed_edge_values -# Various utility functions -include("spatial_reaction_systems/utility.jl") - # Specific spatial problem types. include("spatial_reaction_systems/spatial_ODE_systems.jl") include("spatial_reaction_systems/lattice_jump_systems.jl") +# Various utility functions +include("spatial_reaction_systems/utility.jl") + end # module diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 78db1600bd..5322b49b4e 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -15,7 +15,7 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_ # Converts u0 and p to their internal forms. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. - u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) + u0 = lattice_process_u0(u0_in, species(lrs), num_verts(lrs)) # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. # These elements are length 1 vectors (if the parameter is uniform), # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). @@ -27,8 +27,8 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_ end # Builds a spatial JumpProblem from a DiscreteProblem containg a Lattice Reaction System. -function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(lrs.rs), - combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), kwargs...) +function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(reactionsystem(lrs)), + combinatoric_ratelaws = get_combinatoric_ratelaws(reactionsystem(lrs)), kwargs...) # Error checks. if !isnothing(dprob.f.sys) error("Unexpected `DiscreteProblem` passed into `JumpProblem`. Was a `LatticeReactionSystem` used as input to the initial `DiscreteProblem`?") @@ -42,10 +42,10 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator # The non-spatial DiscreteProblem have a u0 matrix with entries for all combinations of species and vertexes. hopping_constants = make_hopping_constants(dprob, lrs) sma_jumps = make_spatial_majumps(dprob, lrs) - non_spat_dprob = DiscreteProblem(reshape(dprob.u0, lrs.num_species, lrs.num_verts), dprob.tspan, first.(dprob.p[1])) + non_spat_dprob = DiscreteProblem(reshape(dprob.u0, num_species(lrs), num_verts(lrs)), dprob.tspan, first.(dprob.p[1])) return JumpProblem(non_spat_dprob, aggregator, sma_jumps; - hopping_constants, spatial_system = lrs.lattice, name, kwargs...) + hopping_constants, spatial_system = lattice(lrs), name, kwargs...) end # Creates the hopping constants from a discrete problem and a lattice reaction system. @@ -57,14 +57,14 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst # Creates the hopping constant Matrix. It contains one element for each combination of species and vertex. # Each element is a Vector, containing the outgoing hopping rates for that species, from that vertex, on that edge. - hopping_constants = [Vector{Float64}(undef, length(lrs.lattice.fadjlist[j])) - for i in 1:(lrs.num_species), j in 1:(lrs.num_verts)] + hopping_constants = [Vector{Float64}(undef, length(lattice(lrs).fadjlist[j])) + for i in 1:(num_species(lrs)), j in 1:(num_verts(lrs))] # For each edge, finds each position in `hopping_constants`. - for (e_idx, e) in enumerate(edges(lrs.lattice)) - dst_idx = findfirst(isequal(e.dst), lrs.lattice.fadjlist[e.src]) + for (e_idx, e) in enumerate(edges(lattice(lrs))) + dst_idx = findfirst(isequal(e.dst), lattice(lrs).fadjlist[e.src]) # For each species, sets that hopping rate. - for s_idx in 1:(lrs.num_species) + for s_idx in 1:(num_species(lrs)) hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx) end end @@ -77,33 +77,33 @@ end # Not sure if there is any form of performance improvement from that though. Possibly is not the case. function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) # Creates a vector, storing which reactions have spatial components. - is_spatials = [Catalyst.has_spatial_vertex_component(rx.rate, lrs; vert_ps = dprob.p[1]) for rx in reactions(lrs.rs)] + is_spatials = [Catalyst.has_spatial_vertex_component(rx.rate, lrs; vert_ps = dprob.p[1]) for rx in reactions(reactionsystem(lrs))] # Creates templates for the rates (uniform and spatial) and the stoichiometries. # We cannot fetch reactant_stoich and net_stoich from a (non-spatial) MassActionJump. # The reason is that we need to re-order the reactions so that uniform appears first, and spatial next. - u_rates = Vector{Float64}(undef, length(reactions(lrs.rs)) - count(is_spatials)) - s_rates = Matrix{Float64}(undef, count(is_spatials), lrs.num_verts) - reactant_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(lrs.rs))) - net_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(lrs.rs))) + u_rates = Vector{Float64}(undef, length(reactions(reactionsystem(lrs))) - count(is_spatials)) + s_rates = Matrix{Float64}(undef, count(is_spatials), num_verts(lrs)) + reactant_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(reactionsystem(lrs)))) + net_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(reactionsystem(lrs)))) # Loops through reactions with non-spatial rates, computes their rates and stoichiometries. cur_rx = 1; - for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs)) + for (is_spat, rx) in zip(is_spatials, reactions(reactionsystem(lrs))) is_spat && continue u_rates[cur_rx] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1])[1] substoich_map = Pair.(rx.substrates, rx.substoich) - reactant_stoich[cur_rx] = int_map(substoich_map, lrs.rs) - net_stoich[cur_rx] = int_map(rx.netstoich, lrs.rs) + reactant_stoich[cur_rx] = int_map(substoich_map, reactionsystem(lrs)) + net_stoich[cur_rx] = int_map(rx.netstoich, reactionsystem(lrs)) cur_rx += 1 end # Loops through reactions with spatial rates, computes their rates and stoichiometries. - for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs)) + for (is_spat, rx) in zip(is_spatials, reactions(reactionsystem(lrs))) is_spat || continue s_rates[cur_rx-length(u_rates),:] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1]) substoich_map = Pair.(rx.substrates, rx.substoich) - reactant_stoich[cur_rx] = int_map(substoich_map, lrs.rs) - net_stoich[cur_rx] = int_map(rx.netstoich, lrs.rs) + reactant_stoich[cur_rx] = int_map(substoich_map, reactionsystem(lrs)) + net_stoich[cur_rx] = int_map(rx.netstoich, reactionsystem(lrs)) cur_rx += 1 end # SpatialMassActionJump expects empty rate containers to be nothing. diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index a4bc32e4e1..4e521d81ac 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -328,16 +328,13 @@ MT.get_metadata(lrs::LatticeReactionSystem) = MT.get_metadata(reactionsystem(lrs # Lattice reaction systems should not be combined with compositional modelling. # Maybe these should be allowed anyway? Still feel a bit weird function MT.get_eqs(lrs::LatticeReactionSystem) - error("The `get_eqs` function is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `equations` instead.") - # MT.get_eqs(reactionsystem(lrs)) + MT.get_eqs(reactionsystem(lrs)) end function MT.get_unknowns(lrs::LatticeReactionSystem) - error("The `get_unknowns` function is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `unknowns` instead.") - # MT.get_unknowns(reactionsystem(lrs)) + MT.get_unknowns(reactionsystem(lrs)) end function MT.get_ps(lrs::LatticeReactionSystem) - error("The `get_ps` function is primarily relevant for composed models. LatticeReactionSystems cannot be composed, and hence this function should not be used. Please consider using `parameters` instead.") - # MT.get_ps(reactionsystem(lrs)) + MT.get_ps(reactionsystem(lrs)) end # Technically should not be used, but has to be declared for the `show` function to work. @@ -347,7 +344,7 @@ end # Other non-relevant getters. function MT.independent_variables(lrs::LatticeReactionSystem) - error("LatticeReactionSystems are used to model a spatial systems on a discrete lattice. The `independent_variables` function is used to retrieve the independent variables of systems with time and space independent variables. LatticeReactionSystems can only have a single independent variable (the time one). If you want to retrieve this one, please consider using the `get_iv` function.)") + MT.independent_variables(reactionsystem(lrs)) end ### Edge Parameter Value Generators ### diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 6d048c93d2..9d0f169e49 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -173,7 +173,7 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{Basi transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) # Prepares the Jacobian and forcing functions (depending on jacobian and sparsity selection). - osys = complete(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks)) + osys = complete(convert(ODESystem, reactionsystem(lrs); name, combinatoric_ratelaws, include_zero_odes, checks)) if jac # `build_jac_prototype` currently assumes a sparse (non-spatial) Jacobian. Hence compute this. # `LatticeTransportODEjac` currently assumes a dense (non-spatial) Jacobian. Hence compute this. diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 8aa5b14322..857f651f87 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -312,7 +312,7 @@ function compute_edge_value(exp, lrs::LatticeReactionSystem, edge_ps) return [exp_func([sym_val_dict[sym][1] for sym in relevant_syms]...)] end return [exp_func([get_component_value(sym_val_dict[sym], idxE) for sym in relevant_syms]...) - for idxE in 1:lrs.num_edges] + for idxE in 1:num_edges(lrs)] end # For an expression, computes its values using the provided state and parameter vectors. @@ -348,7 +348,7 @@ function compute_vertex_value(exp, lrs::LatticeReactionSystem; u=nothing, vert_p return [exp_func([sym_val_dict[sym][1] for sym in relevant_syms]...)] end return [exp_func([get_component_value(sym_val_dict[sym], idxV) for sym in relevant_syms]...) - for idxV in 1:lrs.num_verts] + for idxV in 1:num_verts(lrs)] end ### System Property Checks ### @@ -373,14 +373,14 @@ function has_spatial_vertex_component(exp, lrs::LatticeReactionSystem; u=nothing # If vertex parameter values where given, checks if any of these have non-uniform values. if !isnothing(vert_ps) exp_vert_ps = filter(sym -> any(isequal(sym), vertex_parameters(lrs)), exp_syms) - p_idxs = [ModelingToolkit.parameter_index(lrs.rs, sym) for sym in exp_vert_ps] + p_idxs = [ModelingToolkit.parameter_index(reactionsystem(lrs), sym) for sym in exp_vert_ps] any(length(vert_ps[p_idx]) != 1 for p_idx in p_idxs) && return true end # If states values where given, checks if any of these have non-uniform values. if !isnothing(u) exp_u = filter(sym -> any(isequal(sym), species(lrs)), exp_syms) - u_idxs = [ModelingToolkit.variable_index(lrs.rs, sym) for sym in exp_u] + u_idxs = [ModelingToolkit.variable_index(reactionsystem(lrs), sym) for sym in exp_u] any(length(u[u_idx]) != 1 for u_idx in u_idxs) && return true end diff --git a/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl b/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl index 74328d6979..972ea1529c 100644 --- a/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl +++ b/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl @@ -20,7 +20,7 @@ include("../spatial_test_networks.jl") # Small grid, small, non-stiff, system. let lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_graph_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lattice(lrs)), :R => 0.0] pV = SIR_p pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] oprob = ODEProblem(lrs, u0, (0.0, 500.0), [pV; pE]; jac = false) @@ -35,7 +35,7 @@ end # Large grid, small, non-stiff, system. let lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lattice(lrs)), :R => 0.0] pV = SIR_p pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] oprob = ODEProblem(lrs, u0, (0.0, 500.0), [pV; pE]; jac = false) @@ -50,7 +50,7 @@ end # Small grid, small, stiff, system. let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_graph_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + u0 = [:X => rand_v_vals(lattice(lrs), 10), :Y => rand_v_vals(lattice(lrs), 10)] pV = brusselator_p pE = [:dX => 0.2] oprob = ODEProblem(lrs, u0, (0.0, 100.0), [pV; pE]) @@ -65,7 +65,7 @@ end # Large grid, small, stiff, system. let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + u0 = [:X => rand_v_vals(lattice(lrs), 10), :Y => rand_v_vals(lattice(lrs), 10)] pV = brusselator_p pE = [:dX => 0.2] oprob = ODEProblem(lrs, u0, (0.0, 100.0), [pV; pE]) @@ -82,10 +82,10 @@ let lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, small_2d_graph_grid) u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAc => 0.005 .+ rand_v_vals(lattice(lrs), 0.005), + :Ligand => 0.005 .+ rand_v_vals(lattice(lrs), 0.005), :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :Silane => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), :CuHLigand => 0.0, :SilaneOAc => 0.0, :Styrene => 0.16, @@ -113,10 +113,10 @@ let lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, large_2d_grid) u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAc => 0.005 .+ rand_v_vals(lattice(lrs), 0.005), + :Ligand => 0.005 .+ rand_v_vals(lattice(lrs), 0.005), :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :Silane => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), :CuHLigand => 0.0, :SilaneOAc => 0.0, :Styrene => 0.16, @@ -143,14 +143,14 @@ end let lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_graph_grid) u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :w2 => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :w2v => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :v => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :vP => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :σB => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :w2σB => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), :vPp => 0.0, :phos => 0.4, ] @@ -169,14 +169,14 @@ end let lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :w2 => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :w2v => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :v => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :vP => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :σB => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :w2σB => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), :vPp => 0.0, :phos => 0.4, ] diff --git a/test/runtests.jl b/test/runtests.jl index 079c8a33e7..c2910e424b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,54 +10,8 @@ using SafeTestsets, Test ### Run Tests ### @time begin - #if GROUP == "All" || GROUP == "ModelCreation" - # Tests the `ReactionSystem` structure and its properties. - @time @safetestset "Reaction Structure" begin include("reactionsystem_core/reaction.jl") end - @time @safetestset "ReactionSystem Structure" begin include("reactionsystem_core/reactionsystem.jl") end - @time @safetestset "Higher Order Reactions" begin include("reactionsystem_core/higher_order_reactions.jl") end - @time @safetestset "Symbolic Stoichiometry" begin include("reactionsystem_core/symbolic_stoichiometry.jl") end - @time @safetestset "Parameter Type Designation" begin include("reactionsystem_core/parameter_type_designation.jl") end - @time @safetestset "Custom CRN Functions" begin include("reactionsystem_core/custom_crn_functions.jl") end - # @time @safetestset "Coupled CRN/Equation Systems" begin include("reactionsystem_core/coupled_equation_crn_systems.jl") end - @time @safetestset "Events" begin include("reactionsystem_core/events.jl") end - - # Tests model creation via the @reaction_network DSL. - @time @safetestset "DSL Basic Model Construction" begin include("dsl/dsl_basic_model_construction.jl") end - @time @safetestset "DSL Advanced Model Construction" begin include("dsl/dsl_advanced_model_construction.jl") end - @time @safetestset "DSL Options" begin include("dsl/dsl_options.jl") end - # Tests compositional and hierarchical modelling. - @time @safetestset "ReactionSystem Components Based Creation" begin include("compositional_modelling/component_based_model_creation.jl") end - #end - - #if GROUP == "All" || GROUP == "Miscellaneous-NetworkAnalysis" - # Tests various miscellaneous features. - @time @safetestset "API" begin include("miscellaneous_tests/api.jl") end - @time @safetestset "Units" begin include("miscellaneous_tests/units.jl") end - @time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end - @time @safetestset "Compound Species" begin include("miscellaneous_tests/compound_macro.jl") end - @time @safetestset "Reaction Balancing" begin include("miscellaneous_tests/reaction_balancing.jl") end - - # Tests reaction network analysis features. - @time @safetestset "Conservation Laws" begin include("network_analysis/conservation_laws.jl") end - @time @safetestset "Network Properties" begin include("network_analysis/network_properties.jl") end - #end - - #if GROUP == "All" || GROUP == "Simulation" - # Tests ODE, SDE, jump simulations, nonlinear solving, and steady state simulations. - @time @safetestset "ODE System Simulations" begin include("simulation_and_solving/simulate_ODEs.jl") end - @time @safetestset "Automatic Jacobian Construction" begin include("simulation_and_solving/jacobian_construction.jl") end - @time @safetestset "SDE System Simulations" begin include("simulation_and_solving/simulate_SDEs.jl") end - @time @safetestset "Jump System Simulations" begin include("simulation_and_solving/simulate_jumps.jl") end - @time @safetestset "Nonlinear and SteadyState System Solving" begin include("simulation_and_solving/solve_nonlinear.jl") end - - # Tests upstream SciML and DiffEq stuff. - @time @safetestset "MTK Structure Indexing" begin include("upstream/mtk_structure_indexing.jl") end - @time @safetestset "MTK Problem Inputs" begin include("upstream/mtk_problem_inputs.jl") end - #end - - @time @safetestset "PDE Systems Simulations" begin include("spatial_modelling/simulate_PDEs.jl") end - @time @safetestset "Lattice Reaction Systems" begin include("spatial_modelling/lattice_reaction_systems.jl") end + @time @safetestset "Lattice Reaction Systems" begin include("spatial_reaction_systems/lattice_reaction_systems.jl") end @time @safetestset "Lattice Reaction Systems Lattice Types" begin include("spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl") end @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_ODEs.jl") end @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end diff --git a/test/spatial_modelling/lattice_reaction_systems.jl b/test/spatial_modelling/lattice_reaction_systems.jl deleted file mode 100644 index a1fce40846..0000000000 --- a/test/spatial_modelling/lattice_reaction_systems.jl +++ /dev/null @@ -1,367 +0,0 @@ -### Preparations ### - -# Fetch packages. -using Catalyst, Graphs, Test -using Symbolics: unwrap -t = default_t() - -# Pre declares a grid. -grid = Graphs.grid([2, 2]) - - -### Tests LatticeReactionSystem Getters Correctness ### - -# Test case 1. -let - rs = @reaction_network begin - (p, 1), 0 <--> X - end - tr = @transport_reaction d X - lrs = LatticeReactionSystem(rs, [tr], grid) - - @unpack X, p = rs - d = edge_parameters(lrs)[1] - @test issetequal(species(lrs), [X]) - @test issetequal(spatial_species(lrs), [X]) - @test issetequal(parameters(lrs), [p, d]) - @test issetequal(vertex_parameters(lrs), [p]) - @test issetequal(edge_parameters(lrs), [d]) -end - -# Test case 2. -let - rs = @reaction_network begin - @parameters p1 p2 [edgeparameter=true] - end - @unpack p1, p2 = rs - - @test !isedgeparameter(p1) - @test isedgeparameter(p2) -end - -# Test case 3. -let - rs = @reaction_network begin - @parameters pX pY dX [edgeparameter=true] dY - (pX, 1), 0 <--> X - (pY, 1), 0 <--> Y - end - tr_1 = @transport_reaction dX X - tr_2 = @transport_reaction dY Y - lrs = LatticeReactionSystem(rs, [tr_1, tr_2], grid) - - @unpack X, Y, pX, pY, dX, dY = rs - @test issetequal(species(lrs), [X, Y]) - @test issetequal(spatial_species(lrs), [X, Y]) - @test issetequal(parameters(lrs), [pX, pY, dX, dY]) - @test issetequal(vertex_parameters(lrs), [pX, pY, dY]) - @test issetequal(edge_parameters(lrs), [dX]) -end - -# Test case 4. -let - rs = @reaction_network begin - @parameters dX p - (pX, 1), 0 <--> X - (pY, 1), 0 <--> Y - end - tr_1 = @transport_reaction dX X - lrs = LatticeReactionSystem(rs, [tr_1], grid) - - @unpack dX, p, X, Y, pX, pY = rs - @test issetequal(species(lrs), [X, Y]) - @test issetequal(spatial_species(lrs), [X]) - @test issetequal(parameters(lrs), [dX, p, pX, pY]) - @test issetequal(vertex_parameters(lrs), [dX, p, pX, pY]) - @test issetequal(edge_parameters(lrs), []) -end - -# Test case 5. -let - rs = @reaction_network begin - @species W(t) - @parameters pX pY dX [edgeparameter=true] dY - (pX, 1), 0 <--> X - (pY, 1), 0 <--> Y - (pZ, 1), 0 <--> Z - (pV, 1), 0 <--> V - end - @unpack dX, X, V = rs - @parameters dV dW - @species W(t) - tr_1 = TransportReaction(dX, X) - tr_2 = @transport_reaction dY Y - tr_3 = @transport_reaction dZ Z - tr_4 = TransportReaction(dV, V) - tr_5 = TransportReaction(dW, W) - lrs = LatticeReactionSystem(rs, [tr_1, tr_2, tr_3, tr_4, tr_5], grid) - - @unpack pX, pY, pZ, pV, dX, dY, X, Y, Z, V = rs - dZ, dV, dW = edge_parameters(lrs)[2:end] - @test issetequal(species(lrs), [W, X, Y, Z, V]) - @test issetequal(spatial_species(lrs), [X, Y, Z, V, W]) - @test issetequal(parameters(lrs), [pX, pY, dX, dY, pZ, pV, dZ, dV, dW]) - @test issetequal(vertex_parameters(lrs), [pX, pY, dY, pZ, pV]) - @test issetequal(edge_parameters(lrs), [dX, dZ, dV, dW]) -end - -# Test case 6. -let - rs = @reaction_network customname begin - (p, 1), 0 <--> X - end - tr = @transport_reaction d X - lrs = LatticeReactionSystem(rs, [tr], grid) - - @test nameof(lrs) == :customname -end - -### Tests Spatial Reactions Getters Correctness ### - -# Test case 1. -let - tr_1 = @transport_reaction dX X - tr_2 = @transport_reaction dY1*dY2 Y - - # @test ModelingToolkit.getname.(species(tr_1)) == ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] # species(::TransportReaction) currently not supported. - # @test ModelingToolkit.getname.(species(tr_2)) == ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] - @test ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] - @test ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] - @test ModelingToolkit.getname.(parameters(tr_1)) == [:dX] - @test ModelingToolkit.getname.(parameters(tr_2)) == [:dY1, :dY2] - - # @test issetequal(species(tr_1), [tr_1.species]) - # @test issetequal(species(tr_2), [tr_2.species]) - @test issetequal(spatial_species(tr_1), [tr_1.species]) - @test issetequal(spatial_species(tr_2), [tr_2.species]) -end - -# Test case 2. -let - rs = @reaction_network begin - @species X(t) Y(t) - @parameters dX dY1 dY2 - end - @unpack X, Y, dX, dY1, dY2 = rs - tr_1 = TransportReaction(dX, X) - tr_2 = TransportReaction(dY1*dY2, Y) - # @test isequal(species(tr_1), [X]) - # @test isequal(species(tr_1), [X]) - @test issetequal(spatial_species(tr_2), [Y]) - @test issetequal(spatial_species(tr_2), [Y]) - @test issetequal(parameters(tr_1), [dX]) - @test issetequal(parameters(tr_2), [dY1, dY2]) -end - -### Tests Spatial Reactions Generation ### - -# Tests TransportReaction with non-trivial rate. -let - rs = @reaction_network begin - @parameters dV dE [edgeparameter=true] - (p,1), 0 <--> X - end - @unpack dV, dE, X = rs - - tr = TransportReaction(dV*dE, X) - @test isequal(tr.rate, dV*dE) -end - -# Tests transport_reactions function for creating TransportReactions. -let - rs = @reaction_network begin - @parameters d - (p,1), 0 <--> X - end - @unpack d, X = rs - trs = TransportReactions([(d, X), (d, X)]) - @test isequal(trs[1], trs[2]) -end - -# Test reactions with constants in rate. -let - @species X(t) Y(t) - - tr_1 = TransportReaction(1.5, X) - tr_1_macro = @transport_reaction 1.5 X - @test isequal(tr_1.rate, tr_1_macro.rate) - @test isequal(tr_1.species, tr_1_macro.species) - - tr_2 = TransportReaction(π, Y) - tr_2_macro = @transport_reaction π Y - @test isequal(tr_2.rate, tr_2_macro.rate) - @test isequal(tr_2.species, tr_2_macro.species) -end - -### Test Interpolation ### - -# Does not currently work. The 3 tr_macro_ lines generate errors. -# Test case 1. -let - rs = @reaction_network begin - @species X(t) Y(t) Z(t) - @parameters dX dY1 dY2 dZ - end - @unpack X, Y, Z, dX, dY1, dY2, dZ = rs - rate1 = dX - rate2 = dY1*dY2 - species3 = Z - tr_1 = TransportReaction(dX, X) - tr_2 = TransportReaction(dY1*dY2, Y) - tr_3 = TransportReaction(dZ, Z) - tr_macro_1 = @transport_reaction $dX X - tr_macro_2 = @transport_reaction $(rate2) Y - # tr_macro_3 = @transport_reaction dZ $species3 # Currently does not work, something with meta programming. - - @test isequal(tr_1, tr_macro_1) - @test isequal(tr_2, tr_macro_2) # Unsure why these fails, since for components equality hold: `isequal(tr_1.species, tr_macro_1.species)` and `isequal(tr_1.rate, tr_macro_1.rate)` are both true. - # @test isequal(tr_3, tr_macro_3) -end - -### Tests Error generation ### - -# Test creation of TransportReaction with non-parameters in rate. -# Tests that it works even when rate is highly nested. -let - @species X(t) Y(t) - @parameters D1 D2 D3 - @test_throws ErrorException TransportReaction(D1 + D2*(D3 + Y), X) - @test_throws ErrorException TransportReaction(Y, X) -end - -# Network where diffusion species is not declared in non-spatial network. -let - rs = @reaction_network begin - (p, d), 0 <--> X - end - tr = @transport_reaction D Y - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) -end - -# Network where the rate depend on a species -let - rs = @reaction_network begin - @species Y(t) - (p, d), 0 <--> X - end - tr = @transport_reaction D*Y X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) -end - -# Network with edge parameter in non-spatial reaction rate. -let - rs = @reaction_network begin - @parameters p [edgeparameter=true] - (p, d), 0 <--> X - end - tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) -end - -# Network where metadata has been added in rs (which is not seen in transport reaction). -let - rs = @reaction_network begin - @species X(t) [description="Species with added metadata"] - (p, d), 0 <--> X - end - tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) - - rs = @reaction_network begin - @parameters D [description="Parameter with added metadata"] - (p, d), 0 <--> X - end - tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) -end - - -### Test Designation of Parameter Types ### -# Currently not supported. Won't be until the LatticeReactionSystem internal update is merged. - -# Checks that parameter types designated in the non-spatial `ReactionSystem` is handled correctly. -@test_broken let - # Declares LatticeReactionSystem with designated parameter types. - rs = @reaction_network begin - @parameters begin - k1 - l1 - k2::Float64 = 2.0 - l2::Float64 - k3::Int64 = 2, [description="A parameter"] - l3::Int64 - k4::Float32, [description="Another parameter"] - l4::Float32 - k5::Rational{Int64} - l5::Rational{Int64} - D1::Float32 - D2, [edgeparameter=true] - D3::Rational{Int64}, [edgeparameter=true] - end - (k1,l1), X1 <--> Y1 - (k2,l2), X2 <--> Y2 - (k3,l3), X3 <--> Y3 - (k4,l4), X4 <--> Y4 - (k5,l5), X5 <--> Y5 - end - tr1 = @transport_reaction $(rs.D1) X1 - tr2 = @transport_reaction $(rs.D2) X2 - tr3 = @transport_reaction $(rs.D3) X3 - lrs = LatticeReactionSystem(rs, [tr1, tr2, tr3], grid) - - # Loops through all parameters, ensuring that they have the correct type - p_types = Dict([ModelingToolkit.nameof(p) => typeof(unwrap(p)) for p in parameters(lrs)]) - @test p_types[:k1] == SymbolicUtils.BasicSymbolic{Real} - @test p_types[:l1] == SymbolicUtils.BasicSymbolic{Real} - @test p_types[:k2] == SymbolicUtils.BasicSymbolic{Float64} - @test p_types[:l2] == SymbolicUtils.BasicSymbolic{Float64} - @test p_types[:k3] == SymbolicUtils.BasicSymbolic{Int64} - @test p_types[:l3] == SymbolicUtils.BasicSymbolic{Int64} - @test p_types[:k4] == SymbolicUtils.BasicSymbolic{Float32} - @test p_types[:l4] == SymbolicUtils.BasicSymbolic{Float32} - @test p_types[:k5] == SymbolicUtils.BasicSymbolic{Rational{Int64}} - @test p_types[:l5] == SymbolicUtils.BasicSymbolic{Rational{Int64}} - @test p_types[:D1] == SymbolicUtils.BasicSymbolic{Float32} - @test p_types[:D2] == SymbolicUtils.BasicSymbolic{Real} - @test p_types[:D3] == SymbolicUtils.BasicSymbolic{Rational{Int64}} -end - -# Checks that programmatically declared parameters (with types) can be used in `TransportReaction`s. -# Checks that LatticeReactionSystem with non-default parameter types can be simulated. -@test_broken let - rs = @reaction_network begin - @parameters p::Float32 - (p,d), 0 <--> X - end - @parameters D::Rational{Int64} - tr = TransportReaction(D, rs.X) - lrs = LatticeReactionSystem(rs, [tr], grid) - - p_types = Dict([ModelingToolkit.nameof(p) => typeof(unwrap(p)) for p in parameters(lrs)]) - @test p_types[:p] == SymbolicUtils.BasicSymbolic{Float32} - @test p_types[:d] == SymbolicUtils.BasicSymbolic{Real} - @test p_types[:D] == SymbolicUtils.BasicSymbolic{Rational{Int64}} - - u0 = [:X => [0.25, 0.5, 2.0, 4.0]] - ps = [rs.p => 2.0, rs.d => 1.0, D => 1//2] - - # Currently broken. This requires some non-trivial reworking of internals. - # However, spatial internals have already been reworked (and greatly improved) in an unmerged PR. - # This will be sorted out once that has finished. - @test_broken false - # oprob = ODEProblem(lrs, u0, (0.0, 10.0), ps) - # sol = solve(oprob, Tsit5()) - # @test sol[end] == [1.0, 1.0, 1.0, 1.0] -end - -# Tests that LatticeReactionSystem cannot be generated where transport reactions depend on parameters -# that have a type designated in the non-spatial `ReactionSystem`. -@test_broken false -# let -# rs = @reaction_network begin -# @parameters D::Int64 -# (p,d), 0 <--> X -# end -# tr = @transport_reaction D X -# @test_throws Exception LatticeReactionSystem(rs, tr, grid) -# end \ No newline at end of file diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl similarity index 100% rename from test/spatial_modelling/lattice_reaction_systems_ODEs.jl rename to test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 8fc019d8bf..361b9d822e 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -16,23 +16,23 @@ let for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) u0_1 = [:S => 999, :I => 1, :R => 0] - u0_2 = [:S => round.(Int64, 500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => 1, :R => 0, ] - u0_3 = [:S => 950, :I => round.(Int64, 50 * rand_v_vals(lrs.lattice)), :R => round.(Int64, 50 * rand_v_vals(lrs.lattice))] - u0_4 = [:S => round.(500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => round.(50 * rand_v_vals(lrs.lattice)), :R => round.(50 * rand_v_vals(lrs.lattice))] - u0_5 = make_u0_matrix(u0_3, vertices(lrs.lattice), map(s -> Symbol(s.f), species(lrs.rs))) + u0_2 = [:S => round.(Int64, 500.0 .+ 500.0 * rand_v_vals(lattice(lrs))), :I => 1, :R => 0, ] + u0_3 = [:S => 950, :I => round.(Int64, 50 * rand_v_vals(lattice(lrs))), :R => round.(Int64, 50 * rand_v_vals(lattice(lrs)))] + u0_4 = [:S => round.(500.0 .+ 500.0 * rand_v_vals(lattice(lrs))), :I => round.(50 * rand_v_vals(lattice(lrs))), :R => round.(50 * rand_v_vals(lattice(lrs)))] + u0_5 = make_u0_matrix(u0_3, vertices(lattice(lrs)), map(s -> Symbol(s.f), species(reactionsystem(lrs)))) for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] p1 = [:α => 0.1 / 1000, :β => 0.01] - p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] + p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lattice(lrs))] p3 = [ - :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), - :β => 0.02 * rand_v_vals(lrs.lattice), + :α => 0.1 / 2000 * rand_v_vals(lattice(lrs)), + :β => 0.02 * rand_v_vals(lattice(lrs)), ] - p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) + p4 = make_u0_matrix(p1, vertices(lattice(lrs)), Symbol.(parameters(reactionsystem(lrs)))) for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. pE_1 = map(sp -> sp => 0.01, ModelingToolkit.getname.(edge_parameters(lrs))) pE_2 = map(sp -> sp => 0.01, ModelingToolkit.getname.(edge_parameters(lrs))) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), ModelingToolkit.getname.(edge_parameters(lrs))) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) + pE_3 = map(sp -> sp => rand_e_vals(lattice(lrs), 0.01), ModelingToolkit.getname.(edge_parameters(lrs))) + pE_4 = make_u0_matrix(pE_3, edges(lattice(lrs)), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) jprob = JumpProblem(lrs, dprob, NSM()) @@ -117,9 +117,9 @@ let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_3d_grid) # Create JumpProblem - u0 = [:X => 1, :Y => rand(1:10, lrs.num_verts)] + u0 = [:X => 1, :Y => rand(1:10, num_verts(lrs))] tspan = (0.0, 100.0) - ps = [:A => 1.0, :B => 5.0 .+ rand(lrs.num_verts), :dX => rand(lrs.num_edges)] + ps = [:A => 1.0, :B => 5.0 .+ rand(num_verts(lrs)), :dX => rand(num_edges(lrs))] dprob = DiscreteProblem(lrs, u0, tspan, ps) jprob = JumpProblem(lrs, dprob, NSM()) @@ -127,8 +127,8 @@ let jprob.massaction_jump.uniform_rates == [1.0, 0.5 ,10.] # 0.5 is due to combinatoric /2! in (2X + Y). jprob.massaction_jump.spatial_rates[1,:] == ps[2][2] # Test when new SII functions are ready, or we implement them in Catalyst. - # @test isequal(to_int(getfield.(reactions(lrs.rs), :netstoich)), jprob.massaction_jump.net_stoch) - # @test isequal(to_int(Pair.(getfield.(reactions(lrs.rs), :substrates),getfield.(reactions(lrs.rs), :substoich))), jprob.massaction_jump.net_stoch) + # @test isequal(to_int(getfield.(reactions(reactionsystem(lrs)), :netstoich)), jprob.massaction_jump.net_stoch) + # @test isequal(to_int(Pair.(getfield.(reactions(reactionsystem(lrs)), :substrates),getfield.(reactions(reactionsystem(lrs)), :substoich))), jprob.massaction_jump.net_stoch) # Checks that problem can be simulated. @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) diff --git a/test/spatial_modelling/simulate_PDEs.jl b/test/spatial_reaction_systems/simulate_PDEs.jl similarity index 100% rename from test/spatial_modelling/simulate_PDEs.jl rename to test/spatial_reaction_systems/simulate_PDEs.jl From 48847d4f206812ca90047c6d31b5808065c2b47a Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 5 Jun 2024 12:24:04 -0400 Subject: [PATCH 178/446] testup --- test/runtests.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index ff6a80a1e6..31456e6244 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -45,8 +45,9 @@ using SafeTestsets, Test #if GROUP == "All" || GROUP == "Spatial" # Tests spatial modelling and simulations. @time @safetestset "PDE Systems Simulations" begin include("spatial_modelling/simulate_PDEs.jl") end - @time @safetestset "Lattice Reaction Systems" begin include("spatial_modelling/lattice_reaction_systems.jl") end - @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_ODEs.jl") end + @time @safetestset "Lattice Reaction Systems" begin include("spatial_reaction_systems/lattice_reaction_systems.jl") end + @time @safetestset "Lattice Reaction Systems Lattice Types" begin include("spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl") end + @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_ODEs.jl") end @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end #if GROUP == "All" || GROUP == "Visualisation-Extensions" From 2e7709a4956b5fb59c8813cf5b72052b99345eb3 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Wed, 5 Jun 2024 13:39:55 -0400 Subject: [PATCH 179/446] Update src/Catalyst.jl --- src/Catalyst.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index dd0bcd2912..750029f189 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -7,9 +7,9 @@ using DocStringExtensions using SparseArrays, DiffEqBase, Reexport, Setfield using LaTeXStrings, Latexify, Requires using LinearAlgebra, Combinatorics -using JumpProcesses: JumpProcesses, - JumpProblem, MassActionJump, ConstantRateJump, - VariableRateJump +using JumpProcesses: JumpProcesses, JumpProblem, + MassActionJump, ConstantRateJump, VariableRateJump, + SpatialMassActionJump # ModelingToolkit imports and convenience functions we use using ModelingToolkit From 9e69861fc1385432b5ebea14a91ee7be047ba8c9 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jun 2024 08:59:40 -0400 Subject: [PATCH 180/446] up --- .../spatial_ODE_systems.jl | 72 +++++++++++++++---- src/spatial_reaction_systems/utility.jl | 17 +++-- .../lattice_reaction_systems_ODEs.jl | 2 +- 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 9d0f169e49..6822374bb0 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -8,6 +8,17 @@ struct LatticeTransportODEf{S,T} num_verts::Int64 """The number of species.""" num_species::Int64 + """The indexes of the vertex parameters in the parameter vector (`parameters(lrs)`).""" + vert_p_idxs::Vector{Int64} + """The indexes of the edge parameters in the parameter vector (`parameters(lrs)`).""" + edge_p_idxs::Vector{Int64} + """ + The non-spatial `ReactionSystem` which was used to create the `LatticeReactionSystem` contain + a set of parameters (either identical to, or a sub set of, `parameters(lrs)`). This vector + contain the indexes of the non-spatial system's parameters in `parameters(lrs)`. These are + required to manage the non-spatial ODEFunction in the spatial call. + """ + nonspatial_rs_p_idxs::Vector{Int64} """The values of the parameters that are tied to vertices.""" vert_ps::Vector{Vector{T}} """ @@ -17,10 +28,10 @@ struct LatticeTransportODEf{S,T} the parameter values in a new vertex. To avoid relocating these values repeatedly, we write them to this vector. """ - work_vert_ps::Vector{T} + work_ps::Vector{T} """ For each parameter in vert_ps, its value is a vector with a length of either num_verts or 1. - To know whenever a parameter's value needs expanding to the work_vert_ps array, its length needs checking. + To know whenever a parameter's value needs expanding to the work_ps array, its length needs checking. This check is done once, and the value is stored in this array. True means a uniform value. """ v_ps_idx_types::Vector{Bool} @@ -55,7 +66,13 @@ struct LatticeTransportODEf{S,T} # Records which parameters and rates are uniform and which are not. v_ps_idx_types = map(vp -> length(vp[2]) == 1, vert_ps) t_rate_idx_types = map(tr -> size(tr[2]) == (1,1), transport_rates) - + + # Computes the indexes of various parameters in in the `parameters(lrs)` vector. + vert_p_idxs = subset_indexes_of(vertex_parameters(lrs), parameters(lrs)) + edge_p_idxs = subset_indexes_of(edge_parameters(lrs), parameters(lrs)) + nonspatial_rs_p_idxs = subset_indexes_of(parameters(reactionsystem(lrs)), parameters(lrs)) + + # Computes the indexes of the vertex parameters in the vector of parameters. # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a # vector). This vector is already sorted according to the order of the parameters. Here, we extract # its values only and put them into `vert_ps`. @@ -70,11 +87,12 @@ struct LatticeTransportODEf{S,T} end end - # Declares `work_vert_ps` (used as storage during computation) and the edge iterator. - work_vert_ps = zeros(length(vert_ps)) + # Declares `work_ps` (used as storage during computation) and the edge iterator. + work_ps = zeros(length(parameters(lrs))) edge_iterator = Catalyst.edge_iterator(lrs) - new{S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_ps, work_vert_ps, - v_ps_idx_types, transport_rates, t_rate_idx_types, leaving_rates, edge_iterator) + new{S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_p_idxs, edge_p_idxs, + nonspatial_rs_p_idxs, vert_ps, work_ps, v_ps_idx_types, transport_rates, + t_rate_idx_types, leaving_rates, edge_iterator) end end @@ -86,6 +104,17 @@ struct LatticeTransportODEjac{R,S,T} num_verts::Int64 """The number of species.""" num_species::Int64 + """The indexes of the vertex parameters in the parameter vector (`parameters(lrs)`).""" + vert_p_idxs::Vector{Int64} + """The indexes of the edge parameters in the parameter vector (`parameters(lrs)`).""" + edge_p_idxs::Vector{Int64} + """ + The non-spatial `ReactionSystem` which was used to create the `LatticeReactionSystem` contain + a set of parameters (either identical to, or a sub set of, `parameters(lrs)`). This vector + contain the indexes of the non-spatial system's parameters in `parameters(lrs)`. These are + required to manage the non-spatial ODEFunction in the spatial call. + """ + nonspatial_rs_p_idxs::Vector{Int64} """The values of the parameters that are tied to vertices.""" vert_ps::Vector{Vector{S}} """ @@ -95,10 +124,10 @@ struct LatticeTransportODEjac{R,S,T} the parameter values in a new vertex. To avoid relocating these values repeatedly, we write them to this vector. """ - work_vert_ps::Vector{S} + work_ps::Vector{S} """ For each parameter in vert_ps, its value is a vector with a length of either num_verts or 1. - To know whenever a parameter's value needs expanding to the work_vert_ps array, its length needs checking. + To know whenever a parameter's value needs expanding to the work_ps array, its length needs checking. This check is done once, and the value is stored in this array. True means a uniform value. """ v_ps_idx_types::Vector{Bool} @@ -110,18 +139,30 @@ struct LatticeTransportODEjac{R,S,T} function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{S}}}, jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, lrs::LatticeReactionSystem, sparse::Bool) where {R,S} + + # Computes the indexes of various parameters in in the `parameters(lrs)` vector. + vert_p_idxs = subset_indexes_of(vertex_parameters(lrs), parameters(lrs)) + edge_p_idxs = subset_indexes_of(edge_parameters(lrs), parameters(lrs)) + nonspatial_rs_p_idxs = subset_indexes_of(parameters(reactionsystem(lrs)), parameters(lrs)) + # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a # vector). This vector is already sorted according to the order of the parameters. Here, we extract # its values only and put them into `vert_ps`. vert_ps = [vp[2] for vp in vert_ps] - work_vert_ps = zeros(num_verts(lrs)) + work_ps = zeros(length(parameters(lrs))) v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) - new{R,S,typeof(jac_transport)}(ofunc, num_verts(lrs), num_species(lrs) , vert_ps, - work_vert_ps, v_ps_idx_types, sparse, jac_transport) + new{R,S,typeof(jac_transport)}(ofunc, num_verts(lrs), num_species(lrs) , vert_p_idxs, + edge_p_idxs, nonspatial_rs_p_idxs, vert_ps, + work_ps, v_ps_idx_types, sparse, jac_transport) end end +# For each symbolic in syms1, returns a vector with their indexes in syms2. +function subset_indexes_of(syms1, syms2) + [findfirst(isequal(sym1, sym2) for sym2 in syms2) for sym1 in syms1] +end + ### ODEProblem ### # Creates an ODEProblem from a LatticeReactionSystem. @@ -155,7 +196,8 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, combinatoric_ratelaws, remove_conserved, checks) # Combines `vert_ps` and `edge_ps` to a single vector with values only (not a map). Creates ODEProblem. - ps = [p[2] for p in [vert_ps; edge_ps]] + pval_dict = Dict([vert_ps; edge_ps]) + ps = [pval_dict[p] for p in parameters(lrs)] return ODEProblem(ofun, u0, tspan, ps, args...; kwargs...) end @@ -288,7 +330,7 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) update_work_vert_ps!(f_func, p, vert_i) # Evaluate reaction contributions to du at vert_i. - f_func.ofunc((@view du[idxs]), (@view u[idxs]), f_func.work_vert_ps, t) + f_func.ofunc((@view du[idxs]), (@view u[idxs]), nonspatial_ps(f_func), t) end # s_idx is the species index among transport species, s is the index among all species. @@ -316,7 +358,7 @@ function (jac_func::LatticeTransportODEjac)(J, u, p, t) for vert_i in 1:(jac_func.num_verts) idxs = get_indexes(vert_i, jac_func.num_species) update_work_vert_ps!(jac_func, p, vert_i) - jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), jac_func.work_vert_ps, t) + jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), nonspatial_ps(jac_func), t) end # Updates for the spatial reactions (adds the Jacobian values from the transportation reactions). diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 857f651f87..2a05303c1f 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -268,21 +268,28 @@ function get_transport_rate(trans_s_idx::Int64, f_func::LatticeTransportODEf, ed get_transport_rate(f_func.transport_rates[trans_s_idx][2], edge, f_func.t_rate_idx_types[trans_s_idx]) end -# Updates the internal work_vert_ps vector for a given vertex. +# Updates the internal work_ps vector for a given vertex. Updates only the parameters that +# actually are vertex parameters. # To this vector, we write the system's parameter values at the specific vertex. -function update_work_vert_ps!(work_vert_ps::Vector{S}, all_ps::Vector{T}, vert::Int64, - vert_ps_idx_types::Vector{Bool}) where {S,T} +function update_work_vert_ps!(work_ps::Vector{S}, vert_p_idxs::Vector{Int64}, all_ps::Vector{T}, + vert::Int64, vert_ps_idx_types::Vector{Bool}) where {S,T} # Loops through all parameters. for (idx,loc_type) in enumerate(vert_ps_idx_types) # If the parameter is uniform across the spatial structure, it will have a length-1 value vector # (which value we write to the work vector). # Else, we extract it value at the specific location. - work_vert_ps[idx] = (loc_type ? all_ps[idx][1] : all_ps[idx][vert]) + work_ps[vert_p_idxs[idx]] = (loc_type ? all_ps[vert_p_idxs[idx]][1] : all_ps[vert_p_idxs[idx]][vert]) end end # Input is either a LatticeTransportODEf or LatticeTransportODEjac function (which fields we pass on). function update_work_vert_ps!(lt_ode_func, all_ps::Vector{T}, vert::Int64) where {T} - return update_work_vert_ps!(lt_ode_func.work_vert_ps, all_ps, vert, lt_ode_func.v_ps_idx_types) + return update_work_vert_ps!(lt_ode_func.work_ps, lt_ode_func.vert_p_idxs, all_ps, vert, lt_ode_func.v_ps_idx_types) +end + +# Fetches the parameter values that currently are in the work parameter vector and which +# corresponds to the parameters of the non-spatial `ReactionSystem` stored in the `ReactionSystem`. +function nonspatial_ps(lt_ode_func) + return @view lt_ode_func.work_ps[lt_ode_func.nonspatial_rs_p_idxs] end # Expands a u0/p information stored in Vector{Vector{}} for to Matrix form diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 7c62bc7892..66b8f41952 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -89,7 +89,7 @@ end # Checks that non-spatial brusselator simulation is identical to all on an unconnected lattice. let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, unconnected_graph) - u0 = [:X => 2.0 + 2.0 * rand(rng), :Y => 10.0 + 10.0 * rand(rng)] + u0 = [:X => 2.0 + 2.0 * rand(rng), :Y => 10.0 * (1.0 * rand(rng))] pV = brusselator_p pE = [:dX => 0.2] oprob_nonspatial = ODEProblem(brusselator_system, u0, (0.0, 100.0), pV) From ac3057b563cc937fa34ce160987de50ecf1226eb Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jun 2024 11:02:28 -0400 Subject: [PATCH 181/446] Update SymbolicUtils compat --- Project.toml | 3 ++- src/reactionsystem_conversions.jl | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index d87c0cd88b..2ccc5fbc24 100644 --- a/Project.toml +++ b/Project.toml @@ -56,8 +56,9 @@ Requires = "1.0" RuntimeGeneratedFunctions = "0.5.12" Setfield = "1" StructuralIdentifiability = "0.5.1" -SymbolicUtils = "1.0.3" Symbolics = "5.27" +SymbolicUtils = "2.0.2" +TermInterface = "0.4.1" Unitful = "1.12.4" julia = "1.10" diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index d87cf7c3da..e0b230bb10 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -560,7 +560,7 @@ function nonlinear_convert_differentials_check(rs::ReactionSystem) # If the contenct of the differential is not a variable (and nothing more). # If either of this is a case, throws the warning. if Symbolics._occursin(Symbolics.is_derivative, eq.rhs) || - !iscall(eq.lhs) || + !Symbolics.is_derivative(eq.lhs) || !isequal(Symbolics.operation(eq.lhs), Differential(get_iv(rs))) || (length(arguments(eq.lhs)) != 1) || !any(isequal(arguments(eq.lhs)[1]), nonspecies(rs)) From 6e500b765c737d272d57cb00df760edf87191b25 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jun 2024 11:47:34 -0400 Subject: [PATCH 182/446] init --- .../serialisation_support.jl | 23 ++--- .../serialise_fields.jl | 98 +++++++++---------- .../serialise_reactionsystem.jl | 10 +- .../reactionsystem_serialisation.jl | 33 +++++-- 4 files changed, 89 insertions(+), 75 deletions(-) diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 62b366ca5b..40a90a5f46 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -36,7 +36,7 @@ function push_field(file_text::String, rn::ReactionSystem, annotate::Bool, top_l has_component, get_comp_string, get_comp_annotation = comp_funcs has_component(rn) || (return (file_text, false)) - # Prepares the text creating the field. For non-top level systems, adds `local `. Observables + # Prepares the text creating the field. For non-top level systems, add `local `. Observables # must be handled differently (as the declaration is not at the beginning of the code for these). # The independent variables is not declared as a variable, and also should not have a `1ocal `. write_string = get_comp_string(rn) @@ -54,7 +54,7 @@ function push_field(file_text::String, rn::ReactionSystem, annotate::Bool, top_l return (file_text * write_string, true) end -# Generic function for creating an string for an unsupported argument. +# Generic function for creating an string for a unsupported argument. function get_unsupported_comp_string(component::String) @warn "Writing ReactionSystem models with $(component) is currently not supported. This field is not written to the file." return "" @@ -82,9 +82,10 @@ function syms_2_strings(syms) return get_substring_end("$(convert(Vector{Any}, strip_called_syms))", 4, 0) end -# Converts a vector of symbolics (e.g. the species or parameter vectors) to a string corresponding to -# the code required to declare them (potential @parameters or @species commands must still be added). -# The `multiline_format` option formats it with a `begin ... end` block and declarations on separate lines. +# Converts a vector of symbolic variables (e.g. the species or parameter vectors) to a string +# corresponding to the code required to declare them (potential @parameters or @species commands +# must still be added). The `multiline_format` option formats it with a `begin ... end` block +# and declarations on separate lines. function syms_2_declaration_string(syms; multiline_format = false) decs_string = (multiline_format ? " begin" : "") for sym in syms @@ -102,7 +103,7 @@ function sym_2_declaration_string(sym; multiline_format = false) # Creates the basic symbol. The `"$(sym)"` ensures that we get e.g. "X(t)" and not "X". dec_string = "$(sym)" - # If the symbol have a non-default type, appends the declaration of this. + # If the symbol has a non-default type, appends the declaration of this. # Assumes that the type is on the form `SymbolicUtils.BasicSymbolic{X}`. Contain error checks # to ensure that this is the case. if !(sym isa SymbolicUtils.BasicSymbolic{Real}) @@ -136,7 +137,7 @@ end # Converts a generic value to a String. Handles each type of value separately. Unsupported values might # not necessarily generate valid code, and hence throw errors. Primarily used to write default values -# and metadata values (which hopefully almost exclusively) has simple, supported, types. Ideally, +# and metadata values (which hopefully almost exclusively) have simple, supported, types. Ideally, # more supported types can be added here. x_2_string(x::Num) = expression_2_string(x) x_2_string(x::SymbolicUtils.BasicSymbolic{<:Real}) = expression_2_string(x) @@ -261,7 +262,7 @@ function get_dep_syms(sym) return Symbolics.get_variables(ModelingToolkit.getdefault(sym)) end -# Checks if a symbolic depends on an symbolics in a vector being declared. +# Checks if a symbolic depends on a symbolics in a vector being declared. # Because Symbolics has to utilise `isequal`, the `isdisjoint` function cannot be used. function depends_on(sym, syms) dep_syms = get_dep_syms(sym) @@ -275,8 +276,8 @@ end # For a set of remaining parameters/species/variables (remaining_syms), return this split into # two sets: -# One with those that does not depend on any sym in `all_remaining_syms`. -# One with those that does depend on at least one sym in `all_remaining_syms`. +# One with those that do not depend on any sym in `all_remaining_syms`. +# One with those that do depend on at least one sym in `all_remaining_syms`. # The first set is returned. Next `remaining_syms` is updated to be the second set. function dependency_split!(remaining_syms, all_remaining_syms) writable_syms = filter(sym -> !depends_on(sym, all_remaining_syms), remaining_syms) @@ -289,7 +290,7 @@ end # Checks if a symbolic's declaration is "complicated". The declaration is considered complicated -# if it have metadata, default value, or type designation that must be declared. +# if it has metadata, default value, or type designation that must be declared. function complicated_declaration(sym) isempty(get_metadata_to_declare(sym)) || (return true) ModelingToolkit.hasdefault(sym) && (return true) diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index 964e3bb617..09ae2fe66a 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -1,7 +1,7 @@ ### Handles Independent Variables ### -# Checks if the reaction system have any independent variable. True for all valid reaction systems. -function has_iv(rn::ReactionSystem) +# Checks if the reaction system has any independent variable. True for all valid reaction systems. +function seri_has_iv(rn::ReactionSystem) return true end @@ -17,13 +17,13 @@ function get_iv_annotation(rn::ReactionSystem) end # Combines the 3 independent variable-related functions in a constant tuple. -IV_FS = (has_iv, get_iv_string, get_iv_annotation) +IV_FS = (seri_has_iv, get_iv_string, get_iv_annotation) ### Handles Spatial Independent Variables ### -# Checks if the reaction system have any spatial independent variables. -function has_sivs(rn::ReactionSystem) +# Checks if the reaction system has any spatial independent variables. +function seri_has_sivs(rn::ReactionSystem) return !isempty(get_sivs(rn)) end @@ -38,7 +38,7 @@ function get_sivs_annotation(rn::ReactionSystem) end # Combines the 3 independent variables-related functions in a constant tuple. -SIVS_FS = (has_sivs, get_sivs_string, get_sivs_annotation) +SIVS_FS = (seri_has_sivs, get_sivs_string, get_sivs_annotation) ### Handles Species, Variables, and Parameters ### @@ -46,15 +46,15 @@ SIVS_FS = (has_sivs, get_sivs_string, get_sivs_annotation) # Function which handles the addition of species, variable, and parameter declarations to the file # text. These must be handled as a unity in case there are default value dependencies between these. function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) - # Fetches the systems parameters, species, and variables. Computes the `has_` `Bool`s. + # Fetches the system's parameters, species, and variables. Computes the `has_` `Bool`s. ps_all = get_ps(rn) sps_all = get_species(rn) vars_all = filter(!isspecies, get_unknowns(rn)) - has_ps = has_parameters(rn) - has_sps = has_species(rn) - has_vars = has_variables(rn) + has_ps = seri_has_parameters(rn) + has_sps = seri_has_species(rn) + has_vars = seri_has_variables(rn) - # Checks which sets have dependencies which requires managing. + # Checks which sets have dependencies which require managing. p_deps = any(depends_on(p, [ps_all; sps_all; vars_all]) for p in ps_all) sp_deps = any(depends_on(sp, [sps_all; vars_all]) for sp in sps_all) var_deps = any(depends_on(var, vars_all) for var in vars_all) @@ -93,7 +93,7 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, t # Pre-declares the sets with written/remaining parameters/species/variables. # Whenever all/none are written depends on whether there were any initial dependencies. - # `deepcopy` is required as these gets mutated by `dependency_split!`. + # `deepcopy` is required as these get mutated by `dependency_split!`. remaining_ps = (p_deps ? deepcopy(ps_all) : []) remaining_sps = (sp_deps ? deepcopy(sps_all) : []) remaining_vars = (var_deps ? deepcopy(vars_all) : []) @@ -113,7 +113,7 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, t isempty(writable_vars) || @string_append! us_n_ps_string get_variables_string(writable_vars) "\n" end - # For parameters, species, and/or variables with dependencies, creates final vectors. + # For parameters, species, and/or variables with dependencies, create final vectors. p_deps && (@string_append! us_n_ps_string "ps = " syms_2_strings(ps_all) "\n") sp_deps && (@string_append! us_n_ps_string "sps = " syms_2_strings(sps_all) "\n") var_deps && (@string_append! us_n_ps_string "vars = " syms_2_strings(vars_all) "\n") @@ -127,17 +127,17 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, t us_n_ps_string = replace(us_n_ps_string, "\nvars = " => "\nlocal vars = ") end - # Merges the file text with `us_n_ps_string` and return the final outputs. + # Merges the file text with `us_n_ps_string` and returns the final outputs. return file_text * us_n_ps_string, has_ps, has_sps, has_vars end ### Handles Parameters ### -# Unlike most other fields, there are not called via `push_field`, but rather via `handle_us_n_ps`. +# Unlike most other fields, these are not called via `push_field`, but rather via `handle_us_n_ps`. # Hence they work slightly differently. -# Checks if the reaction system have any parameters. -function has_parameters(rn::ReactionSystem) +# Checks if the reaction system has any parameters. +function seri_has_parameters(rn::ReactionSystem) return !isempty(get_ps(rn)) end @@ -156,11 +156,11 @@ end ### Handles Species ### -# Unlike most other fields, there are not called via `push_field`, but rather via `handle_us_n_ps`. +# Unlike most other fields, these are not called via `push_field`, but rather via `handle_us_n_ps`. # Hence they work slightly differently. -# Checks if the reaction system have any species. -function has_species(rn::ReactionSystem) +# Checks if the reaction system has any species. +function seri_has_species(rn::ReactionSystem) return !isempty(get_species(rn)) end @@ -179,11 +179,11 @@ end ### Handles Variables ### -# Unlike most other fields, there are not called via `push_field`, but rather via `handle_us_n_ps`. +# Unlike most other fields, these are not called via `push_field`, but rather via `handle_us_n_ps`. # Hence they work slightly differently. -# Checks if the reaction system have any variables. -function has_variables(rn::ReactionSystem) +# Checks if the reaction system has any variables. +function seri_has_variables(rn::ReactionSystem) return length(get_unknowns(rn)) > length(get_species(rn)) end @@ -201,13 +201,13 @@ function get_variables_annotation(rn::ReactionSystem) end # Combines the 3 variables-related functions in a constant tuple. -VARIABLES_FS = (has_variables, get_variables_string, get_variables_annotation) +VARIABLES_FS = (seri_has_variables, get_variables_string, get_variables_annotation) ### Handles Reactions ### -# Checks if the reaction system have any reactions. -function has_reactions(rn::ReactionSystem) +# Checks if the reaction system has any reactions. +function seri_has_reactions(rn::ReactionSystem) return length(reactions(rn)) != 0 end @@ -265,14 +265,14 @@ function get_reactions_annotation(rn::ReactionSystem) return "Reactions:" end -# Combines the 3 reactions-related functions in a constant tuple. -REACTIONS_FS = (has_reactions, get_reactions_string, get_reactions_annotation) +# Combines the 3 reaction-related functions in a constant tuple. +REACTIONS_FS = (seri_has_reactions, get_reactions_string, get_reactions_annotation) ### Handles Equations ### -# Checks if the reaction system have any equations. -function has_equations(rn::ReactionSystem) +# Checks if the reaction system has any equations. +function seri_has_equations(rn::ReactionSystem) return length(get_eqs(rn)) > length(get_rxs(rn)) end @@ -302,13 +302,13 @@ function get_equations_annotation(rn::ReactionSystem) end # Combines the 3 equations-related functions in a constant tuple. -EQUATIONS_FS = (has_equations, get_equations_string, get_equations_annotation) +EQUATIONS_FS = (seri_has_equations, get_equations_string, get_equations_annotation) ### Handles Observables ### -# Checks if the reaction system have any observables. -function has_observed(rn::ReactionSystem) +# Checks if the reaction system has any observables. +function seri_has_observed(rn::ReactionSystem) return !isempty(observed(rn)) end @@ -353,13 +353,13 @@ function get_observed_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -OBSERVED_FS = (has_observed, get_observed_string, get_observed_annotation) +OBSERVED_FS = (seri_has_observed, get_observed_string, get_observed_annotation) ### Handles Continuous Events ### -# Checks if the reaction system have any continuous events. -function has_continuous_events(rn::ReactionSystem) +# Checks if the reaction system have has continuous events. +function seri_has_continuous_events(rn::ReactionSystem) return !isempty(MT.get_continuous_events(rn)) end @@ -410,13 +410,13 @@ function get_continuous_events_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -CONTINUOUS_EVENTS_FS = (has_continuous_events, get_continuous_events_string, get_continuous_events_annotation) +CONTINUOUS_EVENTS_FS = (seri_has_continuous_events, get_continuous_events_string, get_continuous_events_annotation) ### Handles Discrete Events ### -# Checks if the reaction system have any discrete events. -function has_discrete_events(rn::ReactionSystem) +# Checks if the reaction system has any discrete events. +function seri_has_discrete_events(rn::ReactionSystem) return !isempty(MT.get_discrete_events(rn)) end @@ -466,17 +466,17 @@ function get_discrete_events_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -DISCRETE_EVENTS_FS = (has_discrete_events, get_discrete_events_string, get_discrete_events_annotation) +DISCRETE_EVENTS_FS = (seri_has_discrete_events, get_discrete_events_string, get_discrete_events_annotation) ### Handles Systems ### # Specific `push_field` function, which is used for the system field (where the annotation option # must be passed to the `get_component_string` function). Since non-ReactionSystem systems cannot be -# written to file, this functions throws an error if any such systems are encountered. +# written to file, this function throws an error if any such systems are encountered. function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) - # Checks whther there are any subsystems, and if these are ReactionSystems. - has_systems(rn) || (return (file_text, false)) + # Checks whether there are any subsystems, and if these are ReactionSystems. + seri_has_systems(rn) || (return (file_text, false)) if any(!(system isa ReactionSystem) for system in MT.get_systems(rn)) error("Tries to write a ReactionSystem to file which have non-ReactionSystem subs-systems. This is currently not possible.") end @@ -489,8 +489,8 @@ function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Boo return (file_text * write_string, true) end -# Checks if the reaction system have any systems. -function has_systems(rn::ReactionSystem) +# Checks if the reaction system has any systems. +function seri_has_systems(rn::ReactionSystem) return !isempty(MT.get_systems(rn)) end @@ -519,13 +519,13 @@ function get_systems_annotation(rn::ReactionSystem) end # Combines the 3 systems-related functions in a constant tuple. -SYSTEMS_FS = (has_systems, get_systems_string, get_systems_annotation) +SYSTEMS_FS = (seri_has_systems, get_systems_string, get_systems_annotation) ### Handles Connection Types ### -# Checks if the reaction system have any connection types. -function has_connection_type(rn::ReactionSystem) +# Checks if the reaction system has any connection types. +function seri_has_connection_type(rn::ReactionSystem) return false end @@ -540,4 +540,4 @@ function get_connection_type_annotation(rn::ReactionSystem) end # Combines the 3 connection types-related functions in a constant tuple. -CONNECTION_TYPE_FS = (has_connection_type, get_connection_type_string, get_connection_type_annotation) \ No newline at end of file +CONNECTION_TYPE_FS = (seri_has_connection_type, get_connection_type_string, get_connection_type_annotation) \ No newline at end of file diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index 3dc61c5c75..7885c6a105 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -53,9 +53,9 @@ function get_full_system_string(rn::ReactionSystem, annotate::Bool, top_level::B file_text = "" # Goes through each type of system component, potentially adding it to the string. - # Species, variables, and parameters must be handled differently in case there is default-values + # Species, variables, and parameters must be handled differently in case there are default values # dependencies between them. - # Systems uses custom `push_field` function as these requires the annotation `Bool`to be passed + # Systems use custom `push_field` function as these require the annotation `Bool`to be passed # to the function that creates the next sub-system declarations. file_text, _ = push_field(file_text, rn, annotate, top_level, IV_FS) file_text, has_sivs = push_field(file_text, rn, annotate, top_level, SIVS_FS) @@ -68,8 +68,8 @@ function get_full_system_string(rn::ReactionSystem, annotate::Bool, top_level::B file_text, has_systems = push_systems_field(file_text, rn, annotate, top_level) file_text, has_connection_type = push_field(file_text, rn, annotate, top_level, CONNECTION_TYPE_FS) - # Finalises the system. Creates the final `ReactionSystem` call. - # Enclose everything ing a `let ... end` block. + # Finalise the system. Creates the final `ReactionSystem` call. + # Enclose everything in a `let ... end` block. rs_creation_code = make_reaction_system_call(rn, annotate, top_level, has_sivs, has_species, has_variables, has_parameters, has_reactions, has_equations, has_observed, has_continuous_events, @@ -82,7 +82,7 @@ function get_full_system_string(rn::ReactionSystem, annotate::Bool, top_level::B end # Creates a ReactionSystem call for creating the model. Adds all the correct inputs to it. The input -# `has_` `Bool`s described which inputs are used. If model is `complete`, this is handled here. +# `has_` `Bool`s described which inputs are used. If the model is `complete`, this is handled here. function make_reaction_system_call(rs::ReactionSystem, annotate, top_level, has_sivs, has_species, has_variables, has_parameters, has_reactions, has_equations, has_observed, has_continuous_events, has_discrete_events, diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index eab88d0abf..b06c434892 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -359,17 +359,11 @@ end # Tests for (slightly more) complicate system created via the DSL. # Tests for cases where the number of input is untested (i.e. multiple observables and continuous # events, but single equations and discrete events). -# Currently broken due to Symbolics doing something weird with observable variables, where these -# end up not being internal due to something internal in symbolics. I have tried tracking down the -# obscure symbolics subfields. +# Tests with and without `safety_check`. let # Declares the model. rs = @reaction_network begin @equations D(V) ~ 1 - V - @observables begin - X2 ~ 2*X - X3 ~ 3*X - end @continuous_events begin [X ~ 5.0] => [X ~ X + 1.0] [X ~ 20.0] => [X ~ X - 1.0] @@ -379,11 +373,30 @@ let end # Checks that serialisation works. - save_reactionsystem("serialised_rs.jl", rs; safety_check = false) - @test_broken isequal(rs, include("../serialised_rs.jl")) - rm("serialised_rs.jl") + save_reactionsystem("serialised_rs_1.jl", rs) + save_reactionsystem("serialised_rs_2.jl", rs; safety_check = false) + isequal(rs, include("../serialised_rs_1.jl")) + isequal(rs, include("../serialised_rs_2.jl")) + rm("serialised_rs_1.jl") + rm("serialised_rs_2.jl") end +# Tests for system where species depends on multiple independent variables. +# Tests for system where variables depends on multiple independent variables. +let + rs = @reaction_network begin + @ivs t x y z + @parameters p + @species X(t,x,y) Y(t,x,y) XY(t,x,y) Z(t,x,y) + @variables V(t,x,z) + (kB,kD), X + Y <--> XY + end + save_reactionsystem("serialised_rs.jl", rs) + @test !ModelingToolkit.isequal(rs, include("../serialised_rs.jl")) + rm("serialised_rs.jl.jl") +end + + ### Other Tests ### # Tests that an error is generated when non-`ReactionSystem` subs-systems are used. From 4b61e467cc6bd2608f9fcef3c696871e7111171c Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jun 2024 12:42:30 -0400 Subject: [PATCH 183/446] up --- src/reactionsystem_conversions.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index e0b230bb10..c37c5b12b3 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -443,8 +443,8 @@ end # Finds and differentials in an expression, and sets these to 0. function remove_diffs(expr) - if Symbolics._occursin(Symbolics.is_derivative, expr) - return Symbolics.replace(expr, diff_2_zero) + if Symbolics.hasnode(Symbolics.is_derivative, expr) + return Symbolics.replacenode(expr, diff_2_zero) else return expr end From bb2c293f701cf5c428f6bf02dcdbe3e40337ff33 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jun 2024 16:18:12 -0400 Subject: [PATCH 184/446] up --- test/miscellaneous_tests/reactionsystem_serialisation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index b06c434892..1eac2e6985 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -392,7 +392,7 @@ let (kB,kD), X + Y <--> XY end save_reactionsystem("serialised_rs.jl", rs) - @test !ModelingToolkit.isequal(rs, include("../serialised_rs.jl")) + @test ModelingToolkit.isequal(rs, include("../serialised_rs.jl")) rm("serialised_rs.jl.jl") end From d3b80d6e7f8cde70f911e365e41ff59ee0eb179a Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 10:04:01 -0400 Subject: [PATCH 185/446] writing fix --- test/miscellaneous_tests/reactionsystem_serialisation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index 1eac2e6985..bc3845573a 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -393,7 +393,7 @@ let end save_reactionsystem("serialised_rs.jl", rs) @test ModelingToolkit.isequal(rs, include("../serialised_rs.jl")) - rm("serialised_rs.jl.jl") + rm("serialised_rs.jl") end From b7b6e8b69413a9ce93e8cc7d7f65e023b36cb7c4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 10:13:53 -0400 Subject: [PATCH 186/446] init --- docs/make.jl | 24 +- docs/pages.jl | 20 +- ext/CatalystBifurcationKitExtension.jl | 2 +- .../bifurcation_kit_extension.jl | 22 +- ext/CatalystHomotopyContinuationExtension.jl | 2 +- .../homotopy_continuation_extension.jl | 32 ++- ...alystStructuralIdentifiabilityExtension.jl | 2 +- .../structural_identifiability_extension.jl | 64 +++--- src/Catalyst.jl | 6 +- src/chemistry_functionality.jl | 56 +++-- src/dsl.jl | 179 ++++++++------- src/expression_utils.jl | 34 +-- src/graphs.jl | 17 +- src/latexify_recipes.jl | 15 +- src/network_analysis.jl | 87 ++++---- src/reaction.jl | 26 +-- src/reactionsystem.jl | 195 +++++++++-------- src/reactionsystem_conversions.jl | 206 +++++++++--------- .../serialisation_support.jl | 89 ++++---- .../serialise_fields.jl | 66 +++--- .../serialise_reactionsystem.jl | 57 +++-- src/registered_functions.jl | 19 +- .../lattice_jump_systems.jl | 54 +++-- .../lattice_reaction_systems.jl | 29 ++- .../spatial_ODE_systems.jl | 126 ++++++----- .../spatial_reactions.jl | 27 ++- src/spatial_reaction_systems/utility.jl | 180 ++++++++------- src/steady_state_stability.jl | 23 +- 28 files changed, 891 insertions(+), 768 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index bd486f9711..32df32290d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -31,17 +31,17 @@ include("pages.jl") # pages = pages) makedocs(sitename = "Catalyst.jl", - authors = "Samuel Isaacson", - format = Documenter.HTML(; analytics = "UA-90474609-3", - prettyurls = (get(ENV, "CI", nothing) == "true"), - assets = ["assets/favicon.ico"], - canonical = "https://docs.sciml.ai/Catalyst/stable/"), - modules = [Catalyst, ModelingToolkit], - doctest = false, - clean = true, - pages = pages, - pagesonly = true, - warnonly = true) + authors = "Samuel Isaacson", + format = Documenter.HTML(; analytics = "UA-90474609-3", + prettyurls = (get(ENV, "CI", nothing) == "true"), + assets = ["assets/favicon.ico"], + canonical = "https://docs.sciml.ai/Catalyst/stable/"), + modules = [Catalyst, ModelingToolkit], + doctest = false, + clean = true, + pages = pages, + pagesonly = true, + warnonly = true) deploydocs(repo = "github.com/SciML/Catalyst.jl.git"; - push_preview = true) + push_preview = true) diff --git a/docs/pages.jl b/docs/pages.jl index bb0de4f678..21712f279e 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -2,8 +2,8 @@ pages = Any[ "Home" => "index.md", "Introduction to Catalyst" => Any[ "introduction_to_catalyst/catalyst_for_new_julia_users.md", - # "introduction_to_catalyst/introduction_to_catalyst.md" - # Advanced introduction. + # "introduction_to_catalyst/introduction_to_catalyst.md" + # Advanced introduction. ], "Model Creation and Properties" => Any[ "model_creation/dsl_basics.md", @@ -21,8 +21,7 @@ pages = Any[ "Model creation examples" => Any[ "model_creation/examples/basic_CRN_library.md", "model_creation/examples/programmatic_generative_linear_pathway.md", - "model_creation/examples/hodgkin_huxley_equation.md", - #"model_creation/examples/smoluchowski_coagulation_equation.md" + "model_creation/examples/hodgkin_huxley_equation.md" #"model_creation/examples/smoluchowski_coagulation_equation.md" ] ], "Model simulation" => Any[ @@ -31,15 +30,12 @@ pages = Any[ "model_simulation/simulation_structure_interfacing.md", "model_simulation/ensemble_simulations.md", # Stochastic simulation statistical analysis. - "model_simulation/ode_simulation_performance.md", - # SDE Performance considerations/advice. - # Jump Performance considerations/advice. - # Finite state projection + "model_simulation/ode_simulation_performance.md" # SDE Performance considerations/advice. # Jump Performance considerations/advice. # Finite state projection ], "Steady state analysis" => Any[ "steady_state_functionality/homotopy_continuation.md", "steady_state_functionality/nonlinear_solve.md", - "steady_state_functionality/steady_state_stability_computation.md", + "steady_state_functionality/steady_state_stability_computation.md", "steady_state_functionality/bifurcation_diagrams.md", "steady_state_functionality/dynamical_systems.md" ], @@ -58,9 +54,9 @@ pages = Any[ ] ], "Spatial modelling" => Any[ - # Intro. - # Lattice ODEs. - # Lattice Jumps. + # Intro. + # Lattice ODEs. + # Lattice Jumps. ], # "Developer Documentation" => Any[ # # Contributor's guide. diff --git a/ext/CatalystBifurcationKitExtension.jl b/ext/CatalystBifurcationKitExtension.jl index 636d151244..734d6810d8 100644 --- a/ext/CatalystBifurcationKitExtension.jl +++ b/ext/CatalystBifurcationKitExtension.jl @@ -7,4 +7,4 @@ import BifurcationKit as BK # Creates and exports hc_steady_states function. include("CatalystBifurcationKitExtension/bifurcation_kit_extension.jl") -end \ No newline at end of file +end diff --git a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl index ef62ee3bc1..5be1c9c600 100644 --- a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl +++ b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl @@ -2,23 +2,27 @@ # Creates a BifurcationProblem, using a ReactionSystem as an input. function BK.BifurcationProblem(rs::ReactionSystem, u0_bif, ps, bif_par, args...; - plot_var=nothing, record_from_solution=BK.record_sol_default, jac=true, u0=[], kwargs...) - if !isautonomous(rs) + plot_var = nothing, record_from_solution = BK.record_sol_default, jac = true, u0 = [], kwargs...) + if !isautonomous(rs) error("Attempting to create a `BifurcationProblem` for a non-autonomous system (e.g. where some rate depend on $(rs.iv)). This is not possible.") end # Converts symbols to symbolics. (bif_par isa Symbol) && (bif_par = ModelingToolkit.get_var_to_name(rs)[bif_par]) (plot_var isa Symbol) && (plot_var = ModelingToolkit.get_var_to_name(rs)[plot_var]) - ((u0_bif isa Vector{<:Pair{Symbol,<:Any}}) || (u0_bif isa Dict{Symbol, <:Any})) && (u0_bif = symmap_to_varmap(rs, u0_bif)) - ((ps isa Vector{<:Pair{Symbol,<:Any}}) || (ps isa Dict{Symbol, <:Any})) && (ps = symmap_to_varmap(rs, ps)) - ((u0 isa Vector{<:Pair{Symbol,<:Any}}) || (u0 isa Dict{Symbol, <:Any})) && (u0 = symmap_to_varmap(rs, u0)) + ((u0_bif isa Vector{<:Pair{Symbol, <:Any}}) || (u0_bif isa Dict{Symbol, <:Any})) && + (u0_bif = symmap_to_varmap(rs, u0_bif)) + ((ps isa Vector{<:Pair{Symbol, <:Any}}) || (ps isa Dict{Symbol, <:Any})) && + (ps = symmap_to_varmap(rs, ps)) + ((u0 isa Vector{<:Pair{Symbol, <:Any}}) || (u0 isa Dict{Symbol, <:Any})) && + (u0 = symmap_to_varmap(rs, u0)) # Creates NonlinearSystem. Catalyst.conservationlaw_errorcheck(rs, vcat(ps, u0)) - nsys = complete(convert(NonlinearSystem, rs; remove_conserved=true, defaults=Dict(u0))) + nsys = complete(convert( + NonlinearSystem, rs; remove_conserved = true, defaults = Dict(u0))) # Makes BifurcationProblem (this call goes through the ModelingToolkit-based BifurcationKit extension). - return BK.BifurcationProblem(nsys, u0_bif, ps, bif_par, args...; plot_var=plot_var, - record_from_solution=record_from_solution, jac=jac, kwargs...) -end \ No newline at end of file + return BK.BifurcationProblem(nsys, u0_bif, ps, bif_par, args...; plot_var = plot_var, + record_from_solution = record_from_solution, jac = jac, kwargs...) +end diff --git a/ext/CatalystHomotopyContinuationExtension.jl b/ext/CatalystHomotopyContinuationExtension.jl index 6e7fdac50f..8fe9f27af8 100644 --- a/ext/CatalystHomotopyContinuationExtension.jl +++ b/ext/CatalystHomotopyContinuationExtension.jl @@ -11,4 +11,4 @@ import Symbolics: unwrap, wrap, Rewriters, symtype, issym, istree # Creates and exports hc_steady_states function. include("CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl") -end \ No newline at end of file +end diff --git a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl index 1dcc64d9ba..dc61472809 100644 --- a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl +++ b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl @@ -35,8 +35,9 @@ Notes: - Homotopy-based steady state finding only works when all rates are rational polynomials (e.g. constant, linear, mm, or hill functions). ``` """ -function Catalyst.hc_steady_states(rs::ReactionSystem, ps; filter_negative=true, neg_thres=-1e-20, u0=[], kwargs...) - if !isautonomous(rs) +function Catalyst.hc_steady_states(rs::ReactionSystem, ps; filter_negative = true, + neg_thres = -1e-20, u0 = [], kwargs...) + if !isautonomous(rs) error("Attempting to compute steady state for a non-autonomous system (e.g. where some rate depend on $(rs.iv)). This is not possible.") end ss_poly = steady_state_polynomial(rs, ps, u0) @@ -49,10 +50,11 @@ end function steady_state_polynomial(rs::ReactionSystem, ps, u0) rs = Catalyst.expand_registered_functions(rs) ns = complete(convert(NonlinearSystem, rs; remove_conserved = true)) - pre_varmap = [symmap_to_varmap(rs,u0)..., symmap_to_varmap(rs,ps)...] + pre_varmap = [symmap_to_varmap(rs, u0)..., symmap_to_varmap(rs, ps)...] Catalyst.conservationlaw_errorcheck(rs, pre_varmap) - p_vals = ModelingToolkit.varmap_to_vars(pre_varmap, parameters(ns); defaults = ModelingToolkit.defaults(ns)) - p_dict = Dict(parameters(ns) .=> p_vals) + p_vals = ModelingToolkit.varmap_to_vars( + pre_varmap, parameters(ns); defaults = ModelingToolkit.defaults(ns)) + p_dict = Dict(parameters(ns) .=> p_vals) eqs_pars_funcs = vcat(equations(ns), conservedequations(rs)) eqs = map(eq -> substitute(eq.rhs - eq.lhs, p_dict), eqs_pars_funcs) eqs_intexp = make_int_exps.(eqs) @@ -61,12 +63,14 @@ function steady_state_polynomial(rs::ReactionSystem, ps, u0) end # Parses and expression and return a version where any exponents that are Float64 (but an int, like 2.0) are turned into Int64s. -make_int_exps(expr) = wrap(Rewriters.Postwalk(Rewriters.PassThrough(___make_int_exps))(unwrap(expr))).val +function make_int_exps(expr) + wrap(Rewriters.Postwalk(Rewriters.PassThrough(___make_int_exps))(unwrap(expr))).val +end function ___make_int_exps(expr) !istree(expr) && return expr - if (operation(expr) == ^) + if (operation(expr) == ^) if isinteger(arguments(expr)[2]) - return arguments(expr)[1] ^ Int64(arguments(expr)[2]) + return arguments(expr)[1]^Int64(arguments(expr)[2]) else error("An non integer ($(arguments(expr)[2])) was found as a variable exponent. Non-integer exponents are not supported for homotopy continuation based steady state finding.") end @@ -95,7 +99,7 @@ function reorder_sols!(sols, ss_poly, rs::ReactionSystem) end # Filters away solutions with negative species concentrations (and for neg_thres < val < 0.0, sets val=0.0). -function filter_negative_f(sols; neg_thres=-1e-20) +function filter_negative_f(sols; neg_thres = -1e-20) for sol in sols, idx in 1:length(sol) (neg_thres < sol[idx] < 0) && (sol[idx] = 0) end @@ -104,9 +108,13 @@ end # Sometimes (when polynomials are created from coupled CRN/DAEs), the steady state polynomial have the wrong type. # This converts it to the correct type, which homotopy continuation can handle. -const WRONG_POLY_TYPE = Vector{DynamicPolynomials.Polynomial{DynamicPolynomials.Commutative{DynamicPolynomials.CreationOrder}, DynamicPolynomials.Graded{DynamicPolynomials.LexOrder}}} -const CORRECT_POLY_TYPE = Vector{DynamicPolynomials.Polynomial{DynamicPolynomials.Commutative{DynamicPolynomials.CreationOrder}, DynamicPolynomials.Graded{DynamicPolynomials.LexOrder}, Float64}} +const WRONG_POLY_TYPE = Vector{DynamicPolynomials.Polynomial{ + DynamicPolynomials.Commutative{DynamicPolynomials.CreationOrder}, + DynamicPolynomials.Graded{DynamicPolynomials.LexOrder}}} +const CORRECT_POLY_TYPE = Vector{DynamicPolynomials.Polynomial{ + DynamicPolynomials.Commutative{DynamicPolynomials.CreationOrder}, + DynamicPolynomials.Graded{DynamicPolynomials.LexOrder}, Float64}} function poly_type_convert(ss_poly) (typeof(ss_poly) == WRONG_POLY_TYPE) && return convert(CORRECT_POLY_TYPE, ss_poly) return ss_poly -end \ No newline at end of file +end diff --git a/ext/CatalystStructuralIdentifiabilityExtension.jl b/ext/CatalystStructuralIdentifiabilityExtension.jl index 026fbe6122..0a442affa8 100644 --- a/ext/CatalystStructuralIdentifiabilityExtension.jl +++ b/ext/CatalystStructuralIdentifiabilityExtension.jl @@ -7,4 +7,4 @@ import StructuralIdentifiability as SI # Creates and exports hc_steady_states function. include("CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl") -end \ No newline at end of file +end diff --git a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl index 9ae15dc55e..26d4ad1e89 100644 --- a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl +++ b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl @@ -26,12 +26,13 @@ Notes: - This function is part of the StructuralIdentifiability.jl extension. StructuralIdentifiability.jl must be imported to access it. - `measured_quantities` and `known_p` input may also be symbolic (e.g. measured_quantities = [rs.X]) """ -function Catalyst.make_si_ode(rs::ReactionSystem; measured_quantities = [], known_p = [], - ignore_no_measured_warn = false, remove_conserved = true) +function Catalyst.make_si_ode(rs::ReactionSystem; measured_quantities = [], known_p = [], + ignore_no_measured_warn = false, remove_conserved = true) # Creates a MTK ODESystem, and a list of measured quantities (there are equations). # Gives these to SI to create an SI ode model of its preferred form. osys, conseqs, _ = make_osys(rs; remove_conserved) - measured_quantities = make_measured_quantities(rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) + measured_quantities = make_measured_quantities( + rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) return SI.mtk_to_si(osys, measured_quantities)[1] end @@ -62,16 +63,19 @@ Notes: - This function is part of the StructuralIdentifiability.jl extension. StructuralIdentifiability.jl must be imported to access it. - `measured_quantities` and `known_p` input may also be symbolic (e.g. measured_quantities = [rs.X]) """ -function SI.assess_local_identifiability(rs::ReactionSystem, args...; measured_quantities = [], - known_p = [], funcs_to_check = Vector(), remove_conserved = true, - ignore_no_measured_warn=false, kwargs...) +function SI.assess_local_identifiability( + rs::ReactionSystem, args...; measured_quantities = [], + known_p = [], funcs_to_check = Vector(), remove_conserved = true, + ignore_no_measured_warn = false, kwargs...) # Creates a ODESystem, list of measured quantities, and functions to check, of SI's preferred form. osys, conseqs, vars = make_osys(rs; remove_conserved) - measured_quantities = make_measured_quantities(rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) + measured_quantities = make_measured_quantities( + rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) funcs_to_check = make_ftc(funcs_to_check, conseqs, vars) # Computes identifiability and converts it to a easy to read form. - out = SI.assess_local_identifiability(osys, args...; measured_quantities, funcs_to_check, kwargs...) + out = SI.assess_local_identifiability( + osys, args...; measured_quantities, funcs_to_check, kwargs...) return make_output(out, funcs_to_check, reverse.(conseqs)) end @@ -100,16 +104,19 @@ Notes: - This function is part of the StructuralIdentifiability.jl extension. StructuralIdentifiability.jl must be imported to access it. - `measured_quantities` and `known_p` input may also be symbolic (e.g. measured_quantities = [rs.X]) """ -function SI.assess_identifiability(rs::ReactionSystem, args...; measured_quantities = [], known_p = [], - funcs_to_check = Vector(), remove_conserved = true, - ignore_no_measured_warn=false, kwargs...) +function SI.assess_identifiability( + rs::ReactionSystem, args...; measured_quantities = [], known_p = [], + funcs_to_check = Vector(), remove_conserved = true, + ignore_no_measured_warn = false, kwargs...) # Creates a ODESystem, list of measured quantities, and functions to check, of SI's preferred form. osys, conseqs, vars = make_osys(rs; remove_conserved) - measured_quantities = make_measured_quantities(rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) + measured_quantities = make_measured_quantities( + rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) funcs_to_check = make_ftc(funcs_to_check, conseqs, vars) # Computes identifiability and converts it to a easy to read form. - out = SI.assess_identifiability(osys, args...; measured_quantities, funcs_to_check, kwargs...) + out = SI.assess_identifiability( + osys, args...; measured_quantities, funcs_to_check, kwargs...) return make_output(out, funcs_to_check, reverse.(conseqs)) end @@ -138,12 +145,14 @@ Notes: - This function is part of the StructuralIdentifiability.jl extension. StructuralIdentifiability.jl must be imported to access it. - `measured_quantities` and `known_p` input may also be symbolic (e.g. measured_quantities = [rs.X]) """ -function SI.find_identifiable_functions(rs::ReactionSystem, args...; measured_quantities = [], - known_p = [], remove_conserved = true, ignore_no_measured_warn=false, - kwargs...) +function SI.find_identifiable_functions( + rs::ReactionSystem, args...; measured_quantities = [], + known_p = [], remove_conserved = true, ignore_no_measured_warn = false, + kwargs...) # Creates a ODESystem, and list of measured quantities, of SI's preferred form. osys, conseqs = make_osys(rs; remove_conserved) - measured_quantities = make_measured_quantities(rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) + measured_quantities = make_measured_quantities( + rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) # Computes identifiable functions and converts it to a easy to read form. out = SI.find_identifiable_functions(osys, args...; measured_quantities, kwargs...) @@ -154,10 +163,11 @@ end # From a reaction system, creates the corresponding MTK-style ODESystem for SI application # Also compute the, later needed, conservation law equations and list of system symbols (unknowns and parameters). -function make_osys(rs::ReactionSystem; remove_conserved=true) +function make_osys(rs::ReactionSystem; remove_conserved = true) # Creates the ODESystem corresponding to the ReactionSystem (expanding functions and flattening it). # Creates a list of the systems all symbols (unknowns and parameters). - ModelingToolkit.iscomplete(rs) || error("Identifiability should only be computed for complete systems. A ReactionSystem can be marked as complete using the `complete` function.") + ModelingToolkit.iscomplete(rs) || + error("Identifiability should only be computed for complete systems. A ReactionSystem can be marked as complete using the `complete` function.") rs = complete(Catalyst.expand_registered_functions(flatten(rs))) osys = complete(convert(ODESystem, rs; remove_conserved)) vars = [unknowns(rs); parameters(rs)] @@ -177,10 +187,11 @@ end # Creates a list of measured quantities of a form that SI can read. # Each measured quantity must have a form like: # `obs_var ~ X` # (Here, `obs_var` is a variable, and X is whatever we can measure). -function make_measured_quantities(rs::ReactionSystem, measured_quantities::Vector{T}, known_p::Vector{S}, - conseqs; ignore_no_measured_warn=false) where {T,S} +function make_measured_quantities( + rs::ReactionSystem, measured_quantities::Vector{T}, known_p::Vector{S}, + conseqs; ignore_no_measured_warn = false) where {T, S} # Warning if the user didn't give any measured quantities. - if ignore_no_measured_warn || isempty(measured_quantities) + if ignore_no_measured_warn || isempty(measured_quantities) @warn "No measured quantity provided to the `measured_quantities` argument, any further identifiability analysis will likely fail. You can disable this warning by setting `ignore_no_measured_warn=true`." end @@ -192,7 +203,8 @@ function make_measured_quantities(rs::ReactionSystem, measured_quantities::Vecto # Creates one internal observation variable for each measured quantity (`___internal_observables`). # Creates a vector of equations, setting each measured quantity equal to one observation variable. @variables t (___internal_observables(Catalyst.get_iv(rs)))[1:length(mqs)] - return Equation[(q isa Equation) ? q : (___internal_observables[i] ~ q) for (i,q) in enumerate(mqs)] + return Equation[(q isa Equation) ? q : (___internal_observables[i] ~ q) + for (i, q) in enumerate(mqs)] end # Creates the functions that we wish to check for identifiability. @@ -211,9 +223,9 @@ end function make_output(out, funcs_to_check, conseqs) funcs_to_check = vector_subs(funcs_to_check, conseqs) out = Dict(zip(vector_subs(keys(out), conseqs), values(out))) - sortdict = Dict(ftc => i for (i,ftc) in enumerate(funcs_to_check)) - return sort(out; by = x -> sortdict[x]) + sortdict = Dict(ftc => i for (i, ftc) in enumerate(funcs_to_check)) + return sort(out; by = x -> sortdict[x]) end # For a vector of expressions and a conservation law, substitutes the law into every equation. -vector_subs(eqs, subs) = [substitute(eq, subs) for eq in eqs] \ No newline at end of file +vector_subs(eqs, subs) = [substitute(eq, subs) for eq in eqs] diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 750029f189..68fde43b65 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -7,7 +7,7 @@ using DocStringExtensions using SparseArrays, DiffEqBase, Reexport, Setfield using LaTeXStrings, Latexify, Requires using LinearAlgebra, Combinatorics -using JumpProcesses: JumpProcesses, JumpProblem, +using JumpProcesses: JumpProcesses, JumpProblem, MassActionJump, ConstantRateJump, VariableRateJump, SpatialMassActionJump @@ -16,7 +16,6 @@ using ModelingToolkit const MT = ModelingToolkit using DynamicQuantities#, Unitful # Having Unitful here as well currently gives an error. - @reexport using ModelingToolkit using Symbolics using LinearAlgebra @@ -81,7 +80,7 @@ const CONSERVED_CONSTANT_SYMBOL = :Γ # Declares symbols which may neither be used as parameters nor unknowns. const forbidden_symbols_skip = Set([:ℯ, :pi, :π, :t, :∅]) const forbidden_symbols_error = union(Set([:im, :nothing, CONSERVED_CONSTANT_SYMBOL]), - forbidden_symbols_skip) + forbidden_symbols_skip) const forbidden_variables_error = let fvars = copy(forbidden_symbols_error) delete!(fvars, :t) @@ -183,7 +182,6 @@ include("spatial_reaction_systems/utility.jl") include("spatial_reaction_systems/spatial_ODE_systems.jl") include("spatial_reaction_systems/lattice_jump_systems.jl") - ### ReactionSystem Serialisation ### # Has to be at the end (because it uses records of all metadata declared by Catalyst). include("reactionsystem_serialisation/serialisation_support.jl") diff --git a/src/chemistry_functionality.jl b/src/chemistry_functionality.jl index 941038046b..768ee66f67 100644 --- a/src/chemistry_functionality.jl +++ b/src/chemistry_functionality.jl @@ -49,7 +49,6 @@ function component_coefficients(s) return [c => co for (c, co) in zip(components(s), coefficients(s))] end - ### Create @compound Macro(s) ### """ @@ -80,12 +79,14 @@ const COMPOUND_CREATION_ERROR_DEPENDENT_VAR_REQUIRED = "When the components (col function make_compound(expr) # Error checks. (expr isa Expr) || error(COMPOUND_CREATION_ERROR_BASE) - ((expr.head == :call) && (expr.args[1] == :~) && (length(expr.args) == 3)) || error(COMPOUND_CREATION_ERROR_BAD_SEPARATOR) + ((expr.head == :call) && (expr.args[1] == :~) && (length(expr.args) == 3)) || + error(COMPOUND_CREATION_ERROR_BAD_SEPARATOR) # Loops through all components, add the component and the coefficients to the corresponding vectors # Cannot extract directly using e.g. "getfield.(composition, :reactant)" because then # we get something like :([:C, :O]), rather than :([C, O]). - composition = Catalyst.recursive_find_reactants!(expr.args[3], 1, Vector{ReactantStruct}(undef, 0)) + composition = Catalyst.recursive_find_reactants!( + expr.args[3], 1, Vector{ReactantStruct}(undef, 0)) components = :([]) # Becomes something like :([C, O]). coefficients = :([]) # Becomes something like :([1, 2]). for comp in composition @@ -101,11 +102,12 @@ function make_compound(expr) species_name, ivs, _, _ = find_varinfo_in_declaration(expr.args[2]) # If no ivs were given, inserts `(..)` (e.g. turning `CO` to `CO(..)`). - isempty(ivs) && (species_expr = insert_independent_variable(species_expr, :(..))) + isempty(ivs) && (species_expr = insert_independent_variable(species_expr, :(..))) # Expression which when evaluated gives a vector with all the ivs of the components. - ivs_get_expr = :(unique(reduce(vcat,[arguments(ModelingToolkit.unwrap(comp)) for comp in $components]))) - + ivs_get_expr = :(unique(reduce( + vcat, [arguments(ModelingToolkit.unwrap(comp)) for comp in $components]))) + # Creates the found expressions that will create the compound species. # The `Expr(:escape, :(...))` is required so that the expressions are evaluated in # the scope the users use the macro in (to e.g. detect already exiting species). @@ -117,13 +119,24 @@ function make_compound(expr) # `CO2 = ModelingToolkit.setmetadata(CO2, Catalyst.CompoundSpecies, true)` # `CO2 = ModelingToolkit.setmetadata(CO2, Catalyst.CompoundSpecies, [C, O])` # `CO2 = ModelingToolkit.setmetadata(CO2, Catalyst.CompoundSpecies, [1, 2])` - species_declaration_expr = Expr(:escape, :(@species $species_expr)) - multiple_ivs_error_check_expr = Expr(:escape, :($(isempty(ivs)) && (length($ivs_get_expr) > 1) && error($COMPOUND_CREATION_ERROR_DEPENDENT_VAR_REQUIRED))) - iv_designation_expr = Expr(:escape, :($(isempty(ivs)) && ($species_name = $(species_name)($(ivs_get_expr)...)))) - iv_check_expr = Expr(:escape, :(issetequal(arguments(ModelingToolkit.unwrap($species_name)), $ivs_get_expr) || error("The independent variable(S) provided to the compound ($(arguments(ModelingToolkit.unwrap($species_name)))), and those of its components ($($ivs_get_expr)))), are not identical."))) - compound_designation_expr = Expr(:escape, :($species_name = ModelingToolkit.setmetadata($species_name, Catalyst.CompoundSpecies, true))) - components_designation_expr = Expr(:escape, :($species_name = ModelingToolkit.setmetadata($species_name, Catalyst.CompoundComponents, $components))) - coefficients_designation_expr = Expr(:escape, :($species_name = ModelingToolkit.setmetadata($species_name, Catalyst.CompoundCoefficients, $coefficients))) + species_declaration_expr = Expr(:escape, :(@species $species_expr)) + multiple_ivs_error_check_expr = Expr(:escape, + :($(isempty(ivs)) && (length($ivs_get_expr) > 1) && + error($COMPOUND_CREATION_ERROR_DEPENDENT_VAR_REQUIRED))) + iv_designation_expr = Expr(:escape, + :($(isempty(ivs)) && ($species_name = $(species_name)($(ivs_get_expr)...)))) + iv_check_expr = Expr(:escape, + :(issetequal(arguments(ModelingToolkit.unwrap($species_name)), $ivs_get_expr) || + error("The independent variable(S) provided to the compound ($(arguments(ModelingToolkit.unwrap($species_name)))), and those of its components ($($ivs_get_expr)))), are not identical."))) + compound_designation_expr = Expr(:escape, + :($species_name = ModelingToolkit.setmetadata( + $species_name, Catalyst.CompoundSpecies, true))) + components_designation_expr = Expr(:escape, + :($species_name = ModelingToolkit.setmetadata( + $species_name, Catalyst.CompoundComponents, $components))) + coefficients_designation_expr = Expr(:escape, + :($species_name = ModelingToolkit.setmetadata( + $species_name, Catalyst.CompoundCoefficients, $coefficients))) # Returns the rephrased expression. return quote @@ -168,7 +181,7 @@ function make_compounds(expr) # For each compound in `expr`, creates the set of 7 compound creation lines (using `make_compound`). # Next, loops through all 7*[Number of compounds] lines and add them to compound_declarations. - compound_calls = [Catalyst.make_compound(line) for line in expr.args] + compound_calls = [Catalyst.make_compound(line) for line in expr.args] for compound_call in compound_calls, line in MacroTools.striplines(compound_call).args push!(compound_declarations.args, line) end @@ -183,13 +196,13 @@ function make_compounds(expr) push!(compound_declarations.args, :($(Expr(:escape, :($(compound_syms)))))) # The output needs to be converted to Vector{Num} (from Vector{SymbolicUtils.BasicSymbolic{Real}}) to be consistent with e.g. @variables. - compound_declarations.args[end] = :([ModelingToolkit.wrap(cmp) for cmp in $(compound_declarations.args[end])]) + compound_declarations.args[end] = :([ModelingToolkit.wrap(cmp) + for cmp in $(compound_declarations.args[end])]) # Returns output that. return compound_declarations end - ### Reaction Balancing Functionality ### """ @@ -245,21 +258,22 @@ function balance_reaction(reaction::Reaction) balancedrxs = Vector{Reaction}(undef, length(stoichiometries)) # Iterate over each stoichiometry vector and create a reaction - for (i,stoich) in enumerate(stoichiometries) + for (i, stoich) in enumerate(stoichiometries) # Divide the stoichiometry vector into substrate and product stoichiometries. substoich = stoich[1:length(reaction.substrates)] prodstoich = stoich[(length(reaction.substrates) + 1):end] # Create a new reaction with the balanced stoichiometries balancedrx = Reaction(reaction.rate, reaction.substrates, - reaction.products, substoich, prodstoich) + reaction.products, substoich, prodstoich) # Add the reaction to the vector of all reactions balancedrxs[i] = balancedrx end isempty(balancedrxs) && (@warn "Unable to balance reaction.") - (length(balancedrxs) > 1) && (@warn "The space of possible balanced versions of the reaction ($reaction) is greater than one-dimension. This prevents the selection of a single appropriate balanced reaction. Instead, a basis for balanced reactions is returned. Note that we do not check if they preserve the set of substrates and products from the original reaction.") + (length(balancedrxs) > 1) && + (@warn "The space of possible balanced versions of the reaction ($reaction) is greater than one-dimension. This prevents the selection of a single appropriate balanced reaction. Instead, a basis for balanced reactions is returned. Note that we do not check if they preserve the set of substrates and products from the original reaction.") return balancedrxs end @@ -321,7 +335,7 @@ function create_matrix(reaction::Catalyst.Reaction) coeffs = [1] end - for (atom,coeff) in zip(atoms, coeffs) + for (atom, coeff) in zip(atoms, coeffs) # Extract atom and coefficient from the pair i = findfirst(x -> isequal(x, atom), unique_atoms) if i === nothing @@ -387,4 +401,4 @@ function get_balanced_reaction(rx::Reaction) return only(brxs) end # For non-`Reaction` equations, returns the original equation. -get_balanced_reaction(eq::Equation) = eq \ No newline at end of file +get_balanced_reaction(eq::Equation) = eq diff --git a/src/dsl.jl b/src/dsl.jl index cbc72cdc61..370ab2902d 100644 --- a/src/dsl.jl +++ b/src/dsl.jl @@ -64,16 +64,14 @@ Example systems: const empty_set = Set{Symbol}([:∅]) const fwd_arrows = Set{Symbol}([:>, :(=>), :→, :↣, :↦, :⇾, :⟶, :⟼, :⥟, :⥟, :⇀, :⇁, :⇒, :⟾]) const bwd_arrows = Set{Symbol}([:<, :(<=), :←, :↢, :↤, :⇽, :⟵, :⟻, :⥚, :⥞, :↼, :↽, :⇐, :⟽, - Symbol("<--")]) + Symbol("<--")]) const double_arrows = Set{Symbol}([:↔, :⟷, :⇄, :⇆, :⇌, :⇋, :⇔, :⟺, Symbol("<-->")]) const pure_rate_arrows = Set{Symbol}([:(=>), :(<=), :⇐, :⟽, :⇒, :⟾, :⇔, :⟺]) - # Declares the keys used for various options. const option_keys = (:species, :parameters, :variables, :ivs, :compounds, :observables, - :default_noise_scaling, :differentials, :equations, - :continuous_events, :discrete_events, :combinatoric_ratelaws) - + :default_noise_scaling, :differentials, :equations, + :continuous_events, :discrete_events, :combinatoric_ratelaws) ### `@species` Macro ### @@ -88,9 +86,10 @@ macro species(ex...) idx = length(vars.args) resize!(vars.args, idx + length(lastarg.args) + 1) for sym in lastarg.args - vars.args[idx] = :($sym = ModelingToolkit.wrap(setmetadata(ModelingToolkit.value($sym), - Catalyst.VariableSpecies, - true))) + vars.args[idx] = :($sym = ModelingToolkit.wrap(setmetadata( + ModelingToolkit.value($sym), + Catalyst.VariableSpecies, + true))) idx += 1 end @@ -108,7 +107,6 @@ macro species(ex...) esc(vars) end - ### `@reaction_network` and `@network_component` Macros ### """ @@ -144,12 +142,14 @@ emptyrn = @reaction_network ReactionSystems generated through `@reaction_network` are complete. """ macro reaction_network(name::Symbol, ex::Expr) - :(complete($(make_reaction_system(MacroTools.striplines(ex); name = :($(QuoteNode(name))))))) + :(complete($(make_reaction_system( + MacroTools.striplines(ex); name = :($(QuoteNode(name))))))) end # Allows @reaction_network $name begin ... to interpolate variables storing a name. macro reaction_network(name::Expr, ex::Expr) - :(complete($(make_reaction_system(MacroTools.striplines(ex); name = :($(esc(name.args[1]))))))) + :(complete($(make_reaction_system( + MacroTools.striplines(ex); name = :($(esc(name.args[1]))))))) end macro reaction_network(ex::Expr) @@ -161,14 +161,14 @@ macro reaction_network(ex::Expr) else # empty but has interpolated name: @reaction_network $name networkname = :($(esc(ex.args[1]))) return Expr(:block, :(@parameters t), - :(complete(ReactionSystem(Reaction[], t, [], []; name = $networkname)))) + :(complete(ReactionSystem(Reaction[], t, [], []; name = $networkname)))) end end # Returns a empty network (with, or without, a declared name). macro reaction_network(name::Symbol = gensym(:ReactionSystem)) return Expr(:block, :(@parameters t), - :(complete(ReactionSystem(Reaction[], t, [], []; name = $(QuoteNode(name)))))) + :(complete(ReactionSystem(Reaction[], t, [], []; name = $(QuoteNode(name)))))) end # Ideally, it would have been possible to combine the @reaction_network and @network_component macros. @@ -197,17 +197,16 @@ macro network_component(ex::Expr) else # empty but has interpolated name: @network_component $name networkname = :($(esc(ex.args[1]))) return Expr(:block, :(@parameters t), - :(ReactionSystem(Reaction[], t, [], []; name = $networkname))) + :(ReactionSystem(Reaction[], t, [], []; name = $networkname))) end end # Returns a empty network (with, or without, a declared name). macro network_component(name::Symbol = gensym(:ReactionSystem)) return Expr(:block, :(@parameters t), - :(ReactionSystem(Reaction[], t, [], []; name = $(QuoteNode(name))))) + :(ReactionSystem(Reaction[], t, [], []; name = $(QuoteNode(name))))) end - ### Internal DSL Structures ### # Structure containing information about one reactant in one reaction. @@ -224,7 +223,7 @@ struct ReactionStruct metadata::Expr function ReactionStruct(sub_line::ExprValues, prod_line::ExprValues, rate::ExprValues, - metadata_line::ExprValues) + metadata_line::ExprValues) sub = recursive_find_reactants!(sub_line, 1, Vector{ReactantStruct}(undef, 0)) prod = recursive_find_reactants!(prod_line, 1, Vector{ReactantStruct}(undef, 0)) metadata = extract_metadata(metadata_line) @@ -235,21 +234,21 @@ end # Recursive function that loops through the reaction line and finds the reactants and their # stoichiometry. Recursion makes it able to handle weird cases like 2(X+Y+3(Z+XY)). function recursive_find_reactants!(ex::ExprValues, mult::ExprValues, - reactants::Vector{ReactantStruct}) + reactants::Vector{ReactantStruct}) if typeof(ex) != Expr || (ex.head == :escape) || (ex.head == :ref) (ex == 0 || in(ex, empty_set)) && (return reactants) if any(ex == reactant.reactant for reactant in reactants) idx = findall(x -> x == ex, getfield.(reactants, :reactant))[1] reactants[idx] = ReactantStruct(ex, - processmult(+, mult, - reactants[idx].stoichiometry)) + processmult(+, mult, + reactants[idx].stoichiometry)) else push!(reactants, ReactantStruct(ex, mult)) end elseif ex.args[1] == :* if length(ex.args) == 3 recursive_find_reactants!(ex.args[3], processmult(*, mult, ex.args[2]), - reactants) + reactants) else newmult = processmult(*, mult, Expr(:call, ex.args[1:(end - 1)]...)) recursive_find_reactants!(ex.args[end], newmult, reactants) @@ -276,14 +275,15 @@ end function extract_metadata(metadata_line::Expr) metadata = :([]) for arg in metadata_line.args - (arg.head != :(=)) && error("Malformatted metadata line: $metadata_line. Each entry in the vector should contain a `=`.") - (arg.args[1] isa Symbol) || error("Malformatted metadata entry: $arg. Entries left-hand-side should be a single symbol.") + (arg.head != :(=)) && + error("Malformatted metadata line: $metadata_line. Each entry in the vector should contain a `=`.") + (arg.args[1] isa Symbol) || + error("Malformatted metadata entry: $arg. Entries left-hand-side should be a single symbol.") push!(metadata.args, :($(QuoteNode(arg.args[1])) => $(arg.args[2]))) end return metadata end - ### DSL Internal Master Function ### # Function for creating a ReactionSystem structure (used by the @reaction_network macro). @@ -298,10 +298,10 @@ function make_reaction_system(ex::Expr; name = :(gensym(:ReactionSystem))) # Get macro options. if length(unique(arg.args[1] for arg in option_lines)) < length(option_lines) - error("Some options where given multiple times.") + error("Some options where given multiple times.") end options = Dict(map(arg -> Symbol(String(arg.args[1])[2:end]) => arg, - option_lines)) + option_lines)) # Reads options. default_reaction_metadata = :([]) @@ -317,7 +317,8 @@ function make_reaction_system(ex::Expr; name = :(gensym(:ReactionSystem))) variables_declared = extract_syms(options, :variables) # Reads more options. - vars_extracted, add_default_diff, equations = read_equations_options(options, variables_declared) + vars_extracted, add_default_diff, equations = read_equations_options( + options, variables_declared) variables = vcat(variables_declared, vars_extracted) # handle independent variables @@ -340,17 +341,19 @@ function make_reaction_system(ex::Expr; name = :(gensym(:ReactionSystem))) end # Reads more options. - observed_vars, observed_eqs, obs_syms = read_observed_options(options, [species_declared; variables], all_ivs) + observed_vars, observed_eqs, obs_syms = read_observed_options( + options, [species_declared; variables], all_ivs) declared_syms = Set(Iterators.flatten((parameters_declared, species_declared, - variables))) + variables))) species_extracted, parameters_extracted = extract_species_and_parameters!(reactions, - declared_syms) + declared_syms) species = vcat(species_declared, species_extracted) parameters = vcat(parameters_declared, parameters_extracted) # Create differential expression. - diffexpr = create_differential_expr(options, add_default_diff, [species; parameters; variables], tiv) + diffexpr = create_differential_expr( + options, add_default_diff, [species; parameters; variables], tiv) # Checks for input errors. (sum(length.([reaction_lines, option_lines])) != length(ex.args)) && @@ -398,7 +401,6 @@ function make_reaction_system(ex::Expr; name = :(gensym(:ReactionSystem))) end end - ### DSL Reaction Reading Functions ### # Generates a vector containing a number of reaction structures, each containing the information about one reaction. @@ -412,12 +414,16 @@ function get_reactions(exprs::Vector{Expr}, reactions = Vector{ReactionStruct}(u if typeof(rate) != Expr || rate.head != :tuple error("Error: Must provide a tuple of reaction rates when declaring a bi-directional reaction.") end - push_reactions!(reactions, reaction.args[2], reaction.args[3], rate.args[1], metadata.args[1], arrow) - push_reactions!(reactions, reaction.args[3], reaction.args[2], rate.args[2], metadata.args[2], arrow) + push_reactions!(reactions, reaction.args[2], reaction.args[3], + rate.args[1], metadata.args[1], arrow) + push_reactions!(reactions, reaction.args[3], reaction.args[2], + rate.args[2], metadata.args[2], arrow) elseif in(arrow, fwd_arrows) - push_reactions!(reactions, reaction.args[2], reaction.args[3], rate, metadata, arrow) + push_reactions!( + reactions, reaction.args[2], reaction.args[3], rate, metadata, arrow) elseif in(arrow, bwd_arrows) - push_reactions!(reactions, reaction.args[3], reaction.args[2], rate, metadata, arrow) + push_reactions!( + reactions, reaction.args[3], reaction.args[2], rate, metadata, arrow) else throw("Malformed reaction, invalid arrow type used in: $(MacroTools.striplines(line))") end @@ -431,7 +437,8 @@ function read_reaction_line(line::Expr) # Special routine required for the`-->` case, which creates different expression from all other cases. rate = line.args[1] reaction = line.args[2] - (reaction.head == :-->) && (reaction = Expr(:call, :→, reaction.args[1], reaction.args[2])) + (reaction.head == :-->) && + (reaction = Expr(:call, :→, reaction.args[1], reaction.args[2])) arrow = reaction.args[1] # Handles metadata. If not provided, empty metadata is created. @@ -448,8 +455,9 @@ end # Takes a reaction line and creates reaction(s) from it and pushes those to the reaction array. # Used to create multiple reactions from, for instance, `k, (X,Y) --> 0`. -function push_reactions!(reactions::Vector{ReactionStruct}, sub_line::ExprValues, prod_line::ExprValues, - rate::ExprValues, metadata::ExprValues, arrow::Symbol) +function push_reactions!( + reactions::Vector{ReactionStruct}, sub_line::ExprValues, prod_line::ExprValues, + rate::ExprValues, metadata::ExprValues, arrow::Symbol) # The rates, substrates, products, and metadata may be in a tupple form (e.g. `k, (X,Y) --> 0`). # This finds the length of these tuples (or 1 if not in tuple forms). Errors if lengs inconsistent. lengs = (tup_leng(sub_line), tup_leng(prod_line), tup_leng(rate), tup_leng(metadata)) @@ -470,12 +478,12 @@ function push_reactions!(reactions::Vector{ReactionStruct}, sub_line::ExprValues error("Some reaction metadata fields where repeated: $(metadata_entries)") end - push!(reactions, ReactionStruct(get_tup_arg(sub_line, i), get_tup_arg(prod_line, i), - get_tup_arg(rate, i), metadata_i)) + push!(reactions, + ReactionStruct(get_tup_arg(sub_line, i), get_tup_arg(prod_line, i), + get_tup_arg(rate, i), metadata_i)) end end - ### DSL Species and Parameters Extraction ### # When the user have used the @species (or @parameters) options, extract species (or @@ -529,12 +537,11 @@ function add_syms_from_expr!(push_symbols::AbstractSet, rateex::ExprValues, excl nothing end - ### DSL Output Expression Builders ### # Given the species that were extracted from the reactions, and the options dictionary, creates the @species ... expression for the macro output. function get_sexpr(species_extracted, options, key = :species; - iv_symbols = (DEFAULT_IV_SYM,)) + iv_symbols = (DEFAULT_IV_SYM,)) if haskey(options, key) sexprs = options[key] elseif isempty(species_extracted) @@ -543,7 +550,7 @@ function get_sexpr(species_extracted, options, key = :species; sexprs = Expr(:macrocall, Symbol("@", key), LineNumberNode(0)) end foreach(s -> (s isa Symbol) && push!(sexprs.args, Expr(:call, s, iv_symbols...)), - species_extracted) + species_extracted) sexprs end @@ -562,8 +569,8 @@ function get_rxexprs(rxstruct) prod_init = isempty(rxstruct.products) ? nothing : :([]) prod_stoich_init = deepcopy(prod_init) reaction_func = :(Reaction($(recursive_expand_functions!(rxstruct.rate)), $subs_init, - $prod_init, $subs_stoich_init, $prod_stoich_init, - metadata = $(rxstruct.metadata),)) + $prod_init, $subs_stoich_init, $prod_stoich_init, + metadata = $(rxstruct.metadata))) for sub in rxstruct.substrates push!(reaction_func.args[3].args, sub.reactant) push!(reaction_func.args[5].args, sub.stoichiometry) @@ -591,7 +598,6 @@ function scalarize_macro(nonempty, ex, name) ex, namesym end - ### DSL Option Handling ### # Checks if the `@default_noise_scaling` option is used. If so, read its input and adds it as a @@ -601,7 +607,8 @@ function check_default_noise_scaling!(default_reaction_metadata, options) if (length(options[:default_noise_scaling].args) != 3) # Becasue of how expressions are, 3 is used. error("@default_noise_scaling should only have a single input, this appears not to be the case: \"$(options[:default_noise_scaling])\"") end - push!(default_reaction_metadata.args, :(:noise_scaling => $(options[:default_noise_scaling].args[3]))) + push!(default_reaction_metadata.args, + :(:noise_scaling => $(options[:default_noise_scaling].args[3]))) end end @@ -611,7 +618,8 @@ function read_compound_options(opts) if haskey(opts, :compounds) compound_expr = opts[:compounds] # Find compound species names, and append the independent variable. - compound_species = [find_varinfo_in_declaration(arg.args[2])[1] for arg in compound_expr.args[3].args] + compound_species = [find_varinfo_in_declaration(arg.args[2])[1] + for arg in compound_expr.args[3].args] else # If option is not used, return empty vectors and expressions. compound_expr = :() compound_species = Union{Symbol, Expr}[] @@ -622,8 +630,10 @@ end # Read the events (continious or discrete) provided as options to the DSL. Returns an expression which evalutes to these. function read_events_option(options, event_type::Symbol) # Prepares the events, if required to, converts them to block form. - (event_type in [:continuous_events, :discrete_events]) || error("Trying to read an unsupported event type.") - events_input = haskey(options, event_type) ? options[event_type].args[3] : MacroTools.striplines(:(begin end)) + (event_type in [:continuous_events, :discrete_events]) || + error("Trying to read an unsupported event type.") + events_input = haskey(options, event_type) ? options[event_type].args[3] : + MacroTools.striplines(:(begin end)) events_input = option_block_form(events_input) # Goes throgh the events, checks for errors, and adds them to the output vector. @@ -631,14 +641,16 @@ function read_events_option(options, event_type::Symbol) for arg in events_input.args # Formatting error checks. # NOTE: Maybe we should move these deeper into the system (rather than the DSL), throwing errors more generally? - if (arg isa Expr) && (arg.head != :call) || (arg.args[1] != :(=>)) || length(arg.args) != 3 + if (arg isa Expr) && (arg.head != :call) || (arg.args[1] != :(=>)) || + length(arg.args) != 3 error("Events should be on form `condition => affect`, separated by a `=>`. This appears not to be the case for: $(arg).") end - if (arg isa Expr) && (arg.args[2] isa Expr) && (arg.args[2].head != :vect) && (event_type == :continuous_events) + if (arg isa Expr) && (arg.args[2] isa Expr) && (arg.args[2].head != :vect) && + (event_type == :continuous_events) error("The condition part of continious events (the left-hand side) must be a vector. This is not the case for: $(arg).") end if (arg isa Expr) && (arg.args[3] isa Expr) && (arg.args[3].head != :vect) - error("The affect part of all events (the righ-hand side) must be a vector. This is not the case for: $(arg).") + error("The affect part of all events (the righ-hand side) must be a vector. This is not the case for: $(arg).") end # Adds the correctly formatted event to the event creation expression. @@ -658,7 +670,8 @@ function read_equations_options(options, variables_declared) eqs_input = haskey(options, :equations) ? options[:equations].args[3] : :(begin end) eqs_input = option_block_form(eqs_input) equations = Expr[] - ModelingToolkit.parse_equations!(Expr(:block), equations, Dict{Symbol, Any}(), eqs_input) + ModelingToolkit.parse_equations!( + Expr(:block), equations, Dict{Symbol, Any}(), eqs_input) # Loops through all equations, checks for lhs of the form `D(X) ~ ...`. # When this is the case, the variable X and differential D are extracted (for automatic declaration). @@ -667,7 +680,7 @@ function read_equations_options(options, variables_declared) add_default_diff = false for eq in equations if (eq.head != :call) || (eq.args[1] != :~) - error("Malformed equation: \"$eq\". Equation's left hand and right hand sides should be separated by a \"~\".") + error("Malformed equation: \"$eq\". Equation's left hand and right hand sides should be separated by a \"~\".") end # Checks if the equation have the format D(X) ~ ... (where X is a symbol). This means that the @@ -675,7 +688,8 @@ function read_equations_options(options, variables_declared) # we make a note that a differential D = Differential(iv) should be made as well. lhs = eq.args[2] # if lhs: is an expression. Is a function call. The function's name is D. Calls a single symbol. - if (lhs isa Expr) && (lhs.head == :call) && (lhs.args[1] == :D) && (lhs.args[2] isa Symbol) + if (lhs isa Expr) && (lhs.head == :call) && (lhs.args[1] == :D) && + (lhs.args[2] isa Symbol) diff_var = lhs.args[2] if in(diff_var, forbidden_symbols_error) error("A forbidden symbol ($(diff_var)) was used as an variable in this differential equation: $eq") @@ -694,15 +708,20 @@ function create_differential_expr(options, add_default_diff, used_syms, tiv) # Creates the differential expression. # If differentials was provided as options, this is used as the initial expression. # If the default differential (D(...)) was used in equations, this is added to the expression. - diffexpr = (haskey(options, :differentials) ? options[:differentials].args[3] : MacroTools.striplines(:(begin end))) + diffexpr = (haskey(options, :differentials) ? options[:differentials].args[3] : + MacroTools.striplines(:(begin end))) diffexpr = option_block_form(diffexpr) # Goes through all differentials, checking that they are correctly formatted and their symbol is not used elsewhere. for dexpr in diffexpr.args - (dexpr.head != :(=)) && error("Differential declaration must have form like D = Differential(t), instead \"$(dexpr)\" was given.") - (dexpr.args[1] isa Symbol) || error("Differential left-hand side must be a single symbol, instead \"$(dexpr.args[1])\" was given.") - in(dexpr.args[1], used_syms) && error("Differential name ($(dexpr.args[1])) is also a species, variable, or parameter. This is ambigious and not allowed.") - in(dexpr.args[1], forbidden_symbols_error) && error("A forbidden symbol ($(dexpr.args[1])) was used as a differential name.") + (dexpr.head != :(=)) && + error("Differential declaration must have form like D = Differential(t), instead \"$(dexpr)\" was given.") + (dexpr.args[1] isa Symbol) || + error("Differential left-hand side must be a single symbol, instead \"$(dexpr.args[1])\" was given.") + in(dexpr.args[1], used_syms) && + error("Differential name ($(dexpr.args[1])) is also a species, variable, or parameter. This is ambigious and not allowed.") + in(dexpr.args[1], forbidden_symbols_error) && + error("A forbidden symbol ($(dexpr.args[1])) was used as a differential name.") end # If the default differential D has been used, but not pre-declared using the @differenitals @@ -726,15 +745,19 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted) for (idx, obs_eq) in enumerate(observed_eqs.args) # Extract the observable, checks errors, and continues the loop if the observable has been declared. obs_name, ivs, defaults, metadata = find_varinfo_in_declaration(obs_eq.args[2]) - isempty(ivs) || error("An observable ($obs_name) was given independent variable(s). These should not be given, as they are inferred automatically.") - isnothing(defaults) || error("An observable ($obs_name) was given a default value. This is forbidden.") - in(obs_name, forbidden_symbols_error) && error("A forbidden symbol ($(obs_eq.args[2])) was used as an observable name.") + isempty(ivs) || + error("An observable ($obs_name) was given independent variable(s). These should not be given, as they are inferred automatically.") + isnothing(defaults) || + error("An observable ($obs_name) was given a default value. This is forbidden.") + in(obs_name, forbidden_symbols_error) && + error("A forbidden symbol ($(obs_eq.args[2])) was used as an observable name.") # Error checks. if (obs_name in species_n_vars_declared) && is_escaped_expr(obs_eq.args[2]) error("An interpoalted observable have been used, which has also been explicitly delcared within the system using eitehr @species or @variables. This is not permited.") end - if ((obs_name in species_n_vars_declared) || is_escaped_expr(obs_eq.args[2])) && !isnothing(metadata) + if ((obs_name in species_n_vars_declared) || is_escaped_expr(obs_eq.args[2])) && + !isnothing(metadata) error("Metadata was provided to observable $obs_name in the `@observables` macro. However, the obervable was also declared separately (using either @species or @variables). When this is done, metadata should instead be provided within the original @species or @variable declaration.") end @@ -744,16 +767,20 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted) if !((obs_name in species_n_vars_declared) || is_escaped_expr(obs_eq.args[2])) # Appends (..) to the observable (which is later replaced with the extracted ivs). # Adds the observable to the first line of the output expression (starting with `@variables`). - obs_expr = insert_independent_variable(obs_eq.args[2], :(..)) - push!(observed_vars.args[1].args, obs_expr) + obs_expr = insert_independent_variable(obs_eq.args[2], :(..)) + push!(observed_vars.args[1].args, obs_expr) # Adds a line to the `observed_vars` expression, setting the ivs for this observable. # Cannot extract directly using e.g. "getfield.(dependants_structs, :reactant)" because # then we get something like :([:X1, :X2]), rather than :([X1, X2]). - dep_var_expr = :(filter(!MT.isparameter, Symbolics.get_variables($(obs_eq.args[3])))) - ivs_get_expr = :(unique(reduce(vcat,[arguments(MT.unwrap(dep)) for dep in $dep_var_expr]))) - ivs_get_expr_sorted = :(sort($(ivs_get_expr); by = iv -> findfirst(MT.getname(iv) == ivs for ivs in $ivs_sorted))) - push!(observed_vars.args, :($obs_name = $(obs_name)($(ivs_get_expr_sorted)...))) + dep_var_expr = :(filter( + !MT.isparameter, Symbolics.get_variables($(obs_eq.args[3])))) + ivs_get_expr = :(unique(reduce( + vcat, [arguments(MT.unwrap(dep)) for dep in $dep_var_expr]))) + ivs_get_expr_sorted = :(sort($(ivs_get_expr); + by = iv -> findfirst(MT.getname(iv) == ivs for ivs in $ivs_sorted))) + push!(observed_vars.args, + :($obs_name = $(obs_name)($(ivs_get_expr_sorted)...))) end # In case metadata was given, this must be cleared from `observed_eqs`. @@ -793,7 +820,6 @@ function make_observed_eqs(observables_expr) end end - ### `@reaction` Macro & its Internals ### @doc raw""" @@ -870,14 +896,13 @@ function get_reaction(line) return reaction[1] end - ### Generic Expression Manipulation ### # Recursively traverses an expression and replaces special function call like "hill(...)" with the actual corresponding expression. function recursive_expand_functions!(expr::ExprValues) (typeof(expr) != Expr) && (return expr) foreach(i -> expr.args[i] = recursive_expand_functions!(expr.args[i]), - 1:length(expr.args)) + 1:length(expr.args)) if expr.head == :call !isdefined(Catalyst, expr.args[1]) && (expr.args[1] = esc(expr.args[1])) end @@ -895,4 +920,4 @@ end function get_tup_arg(ex::ExprValues, i::Int) (tup_leng(ex) == 1) && (return ex) return ex.args[i] -end \ No newline at end of file +end diff --git a/src/expression_utils.jl b/src/expression_utils.jl index f0d039759f..b5d93e5c0d 100644 --- a/src/expression_utils.jl +++ b/src/expression_utils.jl @@ -19,7 +19,6 @@ function is_escaped_expr(expr) return (expr isa Expr) && (expr.head == :escape) && (length(expr.args) == 1) end - ### Parameters/Species/Variables Symbols Correctness Checking ### # Throws an error when a forbidden symbol is used. @@ -27,7 +26,7 @@ function forbidden_symbol_check(v) !isempty(intersect(forbidden_symbols_error, v)) && error("The following symbol(s) are used as species or parameters: " * ((map(s -> "'" * string(s) * "', ", - intersect(forbidden_symbols_error, v))...)) * + intersect(forbidden_symbols_error, v))...)) * "this is not permited.") nothing end @@ -37,7 +36,7 @@ function forbidden_variable_check(v) !isempty(intersect(forbidden_variables_error, v)) && error("The following symbol(s) are used as variables: " * ((map(s -> "'" * string(s) * "', ", - intersect(forbidden_variables_error, v))...)) * + intersect(forbidden_variables_error, v))...)) * "this is not permited.") end @@ -47,7 +46,6 @@ function unique_symbol_check(syms) nothing end - ### Catalyst-specific Expressions Manipulation ### # Some options takes input on form that is either `@option ...` or `@option begin ... end`. @@ -75,25 +73,31 @@ function find_varinfo_in_declaration(expr) is_escaped_expr(expr) && (return find_varinfo_in_declaration(expr.args[1])) # Case: X - (expr isa Symbol) && (return expr, [], nothing, nothing) + (expr isa Symbol) && (return expr, [], nothing, nothing) # Case: X(t) - (expr.head == :call) && (return expr.args[1], expr.args[2:end], nothing, nothing) + (expr.head == :call) && (return expr.args[1], expr.args[2:end], nothing, nothing) if expr.head == :(=) # Case: X = 1.0 - (expr.args[1] isa Symbol) && (return expr.args[1], [], expr.args[2], nothing) + (expr.args[1] isa Symbol) && (return expr.args[1], [], expr.args[2], nothing) # Case: X(t) = 1.0 - (expr.args[1].head == :call) && (return expr.args[1].args[1], expr.args[1].args[2:end], expr.args[2].args[1], nothing) + (expr.args[1].head == :call) && + (return expr.args[1].args[1], expr.args[1].args[2:end], expr.args[2].args[1], + nothing) end if expr.head == :tuple # Case: X, [metadata=true] - (expr.args[1] isa Symbol) && (return expr.args[1], [], nothing, expr.args[2]) + (expr.args[1] isa Symbol) && (return expr.args[1], [], nothing, expr.args[2]) # Case: X(t), [metadata=true] - (expr.args[1].head == :call) && (return expr.args[1].args[1], expr.args[1].args[2:end], nothing, expr.args[2]) - if (expr.args[1].head == :(=)) + (expr.args[1].head == :call) && + (return expr.args[1].args[1], expr.args[1].args[2:end], nothing, expr.args[2]) + if (expr.args[1].head == :(=)) # Case: X = 1.0, [metadata=true] - (expr.args[1].args[1] isa Symbol) && (return expr.args[1].args[1], [], expr.args[1].args[2], expr.args[2]) + (expr.args[1].args[1] isa Symbol) && + (return expr.args[1].args[1], [], expr.args[1].args[2], expr.args[2]) # Case: X(t) = 1.0, [metadata=true] - (expr.args[1].args[1].head == :call) && (return expr.args[1].args[1].args[1], expr.args[1].args[1].args[2:end], expr.args[1].args[2].args[1], expr.args[2]) + (expr.args[1].args[1].head == :call) && + (return expr.args[1].args[1].args[1], expr.args[1].args[1].args[2:end], + expr.args[1].args[2].args[1], expr.args[2]) end end error("Unable to detect the variable declared in expression: $expr.") @@ -119,11 +123,11 @@ function insert_independent_variable(expr_in, iv_expr) expr = deepcopy(expr_in) # Loops through possible cases. - if expr.head == :(=) + if expr.head == :(=) # Case: :(X = 1.0) expr.args[1] = Expr(:call, expr.args[1], iv_expr) elseif expr.head == :tuple - if expr.args[1] isa Symbol + if expr.args[1] isa Symbol # Case: :(X, [metadata=true]) expr.args[1] = Expr(:call, expr.args[1], iv_expr) elseif (expr.args[1].head == :(=)) && (expr.args[1].args[1] isa Symbol) diff --git a/src/graphs.jl b/src/graphs.jl index 456e9ea728..2f1fb490c4 100644 --- a/src/graphs.jl +++ b/src/graphs.jl @@ -34,7 +34,6 @@ References: - DOT language guide: http://www.graphviz.org/pdf/dotguide.pdf """ - ### AST ### abstract type Expression end @@ -123,7 +122,6 @@ Edge(path::Vector{String}, attrs::AbstractDict) = Edge(map(NodeID, path), attrs) Edge(path::Vector{String}; attrs...) = Edge(map(NodeID, path), attrs) Edge(path::Vararg{String}; attrs...) = Edge(map(NodeID, collect(path)), attrs) - ### Bindings ### """ Run a Graphviz program. @@ -137,7 +135,7 @@ For bindings to the Graphviz C API, see the the package GraphViz.jl is unmaintained. """ function run_graphviz(io::IO, graph::Graph; prog::Union{String, Nothing} = nothing, - format::String = "json0") + format::String = "json0") if isnothing(prog) prog = graph.prog end @@ -240,7 +238,7 @@ function pprint(io::IO, edge::Edge, n::Int; directed::Bool = false) end function pprint_attrs(io::IO, attrs::Attributes, n::Int = 0; - pre::String = "", post::String = "") + pre::String = "", post::String = "") if !isempty(attrs) indent(io, n) print(io, pre) @@ -319,7 +317,6 @@ function modifystrcomp(strcomp::Vector{String}) strcomp = "<" .* strcomp .* ">" end - ### Public-facing API ### """ @@ -366,7 +363,7 @@ function complexgraph(rn::ReactionSystem; complexdata = reactioncomplexes(rn)) append!(stmts2, compnodes) append!(stmts2, collect(Iterators.flatten(edges))) g = Digraph("G", stmts2; graph_attrs = graph_attrs, node_attrs = node_attrs, - edge_attrs = edge_attrs) + edge_attrs = edge_attrs) return g end @@ -392,15 +389,15 @@ function Graph(rn::ReactionSystem) rxs = reactions(rn) specs = species(rn) statenodes = [Node(string(getname(s)), - Attributes(:shape => "circle", :color => "#6C9AC3")) for s in specs] + Attributes(:shape => "circle", :color => "#6C9AC3")) for s in specs] transnodes = [Node(string("rx_$i"), - Attributes(:shape => "point", :color => "#E28F41", :width => ".1")) + Attributes(:shape => "point", :color => "#E28F41", :width => ".1")) for (i, r) in enumerate(rxs)] stmts = vcat(statenodes, transnodes) edges = map(enumerate(rxs)) do (i, r) vcat(edgify(zip(r.substrates, r.substoich), i, false), - edgify(zip(r.products, r.prodstoich), i, true)) + edgify(zip(r.products, r.prodstoich), i, true)) end es = edgifyrates(rxs, specs) (!isempty(es)) && push!(edges, es) @@ -409,7 +406,7 @@ function Graph(rn::ReactionSystem) append!(stmts2, stmts) append!(stmts2, collect(Iterators.flatten(edges))) g = Digraph("G", stmts2; graph_attrs = graph_attrs, node_attrs = node_attrs, - edge_attrs = edge_attrs) + edge_attrs = edge_attrs) return g end diff --git a/src/latexify_recipes.jl b/src/latexify_recipes.jl index a7d5cf1e7c..0b06da1f6b 100644 --- a/src/latexify_recipes.jl +++ b/src/latexify_recipes.jl @@ -11,7 +11,7 @@ const LATEX_DEFS = CatalystLatexParams() ### Latexify Receipt ### -@latexrecipe function f(rs::ReactionSystem; form = :reactions, expand_functions=true) +@latexrecipe function f(rs::ReactionSystem; form = :reactions, expand_functions = true) expand_functions && (rs = expand_registered_functions(rs)) if form == :reactions # Returns chemical reaction network code. cdot --> false @@ -37,9 +37,9 @@ function Latexify.infer_output(env, rs::ReactionSystem, args...) end function chemical_arrows(rn::ReactionSystem; expand = true, - double_linebreak = LATEX_DEFS.double_linebreak, - starred = LATEX_DEFS.starred, mathrm = true, - mathjax = LATEX_DEFS.mathjax, kwargs...) + double_linebreak = LATEX_DEFS.double_linebreak, + starred = LATEX_DEFS.starred, mathrm = true, + mathjax = LATEX_DEFS.mathjax, kwargs...) any_nonrx_subsys(rn) && (@warn "Latexify currently ignores non-ReactionSystem subsystems. Please call `flatsys = flatten(sys)` to obtain a flattened version of your system before trying to Latexify it.") @@ -81,7 +81,7 @@ function chemical_arrows(rn::ReactionSystem; expand = true, ### Generate formatted string of substrates substrates = [make_stoich_str(substrate[1], substrate[2], subber; mathrm, - kwargs...) + kwargs...) for substrate in zip(r.substrates, r.substoich)] isempty(substrates) && (substrates = ["\\varnothing"]) @@ -106,7 +106,7 @@ function chemical_arrows(rn::ReactionSystem; expand = true, ### Generate formatted string of products products = [make_stoich_str(product[1], product[2], subber; mathrm = true, - kwargs...) + kwargs...) for product in zip(r.products, r.prodstoich)] isempty(products) && (products = ["\\varnothing"]) str *= join(products, " + ") @@ -134,7 +134,6 @@ function chemical_arrows(rn::ReactionSystem; expand = true, return latexstr end - ### Utility ### function any_nonrx_subsys(rn::MT.AbstractSystem) @@ -204,4 +203,4 @@ function make_stoich_str(spec, stoich, subber; mathrm = true, kwargs...) prestr * latexraw(subber(spec); kwargs...) * poststr end end -end \ No newline at end of file +end diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 0dde061ec3..f60bfcbc4a 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -106,7 +106,7 @@ function reactioncomplexes(rn::ReactionSystem; sparse = false) end function reactioncomplexes(::Type{SparseMatrixCSC{Int, Int}}, rn::ReactionSystem, - complextorxsmap) + complextorxsmap) complexes = collect(keys(complextorxsmap)) Is = Int[] Js = Int[] @@ -320,7 +320,6 @@ function incidencematgraph(incidencemat::SparseMatrixCSC{Int, Int}) return graph end - ### Linkage, Deficiency, Reversibility ### """ @@ -399,7 +398,7 @@ end # Used in the subsequent function. function subnetworkmapping(linkageclass, allrxs, complextorxsmap, p) rxinds = sort!(collect(Set(rxidx for rcidx in linkageclass - for rxidx in complextorxsmap[rcidx]))) + for rxidx in complextorxsmap[rcidx]))) rxs = allrxs[rxinds] specset = Set(s for rx in rxs for s in rx.substrates if !isconstant(s)) for rx in rxs @@ -447,7 +446,7 @@ function subnetworks(rs::ReactionSystem) reacs, specs, newps = subnetworkmapping(lcs[i], rxs, complextorxsmap, p) newname = Symbol(nameof(rs), "_", i) push!(subnetworks, - ReactionSystem(reacs, t, specs, newps; name = newname, spatial_ivs)) + ReactionSystem(reacs, t, specs, newps; name = newname, spatial_ivs)) end subnetworks end @@ -539,7 +538,6 @@ function isweaklyreversible(rn::ReactionSystem, subnets) all(Graphs.is_strongly_connected ∘ incidencematgraph, subnets) end - ### Conservation Laws ### # Implements the `conserved` parameter metadata. @@ -653,7 +651,7 @@ function cache_conservationlaw_eqs!(rn::ReactionSystem, N::AbstractMatrix, col_o depidxs = col_order[(r + 1):end] depspecs = sts[depidxs] constants = MT.unwrap.(MT.scalarize(only( - @parameters $(CONSERVED_CONSTANT_SYMBOL)[1:nullity] [conserved=true]))) + @parameters $(CONSERVED_CONSTANT_SYMBOL)[1:nullity] [conserved = true]))) conservedeqs = Equation[] constantdefs = Equation[] @@ -726,28 +724,30 @@ Constructively compute whether a network will have complex-balanced equilibrium solutions, following the method in van der Schaft et al., [2015](https://link.springer.com/article/10.1007/s10910-015-0498-2#Sec3). Accepts a dictionary, vector, or tuple of variable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...]. """ -function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) - if length(parametermap) != numparams(rs) +function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) + if length(parametermap) != numparams(rs) error("Incorrect number of parameters specified.") end pmap = symmap_to_varmap(rs, parametermap) - pmap = Dict(ModelingToolkit.value(k) => v for (k,v) in pmap) + pmap = Dict(ModelingToolkit.value(k) => v for (k, v) in pmap) sm = speciesmap(rs) cm = reactioncomplexmap(rs) complexes, D = reactioncomplexes(rs) rxns = reactions(rs) - nc = length(complexes); nr = numreactions(rs); nm = numspecies(rs) + nc = length(complexes) + nr = numreactions(rs) + nm = numspecies(rs) - if !all(r->ismassaction(r, rs), rxns) - error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.") + if !all(r -> ismassaction(r, rs), rxns) + error("The supplied ReactionSystem has reactions that are not ismassaction. Testing for being complex balanced is currently only supported for pure mass action networks.") end rates = [substitute(rate, pmap) for rate in reactionrates(rs)] # Construct kinetic matrix, K - K = zeros(nr, nc) + K = zeros(nr, nc) for c in 1:nc complex = complexes[c] for (r, dir) in cm[complex] @@ -758,7 +758,7 @@ function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) end end - L = -D*K + L = -D * K S = netstoichmat(rs) # Compute ρ using the matrix-tree theorem @@ -768,25 +768,30 @@ function iscomplexbalanced(rs::ReactionSystem, parametermap::Dict) # Determine if 1) ρ is positive and 2) D^T Ln ρ lies in the image of S^T if all(>(0), ρ) - img = D'*log.(ρ) - if rank(S') == rank(hcat(S', img)) return true else return false end + img = D' * log.(ρ) + if rank(S') == rank(hcat(S', img)) + return true + else + return false + end else return false end end -function iscomplexbalanced(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) +function iscomplexbalanced(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) pdict = Dict(parametermap) iscomplexbalanced(rs, pdict) end -function iscomplexbalanced(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) +function iscomplexbalanced(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) pdict = Dict(parametermap) iscomplexbalanced(rs, pdict) end -iscomplexbalanced(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") - +function iscomplexbalanced(rs::ReactionSystem, parametermap) + error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") +end """ ratematrix(rs::ReactionSystem, parametermap) @@ -794,7 +799,7 @@ iscomplexbalanced(rs::ReactionSystem, parametermap) = error("Parameter map must Given a reaction system with n complexes, outputs an n-by-n matrix where R_{ij} is the rate constant of the reaction between complex i and complex j. Accepts a dictionary, vector, or tuple of variable-to-value mappings, e.g. [k1 => 1.0, k2 => 2.0,...]. """ -function ratematrix(rs::ReactionSystem, rates::Vector{Float64}) +function ratematrix(rs::ReactionSystem, rates::Vector{Float64}) complexes, D = reactioncomplexes(rs) n = length(complexes) rxns = reactions(rs) @@ -802,36 +807,38 @@ function ratematrix(rs::ReactionSystem, rates::Vector{Float64}) for r in 1:length(rxns) rxn = rxns[r] - s = findfirst(==(-1), @view D[:,r]) - p = findfirst(==(1), @view D[:,r]) + s = findfirst(==(-1), @view D[:, r]) + p = findfirst(==(1), @view D[:, r]) ratematrix[s, p] = rates[r] end ratematrix end -function ratematrix(rs::ReactionSystem, parametermap::Dict) - if length(parametermap) != numparams(rs) +function ratematrix(rs::ReactionSystem, parametermap::Dict) + if length(parametermap) != numparams(rs) error("Incorrect number of parameters specified.") end pmap = symmap_to_varmap(rs, parametermap) - pmap = Dict(ModelingToolkit.value(k) => v for (k,v) in pmap) + pmap = Dict(ModelingToolkit.value(k) => v for (k, v) in pmap) rates = [substitute(rate, pmap) for rate in reactionrates(rs)] ratematrix(rs, rates) end -function ratematrix(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) +function ratematrix(rs::ReactionSystem, parametermap::Vector{Pair{Symbol, Float64}}) pdict = Dict(parametermap) ratematrix(rs, pdict) end -function ratematrix(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) +function ratematrix(rs::ReactionSystem, parametermap::Tuple{Pair{Symbol, Float64}}) pdict = Dict(parametermap) ratematrix(rs, pdict) end -ratematrix(rs::ReactionSystem, parametermap) = error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") +function ratematrix(rs::ReactionSystem, parametermap) + error("Parameter map must be a dictionary, tuple, or vector of symbol/value pairs.") +end ### BELOW: Helper functions for iscomplexbalanced @@ -841,7 +848,7 @@ function matrixtree(g::SimpleDiGraph, distmx::Matrix) error("Size of distance matrix is incorrect") end - π = zeros(n) + π = zeros(n) if !Graphs.is_connected(g) ccs = Graphs.connected_components(g) @@ -856,26 +863,26 @@ function matrixtree(g::SimpleDiGraph, distmx::Matrix) # generate all spanning trees ug = SimpleGraph(SimpleDiGraph(g)) - trees = collect(Combinatorics.combinations(collect(edges(ug)), n-1)) + trees = collect(Combinatorics.combinations(collect(edges(ug)), n - 1)) trees = SimpleGraph.(trees) - trees = filter!(t->isempty(Graphs.cycle_basis(t)), trees) + trees = filter!(t -> isempty(Graphs.cycle_basis(t)), trees) # constructed rooted trees for every vertex, compute sum for v in 1:n - rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir=:in)) for t in trees] + rootedTrees = [reverse(Graphs.bfs_tree(t, v, dir = :in)) for t in trees] π[v] = sum([treeweight(t, g, distmx) for t in rootedTrees]) end - + # sum the contributions - return π + return π end -function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) +function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) prod = 1 for e in edges(t) - s = Graphs.src(e); t = Graphs.dst(e) - prod *= distmx[s, t] + s = Graphs.src(e) + t = Graphs.dst(e) + prod *= distmx[s, t] end - prod + prod end - diff --git a/src/reaction.jl b/src/reaction.jl index 23538214aa..d8ff6c6e8e 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -62,7 +62,6 @@ Test if a species is valid as a reactant (i.e. a species variable or a constant """ isvalidreactant(s) = MT.isparameter(s) ? isconstant(s) : (isspecies(s) && !isconstant(s)) - ### Reaction Constructor Functions ### # Checks if a metadata input has an entry :only_use_rate => true @@ -161,8 +160,8 @@ end # Five-argument constructor accepting rate, substrates, and products, and their stoichiometries. function Reaction(rate, subs, prods, substoich, prodstoich; - netstoich = nothing, metadata = Pair{Symbol, Any}[], - only_use_rate = metadata_only_use_rate_check(metadata), kwargs...) + netstoich = nothing, metadata = Pair{Symbol, Any}[], + only_use_rate = metadata_only_use_rate_check(metadata), kwargs...) (isnothing(prods) && isnothing(subs)) && throw(ArgumentError("A reaction requires a non-nothing substrate or product vector.")) (isnothing(prodstoich) && isnothing(substoich)) && @@ -222,7 +221,7 @@ function Reaction(rate, subs, prods, substoich, prodstoich; end # Deletes potential `:only_use_rate => ` entries from the metadata. - if any(:only_use_rate == entry[1] for entry in metadata) + if any(:only_use_rate == entry[1] for entry in metadata) deleteat!(metadata, findfirst(:only_use_rate == entry[1] for entry in metadata)) end @@ -311,7 +310,6 @@ function hash(rx::Reaction, h::UInt) Base.hash(rx.only_use_rate, h) end - ### ModelingToolkit Function Dispatches ### # Used by ModelingToolkit.namespace_equation. @@ -336,7 +334,8 @@ function MT.namespace_equation(rx::Reaction, name; kw...) ns = similar(rx.netstoich) map!(n -> f(n[1]) => f(n[2]), ns, rx.netstoich) end - Reaction(rate, subs, prods, substoich, prodstoich, netstoich, rx.only_use_rate, rx.metadata) + Reaction( + rate, subs, prods, substoich, prodstoich, netstoich, rx.only_use_rate, rx.metadata) end # Overwrites equation-type functions to give the correct input for `Reaction`s. @@ -370,7 +369,7 @@ encountered in: function ModelingToolkit.get_variables!(set, rx::Reaction) if isdefined(Main, :Infiltrator) Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__) - end + end get_variables!(set, rx.rate) foreach(sub -> push!(set, sub), rx.substrates) foreach(prod -> push!(set, prod), rx.products) @@ -410,7 +409,6 @@ function MT.modified_unknowns!(munknowns, rx::Reaction, sts::AbstractVector) munknowns end - ### `Reaction`-specific Functions ### """ @@ -438,7 +436,6 @@ function isbcbalanced(rx::Reaction) true end - ### Reaction Metadata Implementation ### # These are currently considered internal, but can be used by public accessor functions like getnoisescaling. @@ -495,14 +492,14 @@ getmetadata(reaction, :description) ``` """ function getmetadata(reaction::Reaction, md_key::Symbol) - if !hasmetadata(reaction, md_key) + if !hasmetadata(reaction, md_key) error("The reaction does not have a metadata field $md_key. It does have the following metadata fields: $(keys(getmetadata_dict(reaction))).") end metadata = getmetadata_dict(reaction) - return metadata[findfirst(isequal(md_key, entry[1]) for entry in getmetadata_dict(reaction))][2] + return metadata[findfirst(isequal(md_key, entry[1]) + for entry in getmetadata_dict(reaction))][2] end - ### Implemented Reaction Metadata ### # Noise scaling. @@ -636,7 +633,6 @@ function getmisc(reaction::Reaction) end end - ### Units Handling ### """ @@ -670,8 +666,8 @@ function validate(rx::Reaction; info::String = "") if (subunits !== nothing) && (produnits !== nothing) && (subunits != produnits) validated = false @warn(string("in ", rx, - " the substrate units are not consistent with the product units.")) + " the substrate units are not consistent with the product units.")) end validated -end \ No newline at end of file +end diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index edb0f9fedf..c84b1feb3f 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -28,14 +28,14 @@ struct ReactionComplex{V <: Integer} <: AbstractVector{ReactionComplexElement{V} speciesstoichs::Vector{V} function ReactionComplex{V}(speciesids::Vector{Int}, - speciesstoichs::Vector{V}) where {V <: Integer} + speciesstoichs::Vector{V}) where {V <: Integer} new{V}(speciesids, speciesstoichs) end end # Special constructor. function ReactionComplex(speciesids::Vector{Int}, - speciesstoichs::Vector{V}) where {V <: Integer} + speciesstoichs::Vector{V}) where {V <: Integer} (length(speciesids) == length(speciesstoichs)) || error("Creating a complex with different number of species ids and associated stoichiometries.") ReactionComplex{V}(speciesids, speciesstoichs) @@ -60,9 +60,9 @@ end function Base.setindex!(rc::ReactionComplex, t::ReactionComplexElement, i...) (setindex!(rc.speciesids, t.speciesid, i...); - setindex!(rc.speciesstoichs, - t.speciesstoich, i...); - rc) + setindex!(rc.speciesstoichs, + t.speciesstoich, i...); + rc) end function Base.isless(a::ReactionComplexElement, b::ReactionComplexElement) @@ -71,7 +71,6 @@ end Base.Sort.defalg(::ReactionComplex) = Base.DEFAULT_UNSTABLE - ### NetworkProperties Structure ### #! format: off @@ -140,7 +139,6 @@ function reset!(nps::NetworkProperties{I, V}) where {I, V} nothing end - ### ReactionSystem Constructor Functions ### # Used to sort the reaction/equation vector as reactions first, equations second. @@ -209,10 +207,11 @@ end # Constant storing all reaction system fields (in order). Used to check whether the `ReactionSystem` # structure have been updated (in the `reactionsystem_uptodate_check` function). -const reactionsystem_fields = (:eqs, :rxs, :iv, :sivs, :unknowns, :species, :ps, :var_to_name, - :observed, :name, :systems, :defaults, :connection_type, - :networkproperties, :combinatoric_ratelaws, :continuous_events, - :discrete_events, :metadata, :complete) +const reactionsystem_fields = ( + :eqs, :rxs, :iv, :sivs, :unknowns, :species, :ps, :var_to_name, + :observed, :name, :systems, :defaults, :connection_type, + :networkproperties, :combinatoric_ratelaws, :continuous_events, + :discrete_events, :metadata, :complete) """ $(TYPEDEF) @@ -311,12 +310,13 @@ struct ReactionSystem{V <: NetworkProperties} <: # inner constructor is considered private and may change between non-breaking releases. function ReactionSystem(eqs, rxs, iv, sivs, unknowns, spcs, ps, var_to_name, observed, - name, systems, defaults, connection_type, nps, cls, cevs, devs, - metadata = nothing, complete = false; checks::Bool = true) - + name, systems, defaults, connection_type, nps, cls, cevs, devs, + metadata = nothing, complete = false; checks::Bool = true) + # Checks that all parameters have the appropriate Symbolics type. for p in ps - (p isa Symbolics.BasicSymbolic) || error("Parameter $p is not a `BasicSymbolic`. This is required.") + (p isa Symbolics.BasicSymbolic) || + error("Parameter $p is not a `BasicSymbolic`. This is required.") end # unit checks are for ODEs and Reactions only currently @@ -337,9 +337,10 @@ struct ReactionSystem{V <: NetworkProperties} <: end end - rs = new{typeof(nps)}(eqs, rxs, iv, sivs, unknowns, spcs, ps, var_to_name, observed, - name, systems, defaults, connection_type, nps, cls, cevs, - devs, metadata, complete) + rs = new{typeof(nps)}( + eqs, rxs, iv, sivs, unknowns, spcs, ps, var_to_name, observed, + name, systems, defaults, connection_type, nps, cls, cevs, + devs, metadata, complete) checks && validate(rs) rs end @@ -348,31 +349,34 @@ end # Four-argument constructor. Permits additional inputs as optional arguments. # Calls the full constructor. function ReactionSystem(eqs, iv, unknowns, ps; - observed = Equation[], - systems = [], - name = nothing, - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - connection_type = nothing, - checks = true, - networkproperties = nothing, - combinatoric_ratelaws = true, - balanced_bc_check = true, - spatial_ivs = nothing, - continuous_events = nothing, - discrete_events = nothing, - metadata = nothing) - + observed = Equation[], + systems = [], + name = nothing, + default_u0 = Dict(), + default_p = Dict(), + defaults = _merge(Dict(default_u0), Dict(default_p)), + connection_type = nothing, + checks = true, + networkproperties = nothing, + combinatoric_ratelaws = true, + balanced_bc_check = true, + spatial_ivs = nothing, + continuous_events = nothing, + discrete_events = nothing, + metadata = nothing) + # Error checks name === nothing && throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) sysnames = nameof.(systems) - (length(unique(sysnames)) == length(sysnames)) || throw(ArgumentError("System names must be unique.")) + (length(unique(sysnames)) == length(sysnames)) || + throw(ArgumentError("System names must be unique.")) # Handle defaults values provided via optional arguments. if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn("`default_u0` and `default_p` are deprecated. Use `defaults` instead.", :ReactionSystem, force = true) + Base.depwarn( + "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", + :ReactionSystem, force = true) end defaults = MT.todict(defaults) defaults = Dict{Any, Any}(value(k) => value(v) for (k, v) in pairs(defaults)) @@ -385,7 +389,7 @@ function ReactionSystem(eqs, iv, unknowns, ps; else value.(MT.scalarize(spatial_ivs)) end - unknowns′ = sort!(value.(MT.scalarize(unknowns)), by = !isspecies) + unknowns′ = sort!(value.(MT.scalarize(unknowns)), by = !isspecies) spcs = filter(isspecies, unknowns′) ps′ = value.(MT.scalarize(ps)) @@ -436,9 +440,10 @@ function ReactionSystem(eqs, iv, unknowns, ps; ccallbacks = MT.SymbolicContinuousCallbacks(continuous_events) dcallbacks = MT.SymbolicDiscreteCallbacks(discrete_events) - ReactionSystem(eqs′, rxs, iv′, sivs′, unknowns′, spcs, ps′, var_to_name, observed, name, - systems, defaults, connection_type, nps, combinatoric_ratelaws, - ccallbacks, dcallbacks, metadata; checks = checks) + ReactionSystem( + eqs′, rxs, iv′, sivs′, unknowns′, spcs, ps′, var_to_name, observed, name, + systems, defaults, connection_type, nps, combinatoric_ratelaws, + ccallbacks, dcallbacks, metadata; checks = checks) end # Two-argument constructor (reactions/equations and time variable). @@ -458,13 +463,14 @@ end # the model creation) and creates the corresponding vectors. # While species are ordered before variables in the unknowns vector, this ordering is not imposed here, # but carried out at a later stage. -function make_ReactionSystem_internal(rxs_and_eqs::Vector, iv, us_in, ps_in; spatial_ivs = nothing, - continuous_events = [], discrete_events = [], observed = [], kwargs...) +function make_ReactionSystem_internal( + rxs_and_eqs::Vector, iv, us_in, ps_in; spatial_ivs = nothing, + continuous_events = [], discrete_events = [], observed = [], kwargs...) # Filters away any potential obervables from `states` and `spcs`. obs_vars = [obs_eq.lhs for obs_eq in observed] us_in = filter(u -> !any(isequal(u, obs_var) for obs_var in obs_vars), us_in) - + # Creates a combined iv vector (iv and sivs). This is used later in the function (so that # independent variables can be exluded when encountered quantities are added to `us` and `ps`). t = value(iv) @@ -516,21 +522,21 @@ function make_ReactionSystem_internal(rxs_and_eqs::Vector, iv, us_in, ps_in; spa union!(ps, parameters(osys)) else fulleqs = rxs - end + end # Loops through all events, adding encountered quantities to the unknwon and parameter vectors. - find_event_vars!(ps, us, continuous_events, ivs, vars) - find_event_vars!(ps, us, discrete_events, ivs, vars) + find_event_vars!(ps, us, continuous_events, ivs, vars) + find_event_vars!(ps, us, discrete_events, ivs, vars) # Converts the found unknowns and parameters to vectors. usv = collect(us) psv = collect(ps) # Passes the processed input into the next `ReactionSystem` call. - ReactionSystem(fulleqs, t, usv, psv; spatial_ivs, continuous_events, discrete_events, observed, kwargs...) + ReactionSystem(fulleqs, t, usv, psv; spatial_ivs, continuous_events, + discrete_events, observed, kwargs...) end - ### Base Function Dispatches ### """ @@ -581,7 +587,6 @@ function isequivalent(rn1::ReactionSystem, rn2::ReactionSystem; ignorenames = tr true end - ### Basic `ReactionSystem`-specific Accessors ### """ @@ -776,7 +781,6 @@ function reactions(network) [rxs; reduce(vcat, namespace_reactions.(systems); init = Reaction[])] end - """ numreactions(network) @@ -820,7 +824,6 @@ Returns whether `rn` has any spatial independent variables (i.e. is a spatial ne """ isspatial(rn::ReactionSystem) = !isempty(get_sivs(rn)) - ### ModelingToolkit Function Dispatches ### # Retrives events. @@ -835,7 +838,7 @@ function MT.equations(sys::ReactionSystem) if !isempty(systems) eqs = CatalystEqType[eqs; reduce(vcat, MT.namespace_equations.(systems, (ivs,)); - init = Any[])] + init = Any[])] return sort!(eqs; by = eqsortby) end return eqs @@ -852,7 +855,6 @@ function MT.unknowns(sys::ReactionSystem) return sts end - ### Network Matrix Representations ### """ @@ -867,7 +869,7 @@ Note: the associated rate law. """ function substoichmat(::Type{SparseMatrixCSC{T, Int}}, - rn::ReactionSystem) where {T <: Number} + rn::ReactionSystem) where {T <: Number} Is = Int[] Js = Int[] Vs = T[] @@ -917,7 +919,7 @@ Note: the associated rate law. """ function prodstoichmat(::Type{SparseMatrixCSC{T, Int}}, - rn::ReactionSystem) where {T <: Number} + rn::ReactionSystem) where {T <: Number} Is = Int[] Js = Int[] Vs = T[] @@ -973,7 +975,7 @@ Notes: the associated rate law. As such they do not contribute to the net stoichiometry matrix. """ function netstoichmat(::Type{SparseMatrixCSC{T, Int}}, - rn::ReactionSystem) where {T <: Number} + rn::ReactionSystem) where {T <: Number} Is = Int[] Js = Int[] Vs = Vector{T}() @@ -1038,7 +1040,7 @@ end # also updating tehse functionalities. function reactionsystem_uptodate_check() if fieldnames(ReactionSystem) != reactionsystem_fields - @warn "The `ReactionSystem` strcuture have been modified without this being taken into account in the functionality you are attempting to use. Please report this at https://github.com/SciML/Catalyst.jl/issues. Proceed with cautioun, as there might be errors in whichever funcionality you are attempting to use." + @warn "The `ReactionSystem` strcuture have been modified without this being taken into account in the functionality you are attempting to use. Please report this at https://github.com/SciML/Catalyst.jl/issues. Proceed with cautioun, as there might be errors in whichever funcionality you are attempting to use." end end @@ -1208,7 +1210,6 @@ function isautonomous(rs::ReactionSystem) return true end - ### `ReactionSystem` Remaking ### """ @@ -1226,15 +1227,17 @@ Arguments: `default_reaction_metadata` (however, `Reaction`s that already have that metadata designated will not have their value updated). """ -function remake_ReactionSystem_internal(rs::ReactionSystem; default_reaction_metadata = []) - rs = set_default_metadata(rs; default_reaction_metadata) +function remake_ReactionSystem_internal(rs::ReactionSystem; default_reaction_metadata = []) + rs = set_default_metadata(rs; default_reaction_metadata) return rs end # For a `ReactionSystem`, updates all `Reaction`'s default metadata. -function set_default_metadata(rs::ReactionSystem; default_reaction_metadata = []) +function set_default_metadata(rs::ReactionSystem; default_reaction_metadata = []) # Updates reaction metadata for for reactions in this specific system. - eqtransform(eq) = eq isa Reaction ? set_default_metadata(eq, default_reaction_metadata) : eq + function eqtransform(eq) + eq isa Reaction ? set_default_metadata(eq, default_reaction_metadata) : eq + end updated_equations = map(eqtransform, get_eqs(rs)) @set! rs.eqs = updated_equations @set! rs.rxs = Reaction[rx for rx in updated_equations if rx isa Reaction] @@ -1244,17 +1247,18 @@ function set_default_metadata(rs::ReactionSystem; default_reaction_metadata = [ drm_dict = Dict(default_reaction_metadata) if haskey(drm_dict, :noise_scaling) # Finds parameters, species, and variables in the noise scaling term. - ns_expr = drm_dict[:noise_scaling] + ns_expr = drm_dict[:noise_scaling] ns_syms = [Symbolics.unwrap(sym) for sym in get_variables(ns_expr)] ns_ps = Iterators.filter(ModelingToolkit.isparameter, ns_syms) ns_sps = Iterators.filter(Catalyst.isspecies, ns_syms) - ns_vs = Iterators.filter(sym -> !Catalyst.isspecies(sym) && - !ModelingToolkit.isparameter(sym), ns_syms) + ns_vs = Iterators.filter( + sym -> !Catalyst.isspecies(sym) && + !ModelingToolkit.isparameter(sym), ns_syms) # Adds parameters, species, and variables to the `ReactionSystem`. @set! rs.ps = union(get_ps(rs), ns_ps) sps_new = union(get_species(rs), ns_sps) @set! rs.species = sps_new - vs_old = @view get_unknowns(rs)[length(get_species(rs))+1 : end] + vs_old = @view get_unknowns(rs)[(length(get_species(rs)) + 1):end] @set! rs.unknowns = union(sps_new, vs_old, ns_vs) end @@ -1271,7 +1275,8 @@ end # For a `Reaction`, adds missing default metadata values. Equations are passed back unmodified. function set_default_metadata(rx::Reaction, default_metadata) - missing_metadata = filter(md -> !in(md[1], entry[1] for entry in rx.metadata), default_metadata) + missing_metadata = filter( + md -> !in(md[1], entry[1] for entry in rx.metadata), default_metadata) updated_metadata = vcat(rx.metadata, missing_metadata) updated_metadata = convert(Vector{Pair{Symbol, Any}}, updated_metadata) return @set rx.metadata = updated_metadata @@ -1290,10 +1295,10 @@ Arguments: - `noise_scaling`: The updated noise scaling terms """ function set_default_noise_scaling(rs::ReactionSystem, noise_scaling) - return remake_ReactionSystem_internal(rs, default_reaction_metadata = [:noise_scaling => noise_scaling]) + return remake_ReactionSystem_internal( + rs, default_reaction_metadata = [:noise_scaling => noise_scaling]) end - ### ReactionSystem Composing & Hierarchical Modelling ### """ @@ -1347,15 +1352,15 @@ function MT.flatten(rs::ReactionSystem; name = nameof(rs)) error("flattening is currently only supported for subsystems mixing ReactionSystems, NonlinearSystems and ODESystems.") ReactionSystem(equations(rs), get_iv(rs), unknowns(rs), parameters(rs); - observed = MT.observed(rs), - name, - defaults = MT.defaults(rs), - checks = false, - combinatoric_ratelaws = combinatoric_ratelaws(rs), - balanced_bc_check = false, - spatial_ivs = get_sivs(rs), - continuous_events = MT.continuous_events(rs), - discrete_events = MT.discrete_events(rs)) + observed = MT.observed(rs), + name, + defaults = MT.defaults(rs), + checks = false, + combinatoric_ratelaws = combinatoric_ratelaws(rs), + balanced_bc_check = false, + spatial_ivs = get_sivs(rs), + continuous_events = MT.continuous_events(rs), + discrete_events = MT.discrete_events(rs)) end """ @@ -1370,7 +1375,7 @@ Notes: - By default, the new `ReactionSystem` will have the same name as `sys`. """ function ModelingToolkit.extend(sys::MT.AbstractSystem, rs::ReactionSystem; - name::Symbol = nameof(sys)) + name::Symbol = nameof(sys)) any(T -> sys isa T, (ReactionSystem, ODESystem, NonlinearSystem)) || error("ReactionSystems can only be extended with ReactionSystems, ODESystems and NonlinearSystems currently. Received a $(typeof(sys)) system.") @@ -1403,16 +1408,16 @@ function ModelingToolkit.extend(sys::MT.AbstractSystem, rs::ReactionSystem; end ReactionSystem(eqs, t, sts, ps; - observed = obs, - systems = syss, - name, - defaults = defs, - checks = false, - combinatoric_ratelaws, - balanced_bc_check = false, - spatial_ivs = sivs, - continuous_events, - discrete_events) + observed = obs, + systems = syss, + name, + defaults = defs, + checks = false, + combinatoric_ratelaws, + balanced_bc_check = false, + spatial_ivs = sivs, + continuous_events, + discrete_events) end ### Units Handling ### @@ -1439,7 +1444,7 @@ function validate(rs::ReactionSystem, info::String = "") if get_unit(spec) != specunits validated = false @warn(string("Species are expected to have units of ", specunits, - " however, species ", spec, " has units ", get_unit(spec), ".")) + " however, species ", spec, " has units ", get_unit(spec), ".")) end end timeunits = get_unit(get_iv(rs)) @@ -1462,15 +1467,17 @@ function validate(rs::ReactionSystem, info::String = "") isequal(rxunits, rateunits) && continue if istree(rxunits) unitless_exp(rxunits) && continue - (operation(rxunits) == *) && all(unitless_exp(arg) for arg in arguments(rxunits)) && continue + (operation(rxunits) == *) && + all(unitless_exp(arg) for arg in arguments(rxunits)) && continue end validated = false - @warn(string("Reaction rate laws are expected to have units of ", rateunits, " however, ", - rx, " has units of ", rxunits, ".")) + @warn(string( + "Reaction rate laws are expected to have units of ", rateunits, " however, ", + rx, " has units of ", rxunits, ".")) end validated end # Checks if a unit consist of exponents with base 1 (and is this unitless). -unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) \ No newline at end of file +unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 6348e553a9..af3b61eb12 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -90,7 +90,7 @@ function assemble_oderhs(rs, ispcs; combinatoric_ratelaws = true, remove_conserv end function assemble_drift(rs, ispcs; combinatoric_ratelaws = true, as_odes = true, - include_zero_odes = true, remove_conserved = false) + include_zero_odes = true, remove_conserved = false) rhsvec = assemble_oderhs(rs, ispcs; combinatoric_ratelaws, remove_conserved) if as_odes D = Differential(get_iv(rs)) @@ -104,7 +104,7 @@ end # this doesn't work with constraint equations currently function assemble_diffusion(rs, sts, ispcs; combinatoric_ratelaws = true, - remove_conserved = false) + remove_conserved = false) # as BC species should ultimately get an equation, we include them in the noise matrix num_bcsts = count(isbc, get_unknowns(rs)) @@ -227,8 +227,8 @@ Notes: coefficients. """ function ismassaction(rx, rs; rxvars = get_variables(rx.rate), - haveivdep::Union{Nothing, Bool} = nothing, - unknownset = Set(get_unknowns(rs)), ivset = nothing) + haveivdep::Union{Nothing, Bool} = nothing, + unknownset = Set(get_unknowns(rs)), ivset = nothing) # we define non-integer (i.e. float or symbolic) stoich to be non-mass action ((eltype(rx.substoich) <: Integer) && (eltype(rx.prodstoich) <: Integer)) || @@ -289,7 +289,7 @@ end error("$rx has no net stoichiometry change once accounting for constant and boundary condition species. This is not supported.") MassActionJump(Num(rate), reactant_stoch, net_stoch, scale_rates = false, - useiszero = false) + useiszero = false) end # recursively visit each neighbor's rooted tree and mark everything in it as vrj @@ -359,7 +359,7 @@ function assemble_jumps(rs; combinatoric_ratelaws = true) isvrj = isvrjvec[i] if (!isvrj) && ismassaction(rx, rs; rxvars = rxvars, haveivdep = false, - unknownset = unknownset) + unknownset = unknownset) push!(meqs, makemajump(rx; combinatoric_ratelaw = combinatoric_ratelaws)) else rl = jumpratelaw(rx; combinatoric_ratelaw = combinatoric_ratelaws) @@ -378,7 +378,6 @@ function assemble_jumps(rs; combinatoric_ratelaws = true) vcat(meqs, ceqs, veqs) end - ### Equation Coupling ### # merge constraint components with the ReactionSystem components @@ -429,11 +428,10 @@ end function error_if_constraints(::Type{T}, sys::ReactionSystem) where {T <: MT.AbstractSystem} any(eq -> eq isa Equation, get_eqs(sys)) && error("Can not convert to a system of type ", T, - " when there are constraint equations.") + " when there are constraint equations.") nothing end - ### Utility ### # Throws an error when attempting to convert a spatial system to an unssuported type. @@ -453,7 +451,6 @@ diff_2_zero(expr) = (Symbolics.is_derivative(expr) ? 0 : expr) COMPLETENESS_ERROR = "A ReactionSystem must be complete before it can be converted to other system types. A ReactionSystem can be marked as complete using the `complete` function." - ### System Conversions ### """ @@ -473,27 +470,28 @@ Keyword args and default values: the number of equations. """ function Base.convert(::Type{<:ODESystem}, rs::ReactionSystem; name = nameof(rs), - combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - include_zero_odes = true, remove_conserved = false, checks = false, - default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)), - kwargs...) + combinatoric_ratelaws = get_combinatoric_ratelaws(rs), + include_zero_odes = true, remove_conserved = false, checks = false, + default_u0 = Dict(), default_p = Dict(), defaults = _merge( + Dict(default_u0), Dict(default_p)), + kwargs...) iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, ODESystem) fullrs = Catalyst.flatten(rs) remove_conserved && conservationlaws(fullrs) ists, ispcs = get_indep_sts(fullrs, remove_conserved) eqs = assemble_drift(fullrs, ispcs; combinatoric_ratelaws, remove_conserved, - include_zero_odes) + include_zero_odes) eqs, us, ps, obs, defs = addconstraints!(eqs, fullrs, ists, ispcs; remove_conserved) ODESystem(eqs, get_iv(fullrs), us, ps; - observed = obs, - name, - defaults = _merge(defaults,defs), - checks, - continuous_events = MT.get_continuous_events(fullrs), - discrete_events = MT.get_discrete_events(fullrs), - kwargs...) + observed = obs, + name, + defaults = _merge(defaults, defs), + checks, + continuous_events = MT.get_continuous_events(fullrs), + discrete_events = MT.get_discrete_events(fullrs), + kwargs...) end """ @@ -514,14 +512,15 @@ Keyword args and default values: the number of equations. """ function Base.convert(::Type{<:NonlinearSystem}, rs::ReactionSystem; name = nameof(rs), - combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - include_zero_odes = true, remove_conserved = false, checks = false, - default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)), - all_differentials_permitted = false, kwargs...) + combinatoric_ratelaws = get_combinatoric_ratelaws(rs), + include_zero_odes = true, remove_conserved = false, checks = false, + default_u0 = Dict(), default_p = Dict(), defaults = _merge( + Dict(default_u0), Dict(default_p)), + all_differentials_permitted = false, kwargs...) # Error checks. iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, NonlinearSystem) - if !isautonomous(rs) + if !isautonomous(rs) error("Attempting to convert a non-autonomous `ReactionSystem` (e.g. where some rate depend on $(rs.iv)) to a `NonlinearSystem`. This is not possible. if you are intending to compute system steady states, consider creating and solving a `SteadyStateProblem.") end @@ -530,7 +529,7 @@ function Base.convert(::Type{<:NonlinearSystem}, rs::ReactionSystem; name = name remove_conserved && conservationlaws(fullrs) ists, ispcs = get_indep_sts(fullrs, remove_conserved) eqs = assemble_drift(fullrs, ispcs; combinatoric_ratelaws, remove_conserved, - as_odes = false, include_zero_odes) + as_odes = false, include_zero_odes) eqs, us, ps, obs, defs = addconstraints!(eqs, fullrs, ists, ispcs; remove_conserved) # Throws a warning if there are differential equations in non-standard format. @@ -538,13 +537,12 @@ function Base.convert(::Type{<:NonlinearSystem}, rs::ReactionSystem; name = name all_differentials_permitted || nonlinear_convert_differentials_check(rs) eqs = [remove_diffs(eq.lhs) ~ remove_diffs(eq.rhs) for eq in eqs] - NonlinearSystem(eqs, us, ps; - name, - observed = obs, - defaults = _merge(defaults,defs), - checks, - kwargs...) + name, + observed = obs, + defaults = _merge(defaults, defs), + checks, + kwargs...) end # Ideally, when `ReactionSystem`s are converted to `NonlinearSystem`s, any coupled ODEs should be @@ -560,15 +558,15 @@ function nonlinear_convert_differentials_check(rs::ReactionSystem) # If the contenct of the differential is not a variable (and nothing more). # If either of this is a case, throws the warning. if Symbolics._occursin(Symbolics.is_derivative, eq.rhs) || - !Symbolics.istree(eq.lhs) || - !isequal(Symbolics.operation(eq.lhs), Differential(get_iv(rs))) || - (length(arguments(eq.lhs)) != 1) || - !any(isequal(arguments(eq.lhs)[1]), nonspecies(rs)) + !Symbolics.istree(eq.lhs) || + !isequal(Symbolics.operation(eq.lhs), Differential(get_iv(rs))) || + (length(arguments(eq.lhs)) != 1) || + !any(isequal(arguments(eq.lhs)[1]), nonspecies(rs)) error("You are attempting to convert a `ReactionSystem` coupled with differential equations to a `NonlinearSystem`. However, some of these differentials are not of the form `D(x) ~ ...` where: (1) The left-hand side is a differential of a single variable with respect to the time independent variable, and (2) The right-hand side does not contain any differentials. This is generally not permitted. - + If you still would like to perform this conversions, please use the `all_differentials_permitted = true` option. In this case, all differential will be set to `0`. However, it is recommended to proceed with caution to ensure that the produced nonlinear equation makes sense for you intended application." ) @@ -596,10 +594,11 @@ Notes: differential equations. """ function Base.convert(::Type{<:SDESystem}, rs::ReactionSystem; - name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - include_zero_odes = true, checks = false, remove_conserved = false, - default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)), - kwargs...) + name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), + include_zero_odes = true, checks = false, remove_conserved = false, + default_u0 = Dict(), default_p = Dict(), defaults = _merge( + Dict(default_u0), Dict(default_p)), + kwargs...) iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, SDESystem) @@ -608,8 +607,9 @@ function Base.convert(::Type{<:SDESystem}, rs::ReactionSystem; remove_conserved && conservationlaws(flatrs) ists, ispcs = get_indep_sts(flatrs, remove_conserved) eqs = assemble_drift(flatrs, ispcs; combinatoric_ratelaws, include_zero_odes, - remove_conserved) - noiseeqs = assemble_diffusion(flatrs, ists, ispcs; combinatoric_ratelaws, remove_conserved) + remove_conserved) + noiseeqs = assemble_diffusion( + flatrs, ists, ispcs; combinatoric_ratelaws, remove_conserved) eqs, us, ps, obs, defs = addconstraints!(eqs, flatrs, ists, ispcs; remove_conserved) if any(isbc, get_unknowns(flatrs)) @@ -617,13 +617,13 @@ function Base.convert(::Type{<:SDESystem}, rs::ReactionSystem; end SDESystem(eqs, noiseeqs, get_iv(flatrs), us, ps; - observed = obs, - name, - defaults = defs, - checks, - continuous_events = MT.get_continuous_events(flatrs), - discrete_events = MT.get_discrete_events(flatrs), - kwargs...) + observed = obs, + name, + defaults = defs, + checks, + continuous_events = MT.get_continuous_events(flatrs), + discrete_events = MT.get_discrete_events(flatrs), + kwargs...) end """ @@ -645,10 +645,11 @@ Notes: `ModelingToolkit.JumpSystems`. """ function Base.convert(::Type{<:JumpSystem}, rs::ReactionSystem; name = nameof(rs), - combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - remove_conserved = nothing, checks = false, - default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)), - kwargs...) + combinatoric_ratelaws = get_combinatoric_ratelaws(rs), + remove_conserved = nothing, checks = false, + default_u0 = Dict(), default_p = Dict(), defaults = _merge( + Dict(default_u0), Dict(default_p)), + kwargs...) iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, JumpSystem) @@ -669,88 +670,87 @@ function Base.convert(::Type{<:JumpSystem}, rs::ReactionSystem; name = nameof(rs ps = get_ps(flatrs) JumpSystem(eqs, get_iv(flatrs), sts, ps; - observed = MT.observed(flatrs), - name, - defaults = _merge(defaults,MT.defaults(flatrs)), - checks, - discrete_events = MT.discrete_events(flatrs), - kwargs...) + observed = MT.observed(flatrs), + name, + defaults = _merge(defaults, MT.defaults(flatrs)), + checks, + discrete_events = MT.discrete_events(flatrs), + kwargs...) end ### Problems ### # ODEProblem from AbstractReactionNetwork function DiffEqBase.ODEProblem(rs::ReactionSystem, u0, tspan, - p = DiffEqBase.NullParameters(), args...; - check_length = false, name = nameof(rs), - combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - include_zero_odes = true, remove_conserved = false, - checks = false, structural_simplify = false, kwargs...) + p = DiffEqBase.NullParameters(), args...; + check_length = false, name = nameof(rs), + combinatoric_ratelaws = get_combinatoric_ratelaws(rs), + include_zero_odes = true, remove_conserved = false, + checks = false, structural_simplify = false, kwargs...) u0map = symmap_to_varmap(rs, u0) pmap = symmap_to_varmap(rs, p) osys = convert(ODESystem, rs; name, combinatoric_ratelaws, include_zero_odes, checks, - remove_conserved) + remove_conserved) # Handles potential differential algebraic equations (which requires `structural_simplify`). - if structural_simplify + if structural_simplify (osys = MT.structural_simplify(osys)) elseif has_alg_equations(rs) error("The input ReactionSystem has algebraic equations. This requires setting `structural_simplify=true` within `ODEProblem` call.") else osys = complete(osys) end - + return ODEProblem(osys, u0map, tspan, pmap, args...; check_length, kwargs...) end # NonlinearProblem from AbstractReactionNetwork function DiffEqBase.NonlinearProblem(rs::ReactionSystem, u0, - p = DiffEqBase.NullParameters(), args...; - name = nameof(rs), include_zero_odes = true, - combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - remove_conserved = false, checks = false, - check_length = false, all_differentials_permitted = false, kwargs...) + p = DiffEqBase.NullParameters(), args...; + name = nameof(rs), include_zero_odes = true, + combinatoric_ratelaws = get_combinatoric_ratelaws(rs), + remove_conserved = false, checks = false, + check_length = false, all_differentials_permitted = false, kwargs...) u0map = symmap_to_varmap(rs, u0) pmap = symmap_to_varmap(rs, p) nlsys = convert(NonlinearSystem, rs; name, combinatoric_ratelaws, include_zero_odes, - checks, all_differentials_permitted, remove_conserved) + checks, all_differentials_permitted, remove_conserved) nlsys = complete(nlsys) - return NonlinearProblem(nlsys, u0map, pmap, args...; check_length, - kwargs...) + return NonlinearProblem(nlsys, u0map, pmap, args...; check_length, + kwargs...) end # SDEProblem from AbstractReactionNetwork function DiffEqBase.SDEProblem(rs::ReactionSystem, u0, tspan, - p = DiffEqBase.NullParameters(), args...; - name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - include_zero_odes = true, checks = false, check_length = false, - remove_conserved = false, structural_simplify = false, kwargs...) - + p = DiffEqBase.NullParameters(), args...; + name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), + include_zero_odes = true, checks = false, check_length = false, + remove_conserved = false, structural_simplify = false, kwargs...) u0map = symmap_to_varmap(rs, u0) pmap = symmap_to_varmap(rs, p) sde_sys = convert(SDESystem, rs; name, combinatoric_ratelaws, - include_zero_odes, checks, remove_conserved) + include_zero_odes, checks, remove_conserved) # Handles potential differential algebraic equations (which requires `structural_simplify`). - if structural_simplify + if structural_simplify (sde_sys = MT.structural_simplify(sde_sys)) elseif has_alg_equations(rs) error("The input ReactionSystem has algebraic equations. This requires setting `structural_simplify=true` within `ODEProblem` call.") else sde_sys = complete(sde_sys) end - + p_matrix = zeros(length(get_unknowns(sde_sys)), numreactions(rs)) return SDEProblem(sde_sys, u0map, tspan, pmap, args...; check_length, - noise_rate_prototype = p_matrix, kwargs...) + noise_rate_prototype = p_matrix, kwargs...) end # DiscreteProblem from AbstractReactionNetwork function DiffEqBase.DiscreteProblem(rs::ReactionSystem, u0, tspan::Tuple, - p = DiffEqBase.NullParameters(), args...; - name = nameof(rs), - combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - checks = false, kwargs...) + p = DiffEqBase.NullParameters(), args...; + name = nameof(rs), + combinatoric_ratelaws = get_combinatoric_ratelaws(rs), + checks = false, kwargs...) u0map = symmap_to_varmap(rs, u0) pmap = symmap_to_varmap(rs, p) jsys = convert(JumpSystem, rs; name, combinatoric_ratelaws, checks) @@ -760,9 +760,9 @@ end # JumpProblem from AbstractReactionNetwork function JumpProcesses.JumpProblem(rs::ReactionSystem, prob, aggregator, args...; - name = nameof(rs), - combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - checks = false, kwargs...) + name = nameof(rs), + combinatoric_ratelaws = get_combinatoric_ratelaws(rs), + checks = false, kwargs...) jsys = convert(JumpSystem, rs; name, combinatoric_ratelaws, checks) jsys = complete(jsys) return JumpProblem(jsys, prob, aggregator, args...; kwargs...) @@ -770,18 +770,18 @@ end # SteadyStateProblem from AbstractReactionNetwork function DiffEqBase.SteadyStateProblem(rs::ReactionSystem, u0, - p = DiffEqBase.NullParameters(), args...; - check_length = false, name = nameof(rs), - combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - remove_conserved = false, include_zero_odes = true, - checks = false, structural_simplify = false, kwargs...) + p = DiffEqBase.NullParameters(), args...; + check_length = false, name = nameof(rs), + combinatoric_ratelaws = get_combinatoric_ratelaws(rs), + remove_conserved = false, include_zero_odes = true, + checks = false, structural_simplify = false, kwargs...) u0map = symmap_to_varmap(rs, u0) pmap = symmap_to_varmap(rs, p) osys = convert(ODESystem, rs; name, combinatoric_ratelaws, include_zero_odes, checks, - remove_conserved) + remove_conserved) # Handles potential differential algebraic equations (which requires `structural_simplify`). - if structural_simplify + if structural_simplify (osys = MT.structural_simplify(osys)) elseif has_alg_equations(rs) error("The input ReactionSystem has algebraic equations. This requires setting `structural_simplify=true` within `ODEProblem` call.") @@ -792,7 +792,6 @@ function DiffEqBase.SteadyStateProblem(rs::ReactionSystem, u0, return SteadyStateProblem(osys, u0map, pmap, args...; check_length, kwargs...) end - ### Symbolic Variable/Symbol Conversions ### # convert symbol of the form :sys.a.b.c to a symbolic a.b.c @@ -877,7 +876,6 @@ end symmap_to_varmap(sys, symmap) = symmap #error("symmap_to_varmap requires a Dict, AbstractArray or Tuple to map Symbols to values.") - ### Other Conversion-related Functions ### # the following function is adapted from SymbolicUtils.jl v.19 @@ -903,4 +901,4 @@ function to_multivariate_poly(polyeqs::AbstractVector{Symbolics.BasicSymbolic{Re end ps -end \ No newline at end of file +end diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 62b366ca5b..319045dcb7 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -23,16 +23,16 @@ end # Gets the character at a specific index. get_char(str, idx) = collect(str)[idx] -get_char_end(str, offset) = collect(str)[end+offset] +get_char_end(str, offset) = collect(str)[end + offset] # Gets a substring (which is robust to unicode characters like η). get_substring(str, idx1, idx2) = String(collect(str)[idx1:idx2]) -get_substring_end(str, idx1, offset) = String(collect(str)[idx1:end+offset]) - +get_substring_end(str, idx1, offset) = String(collect(str)[idx1:(end + offset)]) ### Field Serialisation Support Functions ### # Function which handles the addition of a single component to the file string. -function push_field(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool, comp_funcs::Tuple) +function push_field(file_text::String, rn::ReactionSystem, + annotate::Bool, top_level::Bool, comp_funcs::Tuple) has_component, get_comp_string, get_comp_annotation = comp_funcs has_component(rn) || (return (file_text, false)) @@ -65,12 +65,12 @@ function get_unsupported_comp_annotation(component::String) return "$(component): (OBS: Currently not supported, and hence empty)" end - ### String Conversion ### # Converts a numeric expression (e.g. p*X + 2Y) to a string (e.g. "p*X + 2Y"). Also ensures that for # any variables (e.g. X(t)) the call part is stripped, and only variable name (e.g. X) is written. -function expression_2_string(expr; strip_call_dict = make_strip_call_dict(Symbolics.get_variables(expr))) +function expression_2_string( + expr; strip_call_dict = make_strip_call_dict(Symbolics.get_variables(expr))) strip_called_expr = substitute(expr, strip_call_dict) return repr(strip_called_expr) end @@ -80,7 +80,7 @@ end function syms_2_strings(syms) strip_called_syms = [strip_call(Symbolics.unwrap(sym)) for sym in syms] return get_substring_end("$(convert(Vector{Any}, strip_called_syms))", 4, 0) -end +end # Converts a vector of symbolics (e.g. the species or parameter vectors) to a string corresponding to # the code required to declare them (potential @parameters or @species commands must still be added). @@ -89,7 +89,8 @@ function syms_2_declaration_string(syms; multiline_format = false) decs_string = (multiline_format ? " begin" : "") for sym in syms delimiter = (multiline_format ? "\n\t" : " ") - @string_append! decs_string delimiter sym_2_declaration_string(sym; multiline_format) + @string_append! decs_string delimiter sym_2_declaration_string( + sym; multiline_format) end multiline_format && (@string_append! decs_string "\nend") return decs_string @@ -107,7 +108,8 @@ function sym_2_declaration_string(sym; multiline_format = false) # to ensure that this is the case. if !(sym isa SymbolicUtils.BasicSymbolic{Real}) sym_type = String(Symbol(typeof(Symbolics.unwrap(sym)))) - if (get_substring(sym_type, 1, 28) != "SymbolicUtils.BasicSymbolic{") || (get_char_end(sym_type, 0) != '}') + if (get_substring(sym_type, 1, 28) != "SymbolicUtils.BasicSymbolic{") || + (get_char_end(sym_type, 0) != '}') error("Encountered symbolic of unexpected type: $sym_type.") end @string_append! dec_string "::" get_substring_end(sym_type, 29, -1) @@ -153,14 +155,14 @@ function x_2_string(x::Vector) @string_append! output x_2_string(val) ", " end return get_substring_end(output, 1, -2) * "]" -end +end function x_2_string(x::Tuple) output = "(" for val in x @string_append! output x_2_string(val) ", " end return get_substring_end(output, 1, -2) * ")" -end +end function x_2_string(x::Dict) output = "Dict([" for key in keys(x) @@ -170,18 +172,18 @@ function x_2_string(x::Dict) end function x_2_string(x::Union{Matrix, Symbolics.Arr{Any, 2}}) output = "[" - for j = 1:size(x)[1] - for i = 1:size(x)[2] - @string_append! output x_2_string(x[j,i]) " " + for j in 1:size(x)[1] + for i in 1:size(x)[2] + @string_append! output x_2_string(x[j, i]) " " end output = get_substring_end(output, 1, -1) * "; " end - return get_substring_end(output, 1, -2) *"]" -end - - -x_2_string(x) = error("Tried to write an unsupported value ($(x)) of an unsupported type ($(typeof(x))) to a string.") + return get_substring_end(output, 1, -2) * "]" +end +function x_2_string(x) + error("Tried to write an unsupported value ($(x)) of an unsupported type ($(typeof(x))) to a string.") +end ### Symbolics Metadata Handling ### @@ -209,32 +211,28 @@ end # List of all recognised metadata (we should add as many as possible), and th keyword used to declare # them in code. -const RECOGNISED_METADATA = Dict([ - Catalyst.ParameterConstantSpecies => "isconstantspecies" - Catalyst.VariableBCSpecies => "isbcspecies" - Catalyst.VariableSpecies => "isspecies" - Catalyst.EdgeParameter => "edgeparameter" - Catalyst.CompoundSpecies => "iscompound" - Catalyst.CompoundComponents => "components" - Catalyst.CompoundCoefficients => "coefficients" - - ModelingToolkit.VariableDescription => "description" - ModelingToolkit.VariableBounds => "bounds" - ModelingToolkit.VariableUnit => "unit" - ModelingToolkit.VariableConnectType => "connect" - ModelingToolkit.VariableNoiseType => "noise" - ModelingToolkit.VariableInput => "input" - ModelingToolkit.VariableOutput => "output" - ModelingToolkit.VariableIrreducible => "irreducible" - ModelingToolkit.VariableStatePriority => "state_priority" - ModelingToolkit.VariableMisc => "misc" - ModelingToolkit.TimeDomain => "timedomain" -]) +const RECOGNISED_METADATA = Dict([Catalyst.ParameterConstantSpecies => "isconstantspecies" + Catalyst.VariableBCSpecies => "isbcspecies" + Catalyst.VariableSpecies => "isspecies" + Catalyst.EdgeParameter => "edgeparameter" + Catalyst.CompoundSpecies => "iscompound" + Catalyst.CompoundComponents => "components" + Catalyst.CompoundCoefficients => "coefficients" + ModelingToolkit.VariableDescription => "description" + ModelingToolkit.VariableBounds => "bounds" + ModelingToolkit.VariableUnit => "unit" + ModelingToolkit.VariableConnectType => "connect" + ModelingToolkit.VariableNoiseType => "noise" + ModelingToolkit.VariableInput => "input" + ModelingToolkit.VariableOutput => "output" + ModelingToolkit.VariableIrreducible => "irreducible" + ModelingToolkit.VariableStatePriority => "state_priority" + ModelingToolkit.VariableMisc => "misc" + ModelingToolkit.TimeDomain => "timedomain"]) # List of metadata that does not need to be explicitly declared to be added (or which is handled separately). -const SKIPPED_METADATA = [ModelingToolkit.MTKVariableTypeCtx, Symbolics.VariableSource, - Symbolics.VariableDefaultValue, Catalyst.VariableSpecies] - +const SKIPPED_METADATA = [ModelingToolkit.MTKVariableTypeCtx, Symbolics.VariableSource, + Symbolics.VariableDefaultValue, Catalyst.VariableSpecies] ### Generic Expression Handling ### @@ -252,7 +250,6 @@ function make_strip_call_dict(rn::ReactionSystem) return make_strip_call_dict(get_unknowns(rn)) end - ### Handle Parameters/Species/Variables Declaration Dependencies ### # Gets a vector with the symbolics a symbolic depends on (currently only considers defaults). @@ -284,10 +281,8 @@ function dependency_split!(remaining_syms, all_remaining_syms) return writable_syms end - ### Other Functions ### - # Checks if a symbolic's declaration is "complicated". The declaration is considered complicated # if it have metadata, default value, or type designation that must be declared. function complicated_declaration(sym) @@ -295,4 +290,4 @@ function complicated_declaration(sym) ModelingToolkit.hasdefault(sym) && (return true) (sym isa SymbolicUtils.BasicSymbolic{Real}) || (return true) return false -end \ No newline at end of file +end diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index 964e3bb617..bb2110db3f 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -19,7 +19,6 @@ end # Combines the 3 independent variable-related functions in a constant tuple. IV_FS = (has_iv, get_iv_string, get_iv_annotation) - ### Handles Spatial Independent Variables ### # Checks if the reaction system have any spatial independent variables. @@ -40,12 +39,12 @@ end # Combines the 3 independent variables-related functions in a constant tuple. SIVS_FS = (has_sivs, get_sivs_string, get_sivs_annotation) - ### Handles Species, Variables, and Parameters ### # Function which handles the addition of species, variable, and parameter declarations to the file # text. These must be handled as a unity in case there are default value dependencies between these. -function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) +function handle_us_n_ps( + file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) # Fetches the systems parameters, species, and variables. Computes the `has_` `Bool`s. ps_all = get_ps(rn) sps_all = get_species(rn) @@ -103,14 +102,20 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, t while !(isempty(remaining_ps) && isempty(remaining_sps) && isempty(remaining_vars)) # Checks which parameters/species/variables can be written. The `dependency_split` # function updates the `remaining_` input. - writable_ps = dependency_split!(remaining_ps, [remaining_ps; remaining_sps; remaining_vars]) - writable_sps = dependency_split!(remaining_sps, [remaining_ps; remaining_sps; remaining_vars]) - writable_vars = dependency_split!(remaining_vars, [remaining_ps; remaining_sps; remaining_vars]) - + writable_ps = dependency_split!( + remaining_ps, [remaining_ps; remaining_sps; remaining_vars]) + writable_sps = dependency_split!( + remaining_sps, [remaining_ps; remaining_sps; remaining_vars]) + writable_vars = dependency_split!( + remaining_vars, [remaining_ps; remaining_sps; remaining_vars]) + # Writes those that can be written. - isempty(writable_ps) || @string_append! us_n_ps_string get_parameters_string(writable_ps) "\n" - isempty(writable_sps) || @string_append! us_n_ps_string get_species_string(writable_sps) "\n" - isempty(writable_vars) || @string_append! us_n_ps_string get_variables_string(writable_vars) "\n" + isempty(writable_ps) || + @string_append! us_n_ps_string get_parameters_string(writable_ps) "\n" + isempty(writable_sps) || + @string_append! us_n_ps_string get_species_string(writable_sps) "\n" + isempty(writable_vars) || + @string_append! us_n_ps_string get_variables_string(writable_vars) "\n" end # For parameters, species, and/or variables with dependencies, creates final vectors. @@ -131,7 +136,6 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, t return file_text * us_n_ps_string, has_ps, has_sps, has_vars end - ### Handles Parameters ### # Unlike most other fields, there are not called via `push_field`, but rather via `handle_us_n_ps`. # Hence they work slightly differently. @@ -154,7 +158,6 @@ function get_parameters_annotation(rn::ReactionSystem) return "Parameters:" end - ### Handles Species ### # Unlike most other fields, there are not called via `push_field`, but rather via `handle_us_n_ps`. # Hence they work slightly differently. @@ -177,7 +180,6 @@ function get_species_annotation(rn::ReactionSystem) return "Species:" end - ### Handles Variables ### # Unlike most other fields, there are not called via `push_field`, but rather via `handle_us_n_ps`. # Hence they work slightly differently. @@ -203,7 +205,6 @@ end # Combines the 3 variables-related functions in a constant tuple. VARIABLES_FS = (has_variables, get_variables_string, get_variables_annotation) - ### Handles Reactions ### # Checks if the reaction system have any reactions. @@ -224,7 +225,7 @@ function get_reactions_string(rn::ReactionSystem) # Creates the string corresponding to the code which generates the system's reactions. rxs_string = "rxs = [" for rx in get_rxs(rn) - @string_append! rxs_string "\n\t" * reaction_string(rx, strip_call_dict) "," + @string_append! rxs_string "\n\t"*reaction_string(rx, strip_call_dict) "," end # Updates the string (including removing the last `,`) and returns it. @@ -235,7 +236,7 @@ end function reaction_string(rx::Reaction, strip_call_dict) # Prepares the `Reaction` declaration components. rate = expression_2_string(rx.rate; strip_call_dict) - substrates = isempty(rx.substrates) ? "nothing" : x_2_string(rx.substrates) + substrates = isempty(rx.substrates) ? "nothing" : x_2_string(rx.substrates) products = isempty(rx.products) ? "nothing" : x_2_string(rx.products) substoich = isempty(rx.substoich) ? "nothing" : x_2_string(rx.substoich) prodstoich = isempty(rx.prodstoich) ? "nothing" : x_2_string(rx.prodstoich) @@ -268,7 +269,6 @@ end # Combines the 3 reactions-related functions in a constant tuple. REACTIONS_FS = (has_reactions, get_reactions_string, get_reactions_annotation) - ### Handles Equations ### # Checks if the reaction system have any equations. @@ -288,7 +288,7 @@ function get_equations_string(rn::ReactionSystem) # Creates the string corresponding to the code which generates the system's reactions. eqs_string = "eqs = [" - for eq in get_eqs(rn)[length(get_rxs(rn)) + 1:end] + for eq in get_eqs(rn)[(length(get_rxs(rn)) + 1):end] @string_append! eqs_string "\n\t" expression_2_string(eq; strip_call_dict) "," end @@ -304,7 +304,6 @@ end # Combines the 3 equations-related functions in a constant tuple. EQUATIONS_FS = (has_equations, get_equations_string, get_equations_annotation) - ### Handles Observables ### # Checks if the reaction system have any observables. @@ -344,7 +343,7 @@ function get_observed_string(rn::ReactionSystem) end # Updates the string (including removing the last `,`) and returns it. - return observed_string[1:end-1] * "\n]" + return observed_string[1:(end - 1)] * "\n]" end # Creates an annotation for the system's observables. @@ -355,7 +354,6 @@ end # Combines the 3 -related functions in a constant tuple. OBSERVED_FS = (has_observed, get_observed_string, get_observed_annotation) - ### Handles Continuous Events ### # Checks if the reaction system have any continuous events. @@ -376,7 +374,8 @@ function get_continuous_events_string(rn::ReactionSystem) # Creates the string corresponding to the code which generates the system's reactions. continuous_events_string = "continuous_events = [" for continuous_event in MT.get_continuous_events(rn) - @string_append! continuous_events_string "\n\t" continuous_event_string(continuous_event, strip_call_dict) "," + @string_append! continuous_events_string "\n\t" continuous_event_string( + continuous_event, strip_call_dict) "," end # Updates the string (including removing the last `,`) and returns it. @@ -410,8 +409,8 @@ function get_continuous_events_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -CONTINUOUS_EVENTS_FS = (has_continuous_events, get_continuous_events_string, get_continuous_events_annotation) - +CONTINUOUS_EVENTS_FS = ( + has_continuous_events, get_continuous_events_string, get_continuous_events_annotation) ### Handles Discrete Events ### @@ -433,7 +432,8 @@ function get_discrete_events_string(rn::ReactionSystem) # Creates the string corresponding to the code which generates the system's reactions. discrete_events_string = "discrete_events = [" for discrete_event in MT.get_discrete_events(rn) - @string_append! discrete_events_string "\n\t" discrete_event_string(discrete_event, strip_call_dict) "," + @string_append! discrete_events_string "\n\t" discrete_event_string( + discrete_event, strip_call_dict) "," end # Updates the string (including removing the last `,`) and returns it. @@ -466,18 +466,19 @@ function get_discrete_events_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -DISCRETE_EVENTS_FS = (has_discrete_events, get_discrete_events_string, get_discrete_events_annotation) - +DISCRETE_EVENTS_FS = ( + has_discrete_events, get_discrete_events_string, get_discrete_events_annotation) ### Handles Systems ### # Specific `push_field` function, which is used for the system field (where the annotation option # must be passed to the `get_component_string` function). Since non-ReactionSystem systems cannot be # written to file, this functions throws an error if any such systems are encountered. -function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) +function push_systems_field( + file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) # Checks whther there are any subsystems, and if these are ReactionSystems. has_systems(rn) || (return (file_text, false)) - if any(!(system isa ReactionSystem) for system in MT.get_systems(rn)) + if any(!(system isa ReactionSystem) for system in MT.get_systems(rn)) error("Tries to write a ReactionSystem to file which have non-ReactionSystem subs-systems. This is currently not possible.") end @@ -501,7 +502,8 @@ function get_systems_string(rn::ReactionSystem, annotate::Bool) # Loops through all systems, adding their declaration to the system string. for (idx, system) in enumerate(MT.get_systems(rn)) - annotate && (@string_append! systems_string "\n\n# Declares subsystem: $(getname(system))") + annotate && + (@string_append! systems_string "\n\n# Declares subsystem: $(getname(system))") # Manipulates the subsystem declaration to make it nicer. subsystem_string = get_full_system_string(system, annotate, false) @@ -521,7 +523,6 @@ end # Combines the 3 systems-related functions in a constant tuple. SYSTEMS_FS = (has_systems, get_systems_string, get_systems_annotation) - ### Handles Connection Types ### # Checks if the reaction system have any connection types. @@ -540,4 +541,5 @@ function get_connection_type_annotation(rn::ReactionSystem) end # Combines the 3 connection types-related functions in a constant tuple. -CONNECTION_TYPE_FS = (has_connection_type, get_connection_type_string, get_connection_type_annotation) \ No newline at end of file +CONNECTION_TYPE_FS = ( + has_connection_type, get_connection_type_string, get_connection_type_annotation) diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index 3dc61c5c75..2afeb4bb50 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -32,12 +32,13 @@ Notes: - Reaction systems with components that have units cannot currently be saved. - The `ReactionSystem` is saved using *programmatic* (not DSL) format for model creation. """ -function save_reactionsystem(filename::String, rn::ReactionSystem; annotate = true, safety_check = true) +function save_reactionsystem( + filename::String, rn::ReactionSystem; annotate = true, safety_check = true) reactionsystem_uptodate_check() open(filename, "w") do file write(file, get_full_system_string(rn, annotate, true)) end - if safety_check + if safety_check if !isequal(rn, include(joinpath(pwd(), filename))) rm(filename) error("The serialised `ReactionSystem` is not equal to the original one. Please make a report (including the full system) at https://github.com/SciML/Catalyst.jl/issues. To disable this behaviour, please pass the `safety_check = false` argument to `save_reactionsystem` (warning, this will permit the serialisation of an erroneous system).") @@ -51,7 +52,7 @@ end function get_full_system_string(rn::ReactionSystem, annotate::Bool, top_level::Bool) # Initiates the file string. file_text = "" - + # Goes through each type of system component, potentially adding it to the string. # Species, variables, and parameters must be handled differently in case there is default-values # dependencies between them. @@ -59,23 +60,28 @@ function get_full_system_string(rn::ReactionSystem, annotate::Bool, top_level::B # to the function that creates the next sub-system declarations. file_text, _ = push_field(file_text, rn, annotate, top_level, IV_FS) file_text, has_sivs = push_field(file_text, rn, annotate, top_level, SIVS_FS) - file_text, has_parameters, has_species, has_variables = handle_us_n_ps(file_text, rn, annotate, top_level) + file_text, has_parameters, has_species, has_variables = handle_us_n_ps( + file_text, rn, annotate, top_level) file_text, has_reactions = push_field(file_text, rn, annotate, top_level, REACTIONS_FS) file_text, has_equations = push_field(file_text, rn, annotate, top_level, EQUATIONS_FS) file_text, has_observed = push_field(file_text, rn, annotate, top_level, OBSERVED_FS) - file_text, has_continuous_events = push_field(file_text, rn, annotate, top_level, CONTINUOUS_EVENTS_FS) - file_text, has_discrete_events = push_field(file_text, rn, annotate, top_level, DISCRETE_EVENTS_FS) + file_text, has_continuous_events = push_field( + file_text, rn, annotate, top_level, CONTINUOUS_EVENTS_FS) + file_text, has_discrete_events = push_field( + file_text, rn, annotate, top_level, DISCRETE_EVENTS_FS) file_text, has_systems = push_systems_field(file_text, rn, annotate, top_level) - file_text, has_connection_type = push_field(file_text, rn, annotate, top_level, CONNECTION_TYPE_FS) + file_text, has_connection_type = push_field( + file_text, rn, annotate, top_level, CONNECTION_TYPE_FS) # Finalises the system. Creates the final `ReactionSystem` call. # Enclose everything ing a `let ... end` block. - rs_creation_code = make_reaction_system_call(rn, annotate, top_level, has_sivs, has_species, - has_variables, has_parameters, has_reactions, - has_equations, has_observed, has_continuous_events, - has_discrete_events, has_systems, has_connection_type) - annotate || (@string_prepend! "\n" file_text) - @string_prepend! "let" file_text + rs_creation_code = make_reaction_system_call( + rn, annotate, top_level, has_sivs, has_species, + has_variables, has_parameters, has_reactions, + has_equations, has_observed, has_continuous_events, + has_discrete_events, has_systems, has_connection_type) + annotate || (@string_prepend! "\n" file_text) + @string_prepend! "let" file_text @string_append! file_text "\n\n" rs_creation_code "\n\nend" return file_text @@ -83,10 +89,11 @@ end # Creates a ReactionSystem call for creating the model. Adds all the correct inputs to it. The input # `has_` `Bool`s described which inputs are used. If model is `complete`, this is handled here. -function make_reaction_system_call(rs::ReactionSystem, annotate, top_level, has_sivs, has_species, - has_variables, has_parameters, has_reactions, has_equations, - has_observed, has_continuous_events, has_discrete_events, - has_systems, has_connection_type) +function make_reaction_system_call( + rs::ReactionSystem, annotate, top_level, has_sivs, has_species, + has_variables, has_parameters, has_reactions, has_equations, + has_observed, has_continuous_events, has_discrete_events, + has_systems, has_connection_type) # Gets the independent variable input. iv = x_2_string(get_iv(rs)) @@ -98,7 +105,7 @@ function make_reaction_system_call(rs::ReactionSystem, annotate, top_level, has_ eqs = "rxs" elseif has_equations eqs = "eqs" - else + else eqs = "[]" end @@ -109,14 +116,14 @@ function make_reaction_system_call(rs::ReactionSystem, annotate, top_level, has_ unknowns = "sps" elseif has_variables unknowns = "vars" - else + else unknowns = "[]" end # Gets the parameters input. if has_parameters ps = "ps" - else + else ps = "[]" end @@ -140,10 +147,12 @@ function make_reaction_system_call(rs::ReactionSystem, annotate, top_level, has_ has_connection_type && (@string_append! reaction_system_string ", connection_type") # Potentially appends a combinatoric_ratelaws statement. - Symbolics.unwrap(rs.combinatoric_ratelaws) || (@string_append! reaction_system_string ", combinatoric_ratelaws = false") + Symbolics.unwrap(rs.combinatoric_ratelaws) || + (@string_append! reaction_system_string ", combinatoric_ratelaws = false") # Potentially appends `ReactionSystem` metadata value(s). Weird composite types are not supported. - isnothing(rs.metadata) || (@string_append! reaction_system_string ", metadata = $(x_2_string(rs.metadata))") + isnothing(rs.metadata) || + (@string_append! reaction_system_string ", metadata = $(x_2_string(rs.metadata))") # Finalises the call. Appends potential annotation. If the system is complete, add a call for this. @string_append! reaction_system_string ")" @@ -152,8 +161,8 @@ function make_reaction_system_call(rs::ReactionSystem, annotate, top_level, has_ top_level || (@string_prepend! "local " reaction_system_string) @string_append! reaction_system_string "\ncomplete(rs)" end - if annotate + if annotate @string_prepend! "# Declares ReactionSystem model:\n" reaction_system_string end return reaction_system_string -end \ No newline at end of file +end diff --git a/src/registered_functions.jl b/src/registered_functions.jl index 70f06ac080..934cb202f7 100644 --- a/src/registered_functions.jl +++ b/src/registered_functions.jl @@ -109,7 +109,6 @@ function Symbolics.derivative(::typeof(hillar), args::NTuple{5, Any}, ::Val{5}) (args[1]^args[5] + args[2]^args[5] + args[4]^args[5])^2 end - ### Custom CRN FUnction-related Functions ### """ @@ -121,25 +120,27 @@ function expand_registered_functions(expr) istree(expr) || return expr args = arguments(expr) if operation(expr) == Catalyst.mm - return args[2]*args[1]/(args[1] + args[3]) + return args[2] * args[1] / (args[1] + args[3]) elseif operation(expr) == Catalyst.mmr - return args[2]*args[3]/(args[1] + args[3]) + return args[2] * args[3] / (args[1] + args[3]) elseif operation(expr) == Catalyst.hill - return args[2]*(args[1]^args[4])/((args[1])^args[4] + (args[3])^args[4]) + return args[2] * (args[1]^args[4]) / ((args[1])^args[4] + (args[3])^args[4]) elseif operation(expr) == Catalyst.hillr - return args[2]*(args[3]^args[4])/((args[1])^args[4] + (args[3])^args[4]) + return args[2] * (args[3]^args[4]) / ((args[1])^args[4] + (args[3])^args[4]) elseif operation(expr) == Catalyst.hillar - return args[3]*(args[1]^args[5])/((args[1])^args[5] + (args[2])^args[5] + (args[4])^args[5]) + return args[3] * (args[1]^args[5]) / + ((args[1])^args[5] + (args[2])^args[5] + (args[4])^args[5]) end - for i = 1:length(args) + for i in 1:length(args) args[i] = expand_registered_functions(args[i]) end return expr end # If applied to a Reaction, return a reaction with its rate modified. function expand_registered_functions(rx::Reaction) - Reaction(expand_registered_functions(rx.rate), rx.substrates, rx.products, rx.substoich, - rx.prodstoich, rx.netstoich, rx.only_use_rate, rx.metadata) + Reaction( + expand_registered_functions(rx.rate), rx.substrates, rx.products, rx.substoich, + rx.prodstoich, rx.netstoich, rx.only_use_rate, rx.metadata) end # If applied to a Equation, returns it with it applied to lhs and rhs function expand_registered_functions(eq::Equation) diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 818da05d1a..51b106da32 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -1,34 +1,37 @@ ### JumpProblem ### # Builds a spatial DiscreteProblem from a Lattice Reaction System. -function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) - if !is_transport_system(lrs) +function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, + p_in = DiffEqBase.NullParameters(), args...; kwargs...) + if !is_transport_system(lrs) error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") end # Converts potential symmaps to varmaps # Vertex and edge parameters may be given in a tuple, or in a common vector, making parameter case complicated. u0_in = symmap_to_varmap(lrs, u0_in) - p_in = (p_in isa Tuple{<:Any,<:Any}) ? - (symmap_to_varmap(lrs, p_in[1]),symmap_to_varmap(lrs, p_in[2])) : - symmap_to_varmap(lrs, p_in) + p_in = (p_in isa Tuple{<:Any, <:Any}) ? + (symmap_to_varmap(lrs, p_in[1]), symmap_to_varmap(lrs, p_in[2])) : + symmap_to_varmap(lrs, p_in) # Converts u0 and p to their internal forms. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. - u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) + u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. # These elements are length 1 vectors (if the parameter is uniform), # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). - vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) - + vert_ps, edge_ps = lattice_process_p( + p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + # Returns a DiscreteProblem. # Previously, a Tuple was used for (vert_ps, edge_ps), but this was converted to a Vector internally. return DiscreteProblem(u0, tspan, [vert_ps, edge_ps], args...; kwargs...) end # Builds a spatial JumpProblem from a DiscreteProblem containg a Lattice Reaction System. -function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(lrs.rs), - combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), kwargs...) +function JumpProcesses.JumpProblem( + lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(lrs.rs), + combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), kwargs...) # Error checks. if !isnothing(dprob.f.sys) error("Unexpected `DiscreteProblem` passed into `JumpProblem`. Was a `LatticeReactionSystem` used as input to the initial `DiscreteProblem`?") @@ -42,10 +45,11 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator # The non-spatial DiscreteProblem have a u0 matrix with entries for all combinations of species and vertexes. hopping_constants = make_hopping_constants(dprob, lrs) sma_jumps = make_spatial_majumps(dprob, lrs) - non_spat_dprob = DiscreteProblem(reshape(dprob.u0, lrs.num_species, lrs.num_verts), dprob.tspan, first.(dprob.p[1])) + non_spat_dprob = DiscreteProblem( + reshape(dprob.u0, lrs.num_species, lrs.num_verts), dprob.tspan, first.(dprob.p[1])) - return JumpProblem(non_spat_dprob, aggregator, sma_jumps; - hopping_constants, spatial_system = lrs.lattice, name, kwargs...) + return JumpProblem(non_spat_dprob, aggregator, sma_jumps; + hopping_constants, spatial_system = lrs.lattice, name, kwargs...) end # Creates the hopping constants from a discrete problem and a lattice reaction system. @@ -53,19 +57,21 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst # Creates the all_diff_rates vector, containing for each species, its transport rate across all edges. # If transport rate is uniform for one species, the vector have a single element, else one for each edge. spatial_rates_dict = Dict(compute_all_transport_rates(dprob.p[1], dprob.p[2], lrs)) - all_diff_rates = [haskey(spatial_rates_dict, s) ? spatial_rates_dict[s] : [0.0] for s in species(lrs)] + all_diff_rates = [haskey(spatial_rates_dict, s) ? spatial_rates_dict[s] : [0.0] + for s in species(lrs)] # Creates the hopping constant Matrix. It contains one element for each combination of species and vertex. # Each element is a Vector, containing the outgoing hopping rates for that species, from that vertex, on that edge. - hopping_constants = [Vector{Float64}(undef, length(lrs.lattice.fadjlist[j])) + hopping_constants = [Vector{Float64}(undef, length(lrs.lattice.fadjlist[j])) for i in 1:(lrs.num_species), j in 1:(lrs.num_verts)] # For each edge, finds each position in `hopping_constants`. for (e_idx, e) in enumerate(edges(lrs.lattice)) - dst_idx = findfirst(isequal(e.dst), lrs.lattice.fadjlist[e.src]) + dst_idx = findfirst(isequal(e.dst), lrs.lattice.fadjlist[e.src]) # For each species, sets that hopping rate. for s_idx in 1:(lrs.num_species) - hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx) + hopping_constants[s_idx, e.src][dst_idx] = get_component_value( + all_diff_rates[s_idx], e_idx) end end @@ -77,7 +83,8 @@ end # Not sure if there is any form of performance improvement from that though. Possibly is not the case. function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) # Creates a vector, storing which reactions have spatial components. - is_spatials = [Catalyst.has_spatial_vertex_component(rx.rate, lrs; vert_ps = dprob.p[1]) for rx in reactions(lrs.rs)] + is_spatials = [Catalyst.has_spatial_vertex_component( + rx.rate, lrs; vert_ps = dprob.p[1]) for rx in reactions(lrs.rs)] # Creates templates for the rates (uniform and spatial) and the stoichiometries. # We cannot fetch reactant_stoich and net_stoich from a (non-spatial) MassActionJump. @@ -88,7 +95,7 @@ function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) net_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(lrs.rs))) # Loops through reactions with non-spatial rates, computes their rates and stoichiometries. - cur_rx = 1; + cur_rx = 1 for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs)) is_spat && continue u_rates[cur_rx] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1])[1] @@ -98,9 +105,10 @@ function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) cur_rx += 1 end # Loops through reactions with spatial rates, computes their rates and stoichiometries. - for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs)) + for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs)) is_spat || continue - s_rates[cur_rx-length(u_rates),:] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1]) + s_rates[cur_rx - length(u_rates), :] = compute_vertex_value( + rx.rate, lrs; vert_ps = dprob.p[1]) substoich_map = Pair.(rx.substrates, rx.substoich) reactant_stoich[cur_rx] = int_map(substoich_map, lrs.rs) net_stoich[cur_rx] = int_map(rx.netstoich, lrs.rs) @@ -108,7 +116,7 @@ function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) end # SpatialMassActionJump expects empty rate containers to be nothing. isempty(u_rates) && (u_rates = nothing) - (count(is_spatials)==0) && (s_rates = nothing) + (count(is_spatials) == 0) && (s_rates = nothing) return SpatialMassActionJump(u_rates, s_rates, reactant_stoich, net_stoich) end @@ -116,7 +124,7 @@ end ### Extra ### # Temporary. Awaiting implementation in SII, or proper implementation withinCatalyst (with more general functionality). -function int_map(map_in, sys) where {T,S} +function int_map(map_in, sys) where {T, S} return [ModelingToolkit.variable_index(sys, pair[1]) => pair[2] for pair in map_in] end diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index a153a0b2a6..695bb2a28e 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,7 +1,7 @@ ### Lattice Reaction Network Structure ### # Describes a spatial reaction network over a graph. # Adding the "<: MT.AbstractTimeDependentSystem" part messes up show, disabling me from creating LRSs. -struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem +struct LatticeReactionSystem{S, T} # <: MT.AbstractTimeDependentSystem # Input values. """The reaction system within each compartment.""" rs::ReactionSystem{S} @@ -38,32 +38,37 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem edge_parameters::Vector{BasicSymbolic{Real}} function LatticeReactionSystem(rs::ReactionSystem{S}, spatial_reactions::Vector{T}, - lattice::DiGraph; init_digraph = true) where {S, T} + lattice::DiGraph; init_digraph = true) where {S, T} # There probably some better way to ascertain that T has that type. Not sure how. - if !(T <: AbstractSpatialReaction) - error("The second argument must be a vector of AbstractSpatialReaction subtypes.") + if !(T <: AbstractSpatialReaction) + error("The second argument must be a vector of AbstractSpatialReaction subtypes.") end if isempty(spatial_reactions) spat_species = Vector{BasicSymbolic{Real}}[] else - spat_species = unique(reduce(vcat, [spatial_species(sr) for sr in spatial_reactions])) + spat_species = unique(reduce( + vcat, [spatial_species(sr) for sr in spatial_reactions])) end num_species = length(unique([species(rs); spat_species])) rs_edge_parameters = filter(isedgeparameter, parameters(rs)) if isempty(spatial_reactions) srs_edge_parameters = Vector{BasicSymbolic{Real}}[] else - srs_edge_parameters = setdiff(reduce(vcat, [parameters(sr) for sr in spatial_reactions]), parameters(rs)) + srs_edge_parameters = setdiff( + reduce(vcat, [parameters(sr) for sr in spatial_reactions]), parameters(rs)) end edge_parameters = unique([rs_edge_parameters; srs_edge_parameters]) vertex_parameters = filter(!isedgeparameter, parameters(rs)) # Ensures the parameter order begins similarly to in the non-spatial ReactionSystem. - ps = [parameters(rs); setdiff([edge_parameters; vertex_parameters], parameters(rs))] + ps = [parameters(rs); setdiff([edge_parameters; vertex_parameters], parameters(rs))] - foreach(sr -> check_spatial_reaction_validity(rs, sr; edge_parameters=edge_parameters), spatial_reactions) - return new{S,T}(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), num_species, - init_digraph, spat_species, ps, vertex_parameters, edge_parameters) + foreach( + sr -> check_spatial_reaction_validity(rs, sr; edge_parameters = edge_parameters), + spatial_reactions) + return new{S, T}( + rs, spatial_reactions, lattice, nv(lattice), ne(lattice), num_species, + init_digraph, spat_species, ps, vertex_parameters, edge_parameters) end end function LatticeReactionSystem(rs, srs, lat::SimpleGraph) @@ -88,4 +93,6 @@ edge_parameters(lrs::LatticeReactionSystem) = lrs.edge_parameters ModelingToolkit.nameof(lrs::LatticeReactionSystem) = nameof(lrs.rs) # Checks if a lattice reaction system is a pure (linear) transport reaction system. -is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReaction, lrs.spatial_reactions) +function is_transport_system(lrs::LatticeReactionSystem) + all(sr -> sr isa TransportReaction, lrs.spatial_reactions) +end diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index b967dd29fb..14f48fa53b 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -1,7 +1,7 @@ ### Spatial ODE Functor Structures ### # Functor with information for the forcing function of a spatial ODE with spatial movement on a lattice. -struct LatticeTransportODEf{Q,R,S,T} +struct LatticeTransportODEf{Q, R, S, T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" ofunc::Q """The number of vertices.""" @@ -15,7 +15,7 @@ struct LatticeTransportODEf{Q,R,S,T} at some point these have to be converted of a length num_verts vector. To avoid re-allocation they are written to this vector. """ - work_vert_ps::Vector{R} + work_vert_ps::Vector{R} """ For each parameter in vert_ps, its value is a vector with length either num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. @@ -45,28 +45,30 @@ struct LatticeTransportODEf{Q,R,S,T} Contain one vector for each edge parameter (length one if uniform, else one value for each edge). """ edge_ps::Vector{Vector{T}} - - function LatticeTransportODEf(ofunc::Q, vert_ps::Vector{Vector{R}}, transport_rates::Vector{Pair{Int64, Vector{R}}}, - edge_ps::Vector{Vector{T}}, lrs::LatticeReactionSystem) where {Q,R,T} + + function LatticeTransportODEf(ofunc::Q, vert_ps::Vector{Vector{R}}, + transport_rates::Vector{Pair{Int64, Vector{R}}}, + edge_ps::Vector{Vector{T}}, lrs::LatticeReactionSystem) where {Q, R, T} leaving_rates = zeros(length(transport_rates), lrs.num_verts) for (s_idx, trpair) in enumerate(transport_rates) rates = last(trpair) - for (e_idx, e) in enumerate(edges(lrs.lattice)) + for (e_idx, e) in enumerate(edges(lrs.lattice)) # Updates the exit rate for species s_idx from vertex e.src - leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) + leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) end end work_vert_ps = zeros(lrs.num_verts) # 1 if ps are constant across the graph, 0 else. v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) - eds = edges(lrs.lattice) - new{Q,R,typeof(eds),T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, - v_ps_idx_types, transport_rates, leaving_rates, eds, edge_ps) + eds = edges(lrs.lattice) + new{Q, R, typeof(eds), T}( + ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, + v_ps_idx_types, transport_rates, leaving_rates, eds, edge_ps) end end # Functor with information for the Jacobian function of a spatial ODE with spatial movement on a lattice. -struct LatticeTransportODEjac{Q,R,S,T} +struct LatticeTransportODEjac{Q, R, S, T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" ofunc::Q """The number of vertices.""" @@ -80,7 +82,7 @@ struct LatticeTransportODEjac{Q,R,S,T} at some point these have to be converted of a length(num_verts) vector. To avoid re-allocation they are written to this vector. """ - work_vert_ps::Vector{R} + work_vert_ps::Vector{R} """ For each parameter in vert_ps, it either have length num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, @@ -99,13 +101,14 @@ struct LatticeTransportODEjac{Q,R,S,T} """ edge_ps::Vector{Vector{T}} - function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, - jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, - edge_ps::Vector{Vector{T}}, sparse::Bool) where {R,S,T} + function LatticeTransportODEjac( + ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, + jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, + edge_ps::Vector{Vector{T}}, sparse::Bool) where {R, S, T} work_vert_ps = zeros(lrs.num_verts) v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) - new{R,S,typeof(jac_transport),T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, - work_vert_ps, v_ps_idx_types, sparse, jac_transport, edge_ps) + new{R, S, typeof(jac_transport), T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, + work_vert_ps, v_ps_idx_types, sparse, jac_transport, edge_ps) end end @@ -113,60 +116,64 @@ end # Creates an ODEProblem from a LatticeReactionSystem. function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, - p_in = DiffEqBase.NullParameters(), args...; - jac = false, sparse = false, - name = nameof(lrs), include_zero_odes = true, - combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), - remove_conserved = false, checks = false, kwargs...) + p_in = DiffEqBase.NullParameters(), args...; + jac = false, sparse = false, + name = nameof(lrs), include_zero_odes = true, + combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), + remove_conserved = false, checks = false, kwargs...) if !is_transport_system(lrs) error("Currently lattice ODE simulations are only supported when all spatial reactions are TransportReactions.") end - + # Converts potential symmaps to varmaps # Vertex and edge parameters may be given in a tuple, or in a common vector, making parameter case complicated. u0_in = symmap_to_varmap(lrs, u0_in) - p_in = (p_in isa Tuple{<:Any,<:Any}) ? - (symmap_to_varmap(lrs, p_in[1]),symmap_to_varmap(lrs, p_in[2])) : - symmap_to_varmap(lrs, p_in) + p_in = (p_in isa Tuple{<:Any, <:Any}) ? + (symmap_to_varmap(lrs, p_in[1]), symmap_to_varmap(lrs, p_in[2])) : + symmap_to_varmap(lrs, p_in) # Converts u0 and p to their internal forms. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. - u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) + u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. # These elements are length 1 vectors (if the parameter is uniform), # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). - vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + vert_ps, edge_ps = lattice_process_p( + p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Creates ODEProblem. - ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse, name, include_zero_odes, - combinatoric_ratelaws, remove_conserved, checks) - return ODEProblem(ofun, u0, tspan, vert_ps, args...; kwargs...) + ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse, name, include_zero_odes, + combinatoric_ratelaws, remove_conserved, checks) + return ODEProblem(ofun, u0, tspan, vert_ps, args...; kwargs...) end # Builds an ODEFunction for a spatial ODEProblem. function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T}}, - edge_ps::Vector{Vector{T}}, jac::Bool, sparse::Bool, - name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) where {T} - if remove_conserved + edge_ps::Vector{Vector{T}}, jac::Bool, sparse::Bool, + name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) where {T} + if remove_conserved error("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s") end # Creates a map, taking (the index in species(lrs) each species (with transportation) # to its transportation rate (uniform or one value for each edge). - transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) + transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) # Prepares the Jacobian and forcing functions (depending on jacobian and sparsity selection). - osys = complete(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks)) + osys = complete(convert( + ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks)) if jac # `build_jac_prototype` currently assumes a sparse (non-spatial) Jacobian. Hence compute this. # `LatticeTransportODEjac` currently assumes a dense (non-spatial) Jacobian. Hence compute this. # Long term we could write separate version of these functions for generic input. ofunc_dense = ODEFunction(osys; jac = true, sparse = false) ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) - jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) + jac_vals = build_jac_prototype( + ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) if sparse f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) - jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) + jac_vals = build_jac_prototype( + ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, edge_ps, true) jac_prototype = jac_vals else @@ -178,7 +185,8 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} if sparse ofunc_sparse = ODEFunction(osys; jac = false, sparse = true) f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) - jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = false) + jac_prototype = build_jac_prototype( + ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = false) else ofunc_dense = ODEFunction(osys; jac = false, sparse = false) f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, edge_ps, lrs) @@ -187,15 +195,17 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} J = nothing end - return ODEFunction(f; jac = J, jac_prototype = jac_prototype) + return ODEFunction(f; jac = J, jac_prototype = jac_prototype) end # Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. -function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, trans_rates, - lrs::LatticeReactionSystem; set_nonzero = false) +function build_jac_prototype( + ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, trans_rates, + lrs::LatticeReactionSystem; set_nonzero = false) # Finds the indexes of the transport species, and the species with transport only (and no non-spatial dynamics). trans_species = first.(trans_rates) - trans_only_species = filter(s_idx -> !Base.isstored(ns_jac_prototype, s_idx, s_idx), trans_species) + trans_only_species = filter( + s_idx -> !Base.isstored(ns_jac_prototype, s_idx, s_idx), trans_species) # Finds the indexes of all terms in the non-spatial jacobian. ns_jac_prototype_idxs = findnz(ns_jac_prototype) @@ -204,13 +214,13 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, # Prepares vectors to store i and j indexes of Jacobian entries. idx = 1 - num_entries = lrs.num_verts * length(ns_i_idxs) + + num_entries = lrs.num_verts * length(ns_i_idxs) + lrs.num_edges * (length(trans_only_species) + length(trans_species)) i_idxs = Vector{Int}(undef, num_entries) j_idxs = Vector{Int}(undef, num_entries) # Indexes of elements due to non-spatial dynamics. - for vert in 1:lrs.num_verts + for vert in 1:(lrs.num_verts) for n in 1:length(ns_i_idxs) i_idxs[idx] = get_index(vert, ns_i_idxs[n], lrs.num_species) j_idxs[idx] = get_index(vert, ns_j_idxs[n], lrs.num_species) @@ -244,11 +254,11 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, for (s, rates) in trans_rates, (e_idx, e) in enumerate(edges(lrs.lattice)) idx_src = get_index(e.src, s, lrs.num_species) idx_dst = get_index(e.dst, s, lrs.num_species) - val = get_component_value(rates, e_idx) + val = get_component_value(rates, e_idx) # Term due to species leaving source vertex. jac_prototype[idx_src, idx_src] -= val - + # Term due to species arriving to destination vertex. jac_prototype[idx_src, idx_dst] += val end @@ -263,19 +273,20 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) for vert_i in 1:(f_func.num_verts) # gets the indices of species at vertex i idxs = get_indexes(vert_i, f_func.num_species) - + # vector of vertex ps at vert_i - vert_i_ps = view_vert_ps_vector!(f_func.work_vert_ps, p, vert_i, enumerate(f_func.v_ps_idx_types)) - + vert_i_ps = view_vert_ps_vector!( + f_func.work_vert_ps, p, vert_i, enumerate(f_func.v_ps_idx_types)) + # evaluate reaction contributions to du at vert_i - f_func.ofunc((@view du[idxs]), (@view u[idxs]), vert_i_ps, t) + f_func.ofunc((@view du[idxs]), (@view u[idxs]), vert_i_ps, t) end # s_idx is species index among transport species, s is index among all species # rates are the species' transport rates - for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) + for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) # Rate for leaving vert_i - for vert_i in 1:(f_func.num_verts) + for vert_i in 1:(f_func.num_verts) idx = get_index(vert_i, s, f_func.num_species) du[idx] -= f_func.leaving_rates[s_idx, vert_i] * u[idx] end @@ -283,22 +294,23 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) for (e_idx, e) in enumerate(f_func.edges) idx_dst = get_index(e.dst, s, f_func.num_species) idx_src = get_index(e.src, s, f_func.num_species) - du[idx_dst] += get_component_value(rates, e_idx) * u[idx_src] + du[idx_dst] += get_component_value(rates, e_idx) * u[idx_src] end end end # Defines the jacobian functor's effect on the (spatial) ODE system. function (jac_func::LatticeTransportODEjac)(J, u, p, t) - J .= 0.0 + J .= 0.0 # Update the Jacobian from reaction terms for vert_i in 1:(jac_func.num_verts) idxs = get_indexes(vert_i, jac_func.num_species) - vert_ps = view_vert_ps_vector!(jac_func.work_vert_ps, p, vert_i, enumerate(jac_func.v_ps_idx_types)) + vert_ps = view_vert_ps_vector!( + jac_func.work_vert_ps, p, vert_i, enumerate(jac_func.v_ps_idx_types)) jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), vert_ps, t) end # Updates for the spatial reactions (adds the Jacobian values from the diffusion reactions). J .+= jac_func.jac_transport -end \ No newline at end of file +end diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 581bd7f735..daf3f48bf9 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -29,7 +29,8 @@ struct TransportReaction <: AbstractSpatialReaction # Creates a diffusion reaction. function TransportReaction(rate, species) - if any(!ModelingToolkit.isparameter(var) for var in ModelingToolkit.get_variables(rate)) + if any(!ModelingToolkit.isparameter(var) + for var in ModelingToolkit.get_variables(rate)) error("TransportReaction rate contains variables: $(filter(var -> !ModelingToolkit.isparameter(var), ModelingToolkit.get_variables(rate))). The rate must consist of parameters only.") end new(rate, species.val) @@ -77,31 +78,33 @@ ModelingToolkit.parameters(tr::TransportReaction) = Symbolics.get_variables(tr.r spatial_species(tr::TransportReaction) = [tr.species] # Checks that a transport reaction is valid for a given reaction system. -function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction; edge_parameters=[]) +function check_spatial_reaction_validity( + rs::ReactionSystem, tr::TransportReaction; edge_parameters = []) # Checks that the species exist in the reaction system. # (ODE simulation code becomes difficult if this is not required, # as non-spatial jacobian and f function generated from rs is of wrong size). - if !any(isequal(tr.species), species(rs)) + if !any(isequal(tr.species), species(rs)) error("Currently, species used in TransportReactions must have previously been declared within the non-spatial ReactionSystem. This is not the case for $(tr.species).") end # Checks that the rate does not depend on species. rate_vars = ModelingToolkit.getname.(Symbolics.get_variables(tr.rate)) - if !isempty(intersect(ModelingToolkit.getname.(species(rs)), rate_vars)) + if !isempty(intersect(ModelingToolkit.getname.(species(rs)), rate_vars)) error("The following species were used in rates of a transport reactions: $(setdiff(ModelingToolkit.getname.(species(rs)), rate_vars)).") end # Checks that the species does not exist in the system with different metadata. - if any(isequal(tr.species, s) && !isequivalent(tr.species, s) for s in species(rs)) + if any(isequal(tr.species, s) && !isequivalent(tr.species, s) for s in species(rs)) error("A transport reaction used a species, $(tr.species), with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") end - if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) - for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)) + if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) + for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)) error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") end # Checks that no edge parameter occur among rates of non-spatial reactions. - if any(!isempty(intersect(Symbolics.get_variables(r.rate), edge_parameters)) for r in reactions(rs)) + if any(!isempty(intersect(Symbolics.get_variables(r.rate), edge_parameters)) + for r in reactions(rs)) error("Edge paramter(s) were found as a rate of a non-spatial reaction.") end end @@ -111,8 +114,10 @@ end const ep_metadata = Catalyst.EdgeParameter => true function isequivalent(sym1, sym2) isequal(sym1, sym2) || (return false) - any((md1 != ep_metadata) && !(md1 in sym2.metadata) for md1 in sym1.metadata) && (return false) - any((md2 != ep_metadata) && !(md2 in sym1.metadata) for md2 in sym2.metadata) && (return false) + any((md1 != ep_metadata) && !(md1 in sym2.metadata) for md1 in sym1.metadata) && + (return false) + any((md2 != ep_metadata) && !(md2 in sym1.metadata) for md2 in sym2.metadata) && + (return false) (typeof(sym1) != typeof(sym2)) && (return false) return true end @@ -151,4 +156,4 @@ function find_parameters_in_rate!(parameters, rateex::ExprValues) end end nothing -end \ No newline at end of file +end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 691ba79a3c..d798d933f4 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -3,11 +3,11 @@ # Defines _symbol_to_var, but where the system is a LRS. Required to make symmapt_to_varmap to work. function _symbol_to_var(lrs::LatticeReactionSystem, sym) # Checks if sym is a parameter. - p_idx = findfirst(sym==p_sym for p_sym in ModelingToolkit.getname.(parameters(lrs))) + p_idx = findfirst(sym == p_sym for p_sym in ModelingToolkit.getname.(parameters(lrs))) isnothing(p_idx) || return parameters(lrs)[p_idx] # Checks if sym is a species. - s_idx = findfirst(sym==s_sym for s_sym in ModelingToolkit.getname.(species(lrs))) + s_idx = findfirst(sym == s_sym for s_sym in ModelingToolkit.getname.(species(lrs))) isnothing(s_idx) || return species(lrs)[s_idx] error("Could not find property parameter/species $sym in lattice reaction system.") @@ -19,13 +19,13 @@ function lattice_process_u0(u0_in, u0_syms, num_verts) # u0 values can be given in various forms. This converts it to a Vector{Vector{}} form. # Top-level vector: Contains one vector for each species. # Second-level vector: contain one value if species uniform across lattice, else one value for each vertex). - u0 = lattice_process_input(u0_in, u0_syms, num_verts) + u0 = lattice_process_input(u0_in, u0_syms, num_verts) # Perform various error checks on the (by the user provided) initial conditions. - check_vector_lengths(u0, length(u0_syms), num_verts) + check_vector_lengths(u0, length(u0_syms), num_verts) # Converts the Vector{Vector{}} format to a single Vector (with one values for each species and vertex). - expand_component_values(u0, num_verts) + expand_component_values(u0, num_verts) end # From p input, splits it into diffusion parameters and compartment parameters. @@ -33,21 +33,21 @@ end function lattice_process_p(p_in, p_vertex_syms, p_edge_syms, lrs::LatticeReactionSystem) # If the user provided parameters as a single map (mixing vertex and edge parameters): # Split into two separate vectors. - vert_ps_in, edge_ps_in = split_parameters(p_in, p_vertex_syms, p_edge_syms) + vert_ps_in, edge_ps_in = split_parameters(p_in, p_vertex_syms, p_edge_syms) # Parameter values can be given in various forms. This converts it to the Vector{Vector{}} form. - vert_ps = lattice_process_input(vert_ps_in, p_vertex_syms, lrs.num_verts) + vert_ps = lattice_process_input(vert_ps_in, p_vertex_syms, lrs.num_verts) # Parameter values can be given in various forms. This converts it to the Vector{Vector{}} form. - edge_ps = lattice_process_input(edge_ps_in, p_edge_syms, lrs.num_edges) + edge_ps = lattice_process_input(edge_ps_in, p_edge_syms, lrs.num_edges) # If the lattice defined as (N edge) undirected graph, and we provides N/2 values for some edge parameter: # Presume they want to expand that parameters value so it has the same value in both directions. - lrs.init_digraph || duplicate_trans_params!(edge_ps, lrs) - + lrs.init_digraph || duplicate_trans_params!(edge_ps, lrs) + # Perform various error checks on the (by the user provided) vertex and edge parameters. - check_vector_lengths(vert_ps, length(p_vertex_syms), lrs.num_verts) - check_vector_lengths(edge_ps, length(p_edge_syms), lrs.num_edges) + check_vector_lengths(vert_ps, length(p_vertex_syms), lrs.num_verts) + check_vector_lengths(edge_ps, length(p_edge_syms), lrs.num_edges) return vert_ps, edge_ps end @@ -55,35 +55,35 @@ end # Splits parameters into those for the vertexes and those for the edges. # If they are already split, return that. -split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps +split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps # Providing parameters to a spatial reaction system as a single vector of values (e.g. [1.0, 4.0, 0.1]) is not allowed. # Either use tuple (e.g. ([1.0, 4.0], [0.1])) or map format (e.g. [A => 1.0, B => 4.0, D => 0.1]). -function split_parameters(ps::Vector{<:Number}, args...) +function split_parameters(ps::Vector{<:Number}, args...) error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") end # Splitting is only done for Vectors of Pairs (where the first value is a Symbols, and the second a value). -function split_parameters(ps::Vector{<: Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) - vert_ps_in = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] - edge_ps_in = [p for p in ps if any(isequal(p[1]), p_edge_syms)] +function split_parameters(ps::Vector{<:Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) + vert_ps_in = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] + edge_ps_in = [p for p in ps if any(isequal(p[1]), p_edge_syms)] # Error check, in case some input parameters where neither recognised as vertex or edge parameters. if (sum(length.([vert_ps_in, edge_ps_in])) != length(ps)) - error("These input parameters are not recognised: $(setdiff(first.(ps), vcat(first.([vert_ps_in, edge_ps_in]))))") + error("These input parameters are not recognised: $(setdiff(first.(ps), vcat(first.([vert_ps_in, edge_ps_in]))))") end - + return vert_ps_in, edge_ps_in end # Input may have the following forms (after potential Symbol maps to Symbolic maps conversions): - # - A vector of values, where the i'th value corresponds to the value of the i'th - # initial condition value (for u0_in), vertex parameter value (for vert_ps_in), or edge parameter value (for edge_ps_in). - # - A vector of vectors of values. The same as previously, - # but here the species/parameter can have different values across the spatial structure. - # - A map of Symbols to values. These can either be a single value (if uniform across the spatial structure) - # or a vector (with different values for each vertex/edge). - # These can be mixed (e.g. [X => 1.0, Y => [1.0, 2.0, 3.0, 4.0]] is allowed). - # - A matrix. E.g. for initial conditions you can have a num_species * num_vertex matrix, - # indicating the value of each species at each vertex. +# - A vector of values, where the i'th value corresponds to the value of the i'th +# initial condition value (for u0_in), vertex parameter value (for vert_ps_in), or edge parameter value (for edge_ps_in). +# - A vector of vectors of values. The same as previously, +# but here the species/parameter can have different values across the spatial structure. +# - A map of Symbols to values. These can either be a single value (if uniform across the spatial structure) +# or a vector (with different values for each vertex/edge). +# These can be mixed (e.g. [X => 1.0, Y => [1.0, 2.0, 3.0, 4.0]] is allowed). +# - A matrix. E.g. for initial conditions you can have a num_species * num_vertex matrix, +# indicating the value of each species at each vertex. # The lattice_process_input function takes input initial conditions/vertex parameters/edge parameters # of whichever form the user have used, and converts them to the Vector{Vector{}} form used internally. @@ -94,8 +94,9 @@ end # If the input is given in a map form, the vector needs sorting and the first value removed. # The creates a Vector{Vector{Value}} or Vector{value} form, which is then again sent to lattice_process_input for reprocessing. -function lattice_process_input(input::Vector{<:Pair}, syms::Vector{BasicSymbolic{Real}}, args...) - if !isempty(setdiff(first.(input), syms)) +function lattice_process_input( + input::Vector{<:Pair}, syms::Vector{BasicSymbolic{Real}}, args...) + if !isempty(setdiff(first.(input), syms)) error("Some input symbols are not recognised: $(setdiff(first.(input), syms)).") end sorted_input = sort(input; by = p -> findfirst(isequal(p[1]), syms)) @@ -108,27 +109,30 @@ function lattice_process_input(input::Matrix{<:Number}, args...) end # Possibly we want to support this type of input at some point. function lattice_process_input(input::Array{<:Number, 3}, args...) - error("3 dimensional array parameter input currently not supported.") + error("3 dimensional array parameter input currently not supported.") end # If the input is a Vector containing both vectors and single values, converts it to the Vector{<:Vector} form. # Technically this last lattice_process_input is probably not needed. function lattice_process_input(input::Vector{<:Any}, args...) isempty(input) ? Vector{Vector{Float64}}() : lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], - args...) + args...) end # If the input is of the correct form already, return it. -lattice_process_input(input::Vector{<:Vector}, syms::Vector{BasicSymbolic{Real}}, n::Int64) = input +function lattice_process_input( + input::Vector{<:Vector}, syms::Vector{BasicSymbolic{Real}}, n::Int64) + input +end # Checks that a value vector have the right length, as well as that of all its sub vectors. # Error check if e.g. the user does not provide values for all species/parameters, # or for one: provides a vector of values, but that has the wrong length # (e.g providing 7 values for one species, but there are 8 vertexes). function check_vector_lengths(input::Vector{<:Vector}, n_syms, n_locations) - if (length(input)!=n_syms) + if (length(input) != n_syms) error("Missing values for some initial conditions/parameters. Expected $n_syms values, got $(length(input)).") end - if !isempty(setdiff(unique(length.(input)), [1, n_locations])) + if !isempty(setdiff(unique(length.(input)), [1, n_locations])) error("Some inputs where given values of inappropriate length.") end end @@ -138,33 +142,36 @@ end # If transport parameters are given with n values, we want to use the same value for both directions. # Since the order of edges in the new graph is non-trivial, this function # distributes the n input values to a 2n length vector, putting the correct value in each position. -function duplicate_trans_params!(edge_ps::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) - cum_adjacency_counts = [0;cumsum(length.(lrs.lattice.fadjlist[1:end-1]))] +function duplicate_trans_params!( + edge_ps::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) + cum_adjacency_counts = [0; cumsum(length.(lrs.lattice.fadjlist[1:(end - 1)]))] for idx in 1:length(edge_ps) # If the edge parameter already values for each directed edge, we can continue. (2length(edge_ps[idx]) == lrs.num_edges) || continue # - + # This entire thing depends on the fact that, in the edges(lattice) iterator, the edges are sorted by: # (1) Their source node # (2) Their destination node. # A vector where we will put the edge parameters new values. # Has the correct length (the number of directed edges in the lattice). - new_vals = Vector{Float64}(undef, lrs.num_edges) + new_vals = Vector{Float64}(undef, lrs.num_edges) # As we loop through the edges of the di-graph, this keeps track of each edge's index in the original graph. - original_edge_count = 0 + original_edge_count = 0 for edge in edges(lrs.lattice) # For each edge. # The digraph conversion only adds edges so that src > dst. - (edge.src < edge.dst) ? (original_edge_count += 1) : continue + (edge.src < edge.dst) ? (original_edge_count += 1) : continue # For original edge i -> j, finds the index of i -> j in DiGraph. - idx_fwd = cum_adjacency_counts[edge.src] + findfirst(isequal(edge.dst),lrs.lattice.fadjlist[edge.src]) + idx_fwd = cum_adjacency_counts[edge.src] + + findfirst(isequal(edge.dst), lrs.lattice.fadjlist[edge.src]) # For original edge i -> j, finds the index of j -> i in DiGraph. - idx_bwd = cum_adjacency_counts[edge.dst] + findfirst(isequal(edge.src),lrs.lattice.fadjlist[edge.dst]) + idx_bwd = cum_adjacency_counts[edge.dst] + + findfirst(isequal(edge.src), lrs.lattice.fadjlist[edge.dst]) new_vals[idx_fwd] = edge_ps[idx][original_edge_count] new_vals[idx_bwd] = edge_ps[idx][original_edge_count] end # Replaces the edge parameters values with the updated value vector. - edge_ps[idx] = new_vals + edge_ps[idx] = new_vals end end @@ -173,29 +180,31 @@ vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) # Produces a dictionary with all parameter values. function param_dict(vert_ps, edge_ps, lrs) merge(vals_to_dict(vertex_parameters(lrs), vert_ps), - vals_to_dict(edge_parameters(lrs), edge_ps)) + vals_to_dict(edge_parameters(lrs), edge_ps)) end # Computes the transport rates and stores them in a desired format # (a Dictionary from species index to rates across all edges). -function compute_all_transport_rates(vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) +function compute_all_transport_rates(vert_ps::Vector{Vector{Float64}}, + edge_ps::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) # Creates a dict, allowing us to access the values of wll parameters. - p_val_dict = param_dict(vert_ps, edge_ps, lrs) + p_val_dict = param_dict(vert_ps, edge_ps, lrs) # For all species with transportation, compute their transportation rate (across all edges). # This is a vector, pairing each species to these rates. - unsorted_rates = [s => compute_transport_rates(get_transport_rate_law(s, lrs), p_val_dict, lrs.num_edges) - for s in spatial_species(lrs)] - + unsorted_rates = [s => compute_transport_rates( + get_transport_rate_law(s, lrs), p_val_dict, lrs.num_edges) + for s in spatial_species(lrs)] + # Sorts all the species => rate pairs according to their species index in species(::ReactionSystem). - return sort(unsorted_rates; by=rate -> findfirst(isequal(rate[1]), species(lrs))) + return sort(unsorted_rates; by = rate -> findfirst(isequal(rate[1]), species(lrs))) end # For a species, retrieves the symbolic expression for its transportation rate # (likely only a single parameter, such as `D`, but could be e.g. L*D, where L and D are parameters). # We could allows several transportation reactions for one species and simply sum them though, easy change. function get_transport_rate_law(s::BasicSymbolic{Real}, lrs::LatticeReactionSystem) rates = filter(sr -> isequal(s, sr.species), lrs.spatial_reactions) - (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") + (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") return rates[1].rate end # For the numeric expression describing the rate of transport (likely only a single parameter, e.g. `D`), @@ -203,19 +212,21 @@ end # If all parameters the rate depend on are uniform all edges, this becomes a length 1 vector. # Else a vector with each value corresponding to the rate at one specific edge. function compute_transport_rates(rate_law::Num, - p_val_dict::Dict{SymbolicUtils.BasicSymbolic{Real}, Vector{Float64}}, num_edges::Int64) + p_val_dict::Dict{SymbolicUtils.BasicSymbolic{Real}, Vector{Float64}}, num_edges::Int64) # Finds parameters involved in rate and create a function evaluating the rate law. relevant_ps = Symbolics.get_variables(rate_law) - rate_law_func = drop_expr(@RuntimeGeneratedFunction(build_function(rate_law, relevant_ps...))) + rate_law_func = drop_expr(@RuntimeGeneratedFunction(build_function( + rate_law, relevant_ps...))) # If all these parameters are spatially uniform. `rates` becomes a vector with 1 value. - if all(length(p_val_dict[P]) == 1 for P in relevant_ps) + if all(length(p_val_dict[P]) == 1 for P in relevant_ps) return [rate_law_func([p_val_dict[p][1] for p in relevant_ps]...)] - # If at least on parameter the rate depends on have a value varying across all edges, - # we have to compute one rate value for each edge. + # If at least on parameter the rate depends on have a value varying across all edges, + # we have to compute one rate value for each edge. else - return [rate_law_func([get_component_value(p_val_dict[p], idxE) for p in relevant_ps]...) - for idxE in 1:num_edges] + return [rate_law_func([get_component_value(p_val_dict[p], idxE) + for p in relevant_ps]...) + for idxE in 1:num_edges] end end @@ -224,12 +235,14 @@ end # If the rate is uniform across all edges, the vector will be length 1 (with this value), # else there will be a separate value for each edge. # Pair{Int64, Vector{T}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). -function make_sidxs_to_transrate_map(vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{T}}, - lrs::LatticeReactionSystem) where T +function make_sidxs_to_transrate_map( + vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{T}}, + lrs::LatticeReactionSystem) where {T} transport_rates_speciesmap = compute_all_transport_rates(vert_ps, edge_ps, lrs) return Pair{Int64, Vector{T}}[ - speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] for spat_rates in transport_rates_speciesmap - ] + speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] + for spat_rates in transport_rates_speciesmap + ] end ### Accessing Unknown & Parameter Array Values ### @@ -237,7 +250,9 @@ end # Gets the index in the u array of species s in vertex vert (when their are num_species species). get_index(vert::Int64, s::Int64, num_species::Int64) = (vert - 1) * num_species + s # Gets the indexes in the u array of all species in vertex vert (when their are num_species species). -get_indexes(vert::Int64, num_species::Int64) = ((vert - 1) * num_species + 1):(vert * num_species) +function get_indexes(vert::Int64, num_species::Int64) + ((vert - 1) * num_species + 1):(vert * num_species) +end # For vectors of length 1 or n, we want to get value idx (or the one value, if length is 1). # This function gets that. Here: @@ -249,13 +264,13 @@ get_indexes(vert::Int64, num_species::Int64) = ((vert - 1) * num_species + 1):(v # - location_idx is the index of the vertex or edge for which we wish to access a initial condition or parameter values. # The first two function takes the full value vector, and call the function of at the components specific index. function get_component_value(values::Vector{<:Vector}, component_idx::Int64, - location_idx::Int64) + location_idx::Int64) get_component_value(values[component_idx], location_idx) end # Sometimes we have pre-computed, for each component, whether it's vector is length 1 or not. # This is stored in location_types. function get_component_value(values::Vector{<:Vector}, component_idx::Int64, - location_idx::Int64, location_types::Vector{Bool}) + location_idx::Int64, location_types::Vector{Bool}) get_component_value(values[component_idx], location_idx, location_types[component_idx]) end # For a components value (which is a vector of either length 1 or some other length), retrieves its value. @@ -264,8 +279,8 @@ function get_component_value(values::Vector{<:Number}, location_idx::Int64) end # Again, the location type (length of the value vector) may be pre-computed. function get_component_value(values::Vector{<:Number}, location_idx::Int64, - location_type::Bool) - location_type ? values[1] : values[location_idx] + location_type::Bool) + location_type ? values[1] : values[location_idx] end # Converts a vector of vectors to a long vector. @@ -281,11 +296,11 @@ end # Provides a work vector to which the converted vector is written. function view_vert_ps_vector!(work_vert_ps, vert_ps, comp, enumerated_vert_ps_idx_types) # Loops through all parameters. - for (idx,loc_type) in enumerated_vert_ps_idx_types + for (idx, loc_type) in enumerated_vert_ps_idx_types # If the parameter is uniform across the spatial structure, it will have a length-1 value vector # (which value we write to the work vector). # Else, we extract it value at the specific location. - work_vert_ps[idx] = (loc_type ? vert_ps[idx][1] : vert_ps[idx][comp]) + work_vert_ps[idx] = (loc_type ? vert_ps[idx][1] : vert_ps[idx][comp]) end return work_vert_ps end @@ -303,7 +318,7 @@ end function compute_edge_value(exp, lrs::LatticeReactionSystem, edge_ps) # Finds the symbols in the expression. Checks that all correspond to edge parameters. relevant_syms = Symbolics.get_variables(exp) - if !all(any(isequal(sym, p) for p in edge_parameters(lrs)) for sym in relevant_syms) + if !all(any(isequal(sym, p) for p in edge_parameters(lrs)) for sym in relevant_syms) error("An non-edge parameter was encountered in expressions: $exp. Here, only edge parameters are expected.") end @@ -316,18 +331,19 @@ function compute_edge_value(exp, lrs::LatticeReactionSystem, edge_ps) if !has_spatial_edge_component(exp, lrs, edge_ps) return [exp_func([sym_val_dict[sym][1] for sym in relevant_syms]...)] end - return [exp_func([get_component_value(sym_val_dict[sym], idxE) for sym in relevant_syms]...) - for idxE in 1:lrs.num_edges] + return [exp_func([get_component_value(sym_val_dict[sym], idxE) for sym in relevant_syms]...) + for idxE in 1:(lrs.num_edges)] end # For an expression, computes its values using the provided state and parameter vectors. # The expression is assumed to be valid in vertexes (and can have vertex parameter and state components). # If at least one component is non-uniform, output is a vector of length equal to the number of vertexes. # If all components are uniform, the output is a length one vector. -function compute_vertex_value(exp, lrs::LatticeReactionSystem; u=nothing, vert_ps=nothing) +function compute_vertex_value( + exp, lrs::LatticeReactionSystem; u = nothing, vert_ps = nothing) # Finds the symbols in the expression. Checks that all correspond to states or vertex parameters. relevant_syms = Symbolics.get_variables(exp) - if any(any(isequal(sym) in edge_parameters(lrs)) for sym in relevant_syms) + if any(any(isequal(sym) in edge_parameters(lrs)) for sym in relevant_syms) error("An edge parameter was encountered in expressions: $exp. Here, on vertex-based components are expected.") end # Creates a Function tha computes the expressions value for a parameter set. @@ -347,13 +363,13 @@ function compute_vertex_value(exp, lrs::LatticeReactionSystem; u=nothing, vert_p error("Either u or vertex_ps have to be provided to has_spatial_vertex_component.") end sym_val_dict = vals_to_dict(all_syms, all_vals) - + # If all values are uniform, compute value once. Else, do it at all edges. if !has_spatial_vertex_component(exp, lrs; u, vert_ps) return [exp_func([sym_val_dict[sym][1] for sym in relevant_syms]...)] end - return [exp_func([get_component_value(sym_val_dict[sym], idxV) for sym in relevant_syms]...) - for idxV in 1:lrs.num_verts] + return [exp_func([get_component_value(sym_val_dict[sym], idxV) for sym in relevant_syms]...) + for idxV in 1:(lrs.num_verts)] end ### System Property Checks ### @@ -364,14 +380,16 @@ function has_spatial_edge_component(exp, lrs::LatticeReactionSystem, edge_ps) # Finds the edge parameters in the expression. Computes their indexes. exp_syms = Symbolics.get_variables(exp) exp_edge_ps = filter(sym -> any(isequal(sym), edge_parameters(lrs)), exp_syms) - p_idxs = [findfirst(isequal(sym, edge_p) for edge_p in edge_parameters(lrs)) for sym in exp_syms] + p_idxs = [findfirst(isequal(sym, edge_p) for edge_p in edge_parameters(lrs)) + for sym in exp_syms] # Checks if any of the corresponding value vectors have length != 1 (that is, is not uniform). return any(length(edge_ps[p_idx]) != 1 for p_idx in p_idxs) end # For a Symbolic expression, a LatticeReactionSystem, and a parameter list of the internal format (vector of vectors): # Checks if any vertex parameter in the expression have a spatial component (that is, is not uniform). -function has_spatial_vertex_component(exp, lrs::LatticeReactionSystem; u=nothing, vert_ps=nothing) +function has_spatial_vertex_component( + exp, lrs::LatticeReactionSystem; u = nothing, vert_ps = nothing) # Finds all the symbols in the expression. exp_syms = Symbolics.get_variables(exp) @@ -390,4 +408,4 @@ function has_spatial_vertex_component(exp, lrs::LatticeReactionSystem; u=nothing end return false -end \ No newline at end of file +end diff --git a/src/steady_state_stability.jl b/src/steady_state_stability.jl index f8512116e4..8227d41179 100644 --- a/src/steady_state_stability.jl +++ b/src/steady_state_stability.jl @@ -44,10 +44,11 @@ these. Furthermore, Catalyst uses a tolerance `tol = 10*sqrt(eps())` to determin computed eigenvalue is far away enough from 0 to be reliably used. This selected threshold can be changed through the `tol` argument. ``` """ -function steady_state_stability(u::Vector, rs::ReactionSystem, ps; tol = 10*sqrt(eps(ss_val_type(u))), - ss_jac = steady_state_jac(rs; u0 = u)) +function steady_state_stability( + u::Vector, rs::ReactionSystem, ps; tol = 10 * sqrt(eps(ss_val_type(u))), + ss_jac = steady_state_jac(rs; u0 = u)) # Warning checks. - if !isautonomous(rs) + if !isautonomous(rs) error("Attempting to compute stability for a non-autonomous system (e.g. where some rate depend on $(rs.iv)). This is not possible.") end @@ -62,7 +63,7 @@ function steady_state_stability(u::Vector, rs::ReactionSystem, ps; tol = 10*sqrt J = zeros(length(u), length(u)) ss_jac = remake(ss_jac; u0 = u, p = ps) ss_jac.f.jac(J, ss_jac.u0, ss_jac.p, Inf) - + # Computes stability (by checking that the real part of all eigenvalues is negative). max_eig = maximum(real(ev) for ev in eigvals(J)) if abs(max_eig) < tol @@ -74,8 +75,8 @@ end # Used to determine the type of the steady states values, which is then used to set the tolerance's # type. ss_val_type(u::Vector{T}) where {T} = T -ss_val_type(u::Vector{Pair{S,T}}) where {S,T} = T -ss_val_type(u::Dict{S,T}) where {S,T} = T +ss_val_type(u::Vector{Pair{S, T}}) where {S, T} = T +ss_val_type(u::Dict{S, T}) where {S, T} = T """ steady_state_jac(rs::ReactionSystem; u0 = []) @@ -107,8 +108,8 @@ Notes: such a way that it can be used by the `steady_state_stability` function. ``` """ -function steady_state_jac(rs::ReactionSystem; u0 = [sp => 0.0 for sp in unknowns(rs)], - combinatoric_ratelaws = get_combinatoric_ratelaws(rs)) +function steady_state_jac(rs::ReactionSystem; u0 = [sp => 0.0 for sp in unknowns(rs)], + combinatoric_ratelaws = get_combinatoric_ratelaws(rs)) # If u0 is a vector of values, must be converted to something MTK understands. # Converts u0 to values MTK understands, and checks that potential conservation laws are accounted for. @@ -117,15 +118,15 @@ function steady_state_jac(rs::ReactionSystem; u0 = [sp => 0.0 for sp in unknowns # Creates an `ODEProblem` with a Jacobian. Dummy values for `u0` and `ps` must be provided. ps = [p => 0.0 for p in parameters(rs)] - return ODEProblem(rs, u0, 0, ps; jac = true, remove_conserved = true, - combinatoric_ratelaws = combinatoric_ratelaws) + return ODEProblem(rs, u0, 0, ps; jac = true, remove_conserved = true, + combinatoric_ratelaws = combinatoric_ratelaws) end # Converts a `u` vector from a vector of values to a map. function steady_state_u_conversion(u, rs::ReactionSystem) if (u isa Vector{<:Number}) if length(u) == length(unknowns(rs)) - u = [sp => v for (sp,v) in zip(unknowns(rs), u)] + u = [sp => v for (sp, v) in zip(unknowns(rs), u)] else error("You are trying to generate a stability Jacobian, providing u0 to compute conservation laws. Your provided u0 vector has length < the number of system states. If you provide a u0 vector, these have to be identical.") end From 035e0592834dfcd2b8b85488fbb363f141e4965d Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 11:45:24 -0400 Subject: [PATCH 187/446] init --- docs/pages.jl | 34 ++++--------------- .../introduction_to_catalyst.md | 2 +- .../behaviour_optimisation.md | 2 +- .../global_sensitivity_analysis.md | 2 +- docs/src/model_creation/dsl_advanced.md | 8 ++--- docs/src/model_creation/dsl_basics.md | 4 +-- .../examples/basic_CRN_library.md | 2 +- .../model_file_loading_and_export.md | 2 +- .../simulation_structure_interfacing.md | 2 +- .../nonlinear_solve.md | 2 +- 10 files changed, 20 insertions(+), 40 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index bb0de4f678..69c81e3216 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -2,21 +2,18 @@ pages = Any[ "Home" => "index.md", "Introduction to Catalyst" => Any[ "introduction_to_catalyst/catalyst_for_new_julia_users.md", - # "introduction_to_catalyst/introduction_to_catalyst.md" - # Advanced introduction. + "introduction_to_catalyst/introduction_to_catalyst.md" ], "Model Creation and Properties" => Any[ "model_creation/dsl_basics.md", "model_creation/dsl_advanced.md", - #"model_creation/programmatic_CRN_construction.md", + "model_creation/programmatic_CRN_construction.md", "model_creation/compositional_modeling.md", - #"model_creation/constraint_equations.md", - # Events. - "model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions. - "model_creation/model_file_loading_and_export.md",# Distributed parameters, rates, and initial conditions. - # Loading and writing models to files. + "model_creation/constraint_equations.md", + "model_creation/parametric_stoichiometry.md", + "model_creation/model_file_loading_and_export.md", "model_creation/model_visualisation.md", - #"model_creation/network_analysis.md", + "model_creation/network_analysis.md", "model_creation/chemistry_related_functionality.md", "Model creation examples" => Any[ "model_creation/examples/basic_CRN_library.md", @@ -30,11 +27,7 @@ pages = Any[ "model_simulation/simulation_plotting.md", "model_simulation/simulation_structure_interfacing.md", "model_simulation/ensemble_simulations.md", - # Stochastic simulation statistical analysis. "model_simulation/ode_simulation_performance.md", - # SDE Performance considerations/advice. - # Jump Performance considerations/advice. - # Finite state projection ], "Steady state analysis" => Any[ "steady_state_functionality/homotopy_continuation.md", @@ -44,28 +37,15 @@ pages = Any[ "steady_state_functionality/dynamical_systems.md" ], "Inverse Problems" => Any[ - # Inverse problems introduction. "inverse_problems/optimization_ode_param_fitting.md", # "inverse_problems/petab_ode_param_fitting.md", - # ODE parameter fitting using Turing. - # SDE/Jump fitting. "inverse_problems/behaviour_optimisation.md", - "inverse_problems/structural_identifiability.md", # Broken on Julia v1.10.3, requires v1.10.2 or 1.10.4. - # Practical identifiability. + "inverse_problems/structural_identifiability.md", "inverse_problems/global_sensitivity_analysis.md", "Inverse problem examples" => Any[ "inverse_problems/examples/ode_fitting_oscillation.md" ] ], - "Spatial modelling" => Any[ - # Intro. - # Lattice ODEs. - # Lattice Jumps. - ], - # "Developer Documentation" => Any[ - # # Contributor's guide. - # # Repository structure. - # ], "FAQs" => "faqs.md", "API" => "api.md" ] diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index c0a36a5754..40c2900988 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -285,7 +285,7 @@ For details on what information can be specified via the DSL see the [The Reaction DSL](@ref dsl_description) tutorial. --- -## Reaction rate laws used in simulations +## [Reaction rate laws used in simulations](@id introduction_to_catalyst_ratelaws) In generating mathematical models from a [`ReactionSystem`](@ref), reaction rates are treated as *microscopic* rates. That is, for a general mass action reaction of the form $n_1 S_1 + n_2 S_2 + \dots n_M S_M \to \dots$ with diff --git a/docs/src/inverse_problems/behaviour_optimisation.md b/docs/src/inverse_problems/behaviour_optimisation.md index a555037119..c3b879d33d 100644 --- a/docs/src/inverse_problems/behaviour_optimisation.md +++ b/docs/src/inverse_problems/behaviour_optimisation.md @@ -4,7 +4,7 @@ In previous tutorials we have described how to use [PEtab.jl](@ref petab_paramet ## [Maximising the pulse amplitude of an incoherent feed forward loop](@id behaviour_optimisation_IFFL_example) Incoherent feedforward loops (network motifs where a single component both activates and deactivates a downstream component) are able to generate pulses in response to step inputs[^2]. In this tutorial we will consider such an incoherent feedforward loop, attempting to generate a system with as prominent a response pulse as possible. -Our model consists of 3 species: $X$ (the input node), $Y$ (an intermediary), and $Z$ (the output node). In it, $X$ activates the production of both $Y$ and $Z$, with $Y$ also deactivating $Z$. When $X$ is activated, there will be a brief time window where $Y$ is still inactive, and $Z$ is activated. However, as $Y$ becomes active, it will turn $Z$ off. This creates a pulse of $Z$ activity. To trigger the system, we create [an event](@ref ref), which increases the production rate of $X$ ($pX$) by a factor of $10$ at time $t = 10$. +Our model consists of 3 species: $X$ (the input node), $Y$ (an intermediary), and $Z$ (the output node). In it, $X$ activates the production of both $Y$ and $Z$, with $Y$ also deactivating $Z$. When $X$ is activated, there will be a brief time window where $Y$ is still inactive, and $Z$ is activated. However, as $Y$ becomes active, it will turn $Z$ off. This creates a pulse of $Z$ activity. To trigger the system, we create [an event](@ref constraint_equations_events), which increases the production rate of $X$ ($pX$) by a factor of $10$ at time $t = 10$. ```@example behaviour_optimization using Catalyst incoherent_feed_forward = @reaction_network begin diff --git a/docs/src/inverse_problems/global_sensitivity_analysis.md b/docs/src/inverse_problems/global_sensitivity_analysis.md index ee20b6a802..91019e2383 100644 --- a/docs/src/inverse_problems/global_sensitivity_analysis.md +++ b/docs/src/inverse_problems/global_sensitivity_analysis.md @@ -8,7 +8,7 @@ GSA can be carried out using the [GlobalSensitivity.jl](https://github.com/SciML ### [Global vs local sensitivity](@id global_sensitivity_analysis_global_vs_local_sensitivity) A related concept to global sensitivity is *local sensitivity*. This, rather than measuring a function's sensitivity (with regards to its inputs) across its entire (or large part of its) domain, measures it at a specific point. This is equivalent to computing the function's gradients at a specific point in phase space, which is an important routine for most gradient-based optimisation methods (typically carried out through [*automatic differentiation*](https://en.wikipedia.org/wiki/Automatic_differentiation)). For most Catalyst-related functionalities, local sensitivities are computed using the [SciMLSensitivity.jl](https://github.com/SciML/SciMLSensitivity.jl) package. While certain GSA methods can utilise local sensitivities, this is not necessarily the case. -While local sensitivities are primarily used as a subroutine of other methodologies (such as optimisation schemes), it also has direct uses. E.g., in the context of fitting parameters to data, local sensitivity analysis can be used to, at the parameter set of the optimal fit, [determine the cost function's sensitivity to the system parameters](@ref ref). +While local sensitivities are primarily used as a subroutine of other methodologies (such as optimisation schemes), it also has direct uses. E.g., in the context of fitting parameters to data, local sensitivity analysis can be used to, at the parameter set of the optimal fit, determine the cost function's sensitivity to the system parameters. ## [Basic example](@id global_sensitivity_analysis_basic_example) We will consider a simple [SEIR model of an infectious disease](https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology). This is an expansion of the classic [SIR model](@ref basic_CRN_library_sir) with an additional *exposed* state, $E$, denoting individuals who are latently infected but currently unable to transmit their infection to others. diff --git a/docs/src/model_creation/dsl_advanced.md b/docs/src/model_creation/dsl_advanced.md index 1369efe04e..f2d1d16bf5 100644 --- a/docs/src/model_creation/dsl_advanced.md +++ b/docs/src/model_creation/dsl_advanced.md @@ -86,7 +86,7 @@ Generally, there are four main reasons for specifying species/parameters using t 4. To designate a species or parameters that do not occur in reactions, but are still part of the model (e.g a [parametric initial condition](@ref dsl_advanced_options_parametric_initial_conditions)) !!! warn - Catalyst's DSL automatically infer species and parameters from the input. However, it only does so for *quantities that appear in reactions*. Until now this has not been relevant. However, this tutorial will demonstrate cases where species/parameters that are not part of reactions are used. These *must* be designated using either the `@species` or `@parameters` options (or the `@variables` option, which is described [later](@ref ref)). + Catalyst's DSL automatically infer species and parameters from the input. However, it only does so for *quantities that appear in reactions*. Until now this has not been relevant. However, this tutorial will demonstrate cases where species/parameters that are not part of reactions are used. These *must* be designated using either the `@species` or `@parameters` options (or the `@variables` option, which is described [later](@ref constraint_equations)). ### [Setting default values for species and parameters](@id dsl_advanced_options_default_vals) When declaring species/parameters using the `@species` and `@parameters` options, one can also assign them default values (by appending them with `=` followed by the desired default value). E.g here we set `X`'s default initial condition value to $1.0$, and `p` and `d`'s default values to $1.0$ and $0.2$, respectively: @@ -207,7 +207,7 @@ It is not possible for the user to directly designate their own metadata. These ### [Designating constant-valued/fixed species parameters](@id dsl_advanced_options_constant_species) -Catalyst enables the designation of parameters as `constantspecies`. These parameters can be used as species in reactions, however, their values are not changed by the reaction and remain constant throughout the simulation (unless changed by e.g. the [occurrence of an event]@ref ref). Practically, this is done by setting the parameter's `isconstantspecies` metadata to `true`. Here, we create a simple reaction where the species `X` is converted to `Xᴾ` at rate `k`. By designating `X` as a constant species parameter, we ensure that its quantity is unchanged by the occurrence of the reaction. +Catalyst enables the designation of parameters as `constantspecies`. These parameters can be used as species in reactions, however, their values are not changed by the reaction and remain constant throughout the simulation (unless changed by e.g. the [occurrence of an event]@ref constraint_equations_events). Practically, this is done by setting the parameter's `isconstantspecies` metadata to `true`. Here, we create a simple reaction where the species `X` is converted to `Xᴾ` at rate `k`. By designating `X` as a constant species parameter, we ensure that its quantity is unchanged by the occurrence of the reaction. ```@example dsl_advanced_constant_species using Catalyst # hide rn = @reaction_network begin @@ -357,7 +357,7 @@ plot!(ylimit = (minimum(sol[:Xtot])*0.95, maximum(sol[:Xtot])*1.05)) # hide ``` to plot the observables (rather than the species). -Observables can be defined using complicated expressions containing species, parameters, and [variables](@ref ref) (but not other observables). In the following example (which uses a [parametric stoichiometry](@ref dsl_description_stoichiometries_parameters)) `X` polymerises to form a complex `Xn` containing `n` copies of `X`. Here, we create an observable describing the total number of `X` molecules in the system: +Observables can be defined using complicated expressions containing species, parameters, and [variables](@ref constraint_equations) (but not other observables). In the following example (which uses a [parametric stoichiometry](@ref dsl_description_stoichiometries_parameters)) `X` polymerises to form a complex `Xn` containing `n` copies of `X`. Here, we create an observable describing the total number of `X` molecules in the system: ```@example dsl_advanced_observables rn = @reaction_network begin @observables Xtot ~ X + n*Xn @@ -377,7 +377,7 @@ end nothing # hide ``` -Observables are by default considered [variables](@ref ref) (not species). To designate them as a species, they can be pre-declared using the `@species` option. I.e. Here `Xtot` becomes a species: +Observables are by default considered [variables](@ref constraint_equations) (not species). To designate them as a species, they can be pre-declared using the `@species` option. I.e. Here `Xtot` becomes a species: ```@example dsl_advanced_observables rn = @reaction_network begin @species Xtot(t) diff --git a/docs/src/model_creation/dsl_basics.md b/docs/src/model_creation/dsl_basics.md index 8f8029585f..a17e13a976 100644 --- a/docs/src/model_creation/dsl_basics.md +++ b/docs/src/model_creation/dsl_basics.md @@ -1,7 +1,7 @@ # [The Catalyst DSL - Introduction](@id dsl_description) In the [introduction to Catalyst](@ref introduction_to_catalyst) we described how the `@reaction_network` [macro](https://docs.julialang.org/en/v1/manual/metaprogramming/#man-macros) can be used to create chemical reaction network (CRN) models. This macro enables a so-called [domain-specific language](https://en.wikipedia.org/wiki/Domain-specific_language) (DSL) for creating CRN models. This tutorial will give a basic introduction on how to create Catalyst models using this macro (from now onwards called "*the Catalyst DSL*"). A [follow-up tutorial](@ref dsl_advanced_options) will describe some of the DSL's more advanced features. -The Catalyst DSL generates a [`ReactionSystem`](@ref) (the [julia structure](https://docs.julialang.org/en/v1/manual/types/#Composite-Types) Catalyst uses to represent CRN models). These can be created through alternative methods (e.g. [programmatically](@ref programmatic_CRN_construction) or [compositionally](@ref compositional_modeling)). A summary of the various ways to create `ReactionSystems`s can be found [here](@ref ref). [Previous](@ref ref) and [following](@ref simulation_intro) tutorials describe how to simulate models once they have been created using the DSL. This tutorial will solely focus on model creation. +The Catalyst DSL generates a [`ReactionSystem`](@ref) (the [julia structure](https://docs.julialang.org/en/v1/manual/types/#Composite-Types) Catalyst uses to represent CRN models). These can be created through alternative methods (e.g. [programmatically](@ref programmatic_CRN_construction) or [compositionally](@ref compositional_modeling)). A summary of the various ways to create `ReactionSystems`s can be found [here](@ref ref). [Previous](@ref introduction_to_catalyst) and [following](@ref simulation_intro) tutorials describe how to simulate models once they have been created using the DSL. This tutorial will solely focus on model creation. Before we begin, we will first load the Catalyst package (which is required to run the code). ```@example dsl_basics_intro @@ -88,7 +88,7 @@ rn5 = @reaction_network begin kD, X2 --> 2X end ``` -Reactants whose stoichiometries are not defined are assumed to have stoichiometry `1`. Any integer number can be used, furthermore, [decimal numbers and parameters can also be used as stoichiometries](@ref dsl_description_stoichiometries). A discussion of non-unitary (i.e. not equal to `1`) stoichiometries affecting the created model can be found [here](@ref ref). +Reactants whose stoichiometries are not defined are assumed to have stoichiometry `1`. Any integer number can be used, furthermore, [decimal numbers and parameters can also be used as stoichiometries](@ref dsl_description_stoichiometries). A discussion of non-unitary (i.e. not equal to `1`) stoichiometries affecting the created model can be found [here](@ref introduction_to_catalyst_ratelaws). Stoichiometries can be combined with `()` to define them for multiple reactants. Here, the following (mock) model declares the same reaction twice, both with and without this notation: ```@example dsl_basics diff --git a/docs/src/model_creation/examples/basic_CRN_library.md b/docs/src/model_creation/examples/basic_CRN_library.md index 4955bae3a6..3ac3a8d5d5 100644 --- a/docs/src/model_creation/examples/basic_CRN_library.md +++ b/docs/src/model_creation/examples/basic_CRN_library.md @@ -265,7 +265,7 @@ brusselator = @reaction_network begin 1, X --> ∅ end ``` -It is generally known to (for reaction rate equation-based ODE simulations) produce oscillations when $B > 1 + A^2$. However, this result is based on models generated when *combinatorial adjustment of rates is not performed*. Since Catalyst [automatically perform these adjustments](@ref ref), and one reaction contains a stoichiometric constant $>1$, the threshold will be different. Here, we trial two different values of $B$. In both cases, $B < 1 + A^2$, however, in the second case the system can generate oscillations. +It is generally known to (for reaction rate equation-based ODE simulations) produce oscillations when $B > 1 + A^2$. However, this result is based on models generated when *combinatorial adjustment of rates is not performed*. Since Catalyst [automatically perform these adjustments](@ref introduction_to_catalyst_ratelaws), and one reaction contains a stoichiometric constant $>1$, the threshold will be different. Here, we trial two different values of $B$. In both cases, $B < 1 + A^2$, however, in the second case the system can generate oscillations. ```@example crn_library_brusselator using OrdinaryDiffEq, Plots u0 = [:X => 1.0, :Y => 1.0] diff --git a/docs/src/model_creation/model_file_loading_and_export.md b/docs/src/model_creation/model_file_loading_and_export.md index bcd86167d8..813ae5f401 100644 --- a/docs/src/model_creation/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -52,7 +52,7 @@ complete(rs) end ``` !!! note - The code that `save_reactionsystem` prints uses [programmatic modelling](@ref ref) to generate the written model. + The code that `save_reactionsystem` prints uses [programmatic modelling](@ref programmatic_CRN_construction) to generate the written model. In addition to transferring models between Julia sessions, the `save_reactionsystem` function can also be used or print a model to a text file where you can easily inspect its components. diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index aa70d51ed4..7acc660eca 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -21,7 +21,7 @@ oprob = ODEProblem(cc_system, u0, tspan, ps) nothing # hide ``` -We can find a species's (or [variable's](@ref ref)) initial condition value by simply indexing with the species of interest as input. Here we check the initial condition value of $C$: +We can find a species's (or [variable's](@ref constraint_equations)) initial condition value by simply indexing with the species of interest as input. Here we check the initial condition value of $C$: ```@example structure_indexing oprob[:C] ``` diff --git a/docs/src/steady_state_functionality/nonlinear_solve.md b/docs/src/steady_state_functionality/nonlinear_solve.md index 3a97392fdf..c3df99d484 100644 --- a/docs/src/steady_state_functionality/nonlinear_solve.md +++ b/docs/src/steady_state_functionality/nonlinear_solve.md @@ -1,6 +1,6 @@ # [Finding Steady States using NonlinearSolve.jl and SteadyStateDiffEq.jl](@id steady_state_solving) -Catalyst `ReactionSystem` models can be converted to ODEs (through [the reaction rate equation](@ref ref)). We have previously described how these ODEs' steady states can be found through [homotopy continuation](@ref homotopy_continuation). Generally, homotopy continuation (due to its ability to find *all* of a system's steady states) is the preferred approach. However, Catalyst supports two additional approaches for finding steady states: +Catalyst `ReactionSystem` models can be converted to ODEs (through [the reaction rate equation](@ref introduction_to_catalyst_ratelaws)). We have previously described how these ODEs' steady states can be found through [homotopy continuation](@ref homotopy_continuation). Generally, homotopy continuation (due to its ability to find *all* of a system's steady states) is the preferred approach. However, Catalyst supports two additional approaches for finding steady states: - Through solving the nonlinear system produced by setting all ODE differentials to 0. - Through forward ODE simulation from an initial condition until a steady state has been reached. From 99a9753c32ed887e5237534d6d612be2c71ef187 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 12:12:51 -0400 Subject: [PATCH 188/446] init --- src/reaction.jl | 6 ++ test/reactionsystem_core/reaction.jl | 87 ++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/src/reaction.jl b/src/reaction.jl index 23538214aa..7f82f1376d 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -163,6 +163,12 @@ end function Reaction(rate, subs, prods, substoich, prodstoich; netstoich = nothing, metadata = Pair{Symbol, Any}[], only_use_rate = metadata_only_use_rate_check(metadata), kwargs...) + # Handles empty/nothing vectors. + isnothing(subs) || isempty(subs) && (subs = nothing) + isnothing(prods) || isempty(prods) && (prods = nothing) + isnothing(substoich) || isempty(substoich) && (substoich = nothing) + isnothing(prodstoich) || isempty(prodstoich) && (prodstoich = nothing) + (isnothing(prods) && isnothing(subs)) && throw(ArgumentError("A reaction requires a non-nothing substrate or product vector.")) (isnothing(prodstoich) && isnothing(substoich)) && diff --git a/test/reactionsystem_core/reaction.jl b/test/reactionsystem_core/reaction.jl index 4760641c4b..e70544d0fb 100644 --- a/test/reactionsystem_core/reaction.jl +++ b/test/reactionsystem_core/reaction.jl @@ -8,6 +8,89 @@ using ModelingToolkit: value, get_variables! # Sets the default `t` to use. t = default_t() +### Reaction Constructor Tests ### + +# Checks that `Reaction`s can be successfully created using various complicated inputs. +# Checks that the `Reaction`s have the correct type, and the correct net stoichiometries are generated. +let + # Declare symbolic variables. + @parameters k n1 n2::Int32 x [isconstantspecies=true] + @species X(t) Y(t) Z(t) + @variables A(t) + + # Tries for different types of rates (should not matter). + for rate in (k, k*A, 2, 3.0, 4//3) + # Creates `Reaction`s. + rx1 = Reaction(rate, [X], []) + rx2 = Reaction(rate, [x], [Y], [1.5], [1]) + rx3 = Reaction(rate, [x, X], [], [n1 + n2, n2], []) + rx4 = Reaction(rate, [X, Y], [X, Y, Z], [2//3, 3], [1//3, 1, 2]) + rx5 = Reaction(rate, [X, Y], [X, Y, Z], [2, 3], [1, n1, n2]) + rx6 = Reaction(rate, [X], [x], [n1], [1]) + + # Check `Reaction` types. + @test rx1 isa Reaction{Any,Int64} + @test rx2 isa Reaction{Any,Float64} + @test rx3 isa Reaction{Any,Any} + @test rx4 isa Reaction{Any,Rational{Int64}} + @test rx5 isa Reaction{Any,Any} + @test rx6 isa Reaction{Any,Any} + + # Check `Reaction` net stoichiometries. + issetequal(rx1.netstoich, [X => -1]) + issetequal(rx2.netstoich, [x => -1.5, Y => 1.0]) + issetequal(rx3.netstoich, [x => -n1 - n2, X => -n2]) + issetequal(rx4.netstoich, [X => -1//3, Y => -2//1, Z => 2//1]) + issetequal(rx5.netstoich, [X => -1, Y => n1 - 3, Z => n2]) + issetequal(rx6.netstoich, [X => -n1, x => 1]) + end +end + +# Tests that various `Reaction` constructors gives identical inputs. +let + # Declare symbolic variables. + @parameters k n1 n2::Int32 + @species X(t) Y(t) Z(t) + @variables A(t) + + # Tests that the three-argument constructor generates correct result. + @test Reaction(k*A, [X], [Y, Z]) == Reaction(k*A, [X], [Y, Z], [1], [1, 1]) + + # Tests that `[]` and `nothing` can be used interchangeably. + @test Reaction(k*A, [X, Z], nothing) == Reaction(k*A, [X, Z], []) + @test Reaction(k*A, nothing, [Y, Z]) == Reaction(k*A, [], [Y, Z]) + @test Reaction(k*A, [X, Z], nothing, [n1 + n2, 2], nothing) == Reaction(k*A, [X, Z], [], [n1 + n2, 2], []) + @test Reaction(k*A, nothing, [Y, Z], nothing, [n1 + n2, 2]) == Reaction(k*A, [], [Y, Z], [], [n1 + n2, 2]) +end + +# Tests that various incorrect inputs yields errors. +let + # Declare symbolic variables. + @parameters k n1 n2::Int32 + @species X(t) Y(t) Z(t) + @variables A(t) + + # Neither substrates nor products. + @test_throws ArgumentError Reaction(k*A, [], []) + + # Substrate vector not of equal length to substrate stoichiometry vector. + @test_throws ArgumentError Reaction(k*A, [X, X, Z], [], [1, 2], []) + + # Product vector not of equal length to product stoichiometry vector. + @test_throws ArgumentError Reaction(k*A, [], [X, X, Z], [], [1, 2]) + + # Repeated substrates. + @test_throws ArgumentError Reaction(k*A, [X, X, Z], []) + + # Repeated products. + @test_throws ArgumentError Reaction(k*A, [], [Y, Z, Z]) + + # Non-valid reactants (parameter or variable). + @test_throws ArgumentError Reaction(k*A, [], [A]) + @test_throws ArgumentError Reaction(k*A, [], [k]) +end + + ### Test Basic Accessors ### # Tests the `get_variables` function. @@ -42,7 +125,6 @@ end # Tests basic accessor functions. # Tests that repeated metadata entries are not permitted. let - @variables t @parameters k @species X(t) X2(t) @@ -60,7 +142,6 @@ end # Tests accessors for system without metadata. let - @variables t @parameters k @species X(t) X2(t) @@ -77,7 +158,6 @@ end # Tests basic accessor functions. # Tests various metadata types. let - @variables t @parameters k @species X(t) X2(t) @@ -109,7 +189,6 @@ end # Tests the noise scaling metadata. let - @variables t @parameters k η @species X(t) X2(t) From ffb24c45bd577f4b1e7117f7bc8e2819e0ff3cf1 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Fri, 7 Jun 2024 13:28:17 -0400 Subject: [PATCH 189/446] update Smoluchowski tutorial --- docs/pages.jl | 4 +- .../smoluchowski_coagulation_equation.md | 78 +++++++++++-------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index 69c81e3216..ea66c7e088 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -19,7 +19,7 @@ pages = Any[ "model_creation/examples/basic_CRN_library.md", "model_creation/examples/programmatic_generative_linear_pathway.md", "model_creation/examples/hodgkin_huxley_equation.md", - #"model_creation/examples/smoluchowski_coagulation_equation.md" + "model_creation/examples/smoluchowski_coagulation_equation.md" ] ], "Model simulation" => Any[ @@ -32,7 +32,7 @@ pages = Any[ "Steady state analysis" => Any[ "steady_state_functionality/homotopy_continuation.md", "steady_state_functionality/nonlinear_solve.md", - "steady_state_functionality/steady_state_stability_computation.md", + "steady_state_functionality/steady_state_stability_computation.md", "steady_state_functionality/bifurcation_diagrams.md", "steady_state_functionality/dynamical_systems.md" ], diff --git a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md index 8e9dd34ecb..a226da718e 100644 --- a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md +++ b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md @@ -6,47 +6,58 @@ The Smoluchowski coagulation equation describes a system of reactions in which m We begin by importing some necessary packages. ```julia using ModelingToolkit, Catalyst, LinearAlgebra -using DiffEqBase, JumpProcesses +using JumpProcesses using Plots, SpecialFunctions ``` 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 ```julia -## Parameter -N = 10 # maximum cluster size -Vₒ = (4π/3)*(10e-06*100)^3 # volume of a monomers in cm³ -Nₒ = 1e-06/Vₒ # initial conc. = (No. of init. monomers) / bulk volume -uₒ = 10000 # No. of monomers initially -V = uₒ/Nₒ # Bulk volume of system in cm³ - -integ(x) = Int(floor(x)) -n = integ(N/2) -nr = N%2 == 0 ? (n*(n + 1) - n) : (n*(n + 1)) # No. of forward reactions +# maximum cluster size +N = 10 + +# volume of a monomers in cm³ +Vₒ = (4π / 3) * (10e-06 * 100)^3 + +# initial conc. = (No. of init. monomers) / bulk volume +Nₒ = 1e-06 / Vₒ + +# No. of monomers initially +uₒ = 10000 + +# Bulk volume of system in cm³ +V = uₒ / Nₒ +n = floor(Int, N / 2) + +# No. of forward reactions +nr = ((N % 2) == 0) ? (n*(n + 1) - n) : (n*(n + 1)) ``` 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ⱼ` ```julia # possible pairs of reactant multimers -pair = [] +pair = Int[] for i = 2:N - push!(pair, [1:integ(i/2) i .- (1:integ(i/2))]) + halfi = floor(Int, i/2) + push!(pair, [(1:halfi) (i .- (1:halfi))]) end pair = vcat(pair...) -vᵢ = @view pair[:,1] # Reactant 1 indices -vⱼ = @view pair[:,2] # Reactant 2 indices -volᵢ = Vₒ*vᵢ # cm⁻³ -volⱼ = Vₒ*vⱼ # cm⁻³ +vᵢ = @view pair[:, 1] # Reactant 1 indices +vⱼ = @view pair[:, 2] # Reactant 2 indices +volᵢ = Vₒ * vᵢ # cm⁻³ +volⱼ = Vₒ * vⱼ # cm⁻³ sum_vᵢvⱼ = @. vᵢ + vⱼ # Product index ``` 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) ```julia # set i to 1 for additive kernel, 2 for constant i = 1 -if i==1 - B = 1.53e03 # s⁻¹ - kv = @. B*(volᵢ + volⱼ)/V # dividing by volume as its a bi-molecular reaction chain +if i == 1 + B = 1.53e03 # s⁻¹ + + # dividing by volume as it is a bimolecular reaction chain + kv = @. B * (volᵢ + volⱼ) / V elseif i==2 - C = 1.84e-04 # cm³ s⁻¹ - kv = fill(C/V, nr) + C = 1.84e-04 # cm³ s⁻¹ + kv = fill(C / V, nr) end ``` 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`. @@ -58,9 +69,9 @@ pars = Pair.(collect(k), kv) # time-span if i == 1 - tspan = (0. ,2000.) + tspan = (0.0, 2000.0) elseif i == 2 - tspan = (0. ,350.) + tspan = (0.0, 350.0) end # initial condition of monomers @@ -82,19 +93,20 @@ for n = 1:nr end end @named rs = ReactionSystem(rx, t, collect(X), collect(k)) +rs = complete(rs) ``` We now convert the [`ReactionSystem`](@ref) into a `ModelingToolkit.JumpSystem`, and solve it using Gillespie's direct method. For details on other possible solvers (SSAs), see the [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/types/jump_types/) documentation ```julia # solving the system jumpsys = convert(JumpSystem, rs) dprob = DiscreteProblem(jumpsys, u₀map, tspan, pars) -jprob = JumpProblem(jumpsys, dprob, Direct(), save_positions=(false,false)) -jsol = solve(jprob, SSAStepper(), saveat = tspan[2]/30) +jprob = JumpProblem(jumpsys, dprob, Direct(), save_positions = (false, false)) +jsol = solve(jprob, SSAStepper(), saveat = tspan[2] / 30) ``` Lets check the results for the first three polymers/cluster sizes. We compare to the analytical solution for this system: ```julia # Results for first three polymers...i.e. monomers, dimers and trimers -v_res = [1;2;3] +v_res = [1; 2; 3] # comparison with analytical solution # we only plot the stochastic solution at a small number of points @@ -114,15 +126,15 @@ elseif i == 2 end # plotting normalised concentration vs analytical solution -default(lw=2, xlabel="Time (sec)") -scatter(ϕ, jsol(t)[1,:]/uₒ, label="X1 (monomers)", markercolor=:blue) +default(lw = 2, xlabel = "Time (sec)") +scatter(ϕ, jsol(t)[1,:] / uₒ, label = "X1 (monomers)", markercolor = :blue) plot!(ϕ, sol[1,:]/Nₒ, line = (:dot,4,:blue), label="Analytical sol--X1") -scatter!(ϕ, jsol(t)[2,:]/uₒ, label="X2 (dimers)", markercolor=:orange) -plot!(ϕ, sol[2,:]/Nₒ, line = (:dot,4,:orange), label="Analytical sol--X2") +scatter!(ϕ, jsol(t)[2,:] / uₒ, label = "X2 (dimers)", markercolor = :orange) +plot!(ϕ, sol[2,:] / Nₒ, line = (:dot, 4, :orange), label = "Analytical sol--X2") -scatter!(ϕ, jsol(t)[3,:]/uₒ, label="X3 (trimers)", markercolor=:purple) -plot!(ϕ, sol[3,:]/Nₒ, line = (:dot,4,:purple), label="Analytical sol--X3", +scatter!(ϕ, jsol(t)[3,:] / uₒ, label = "X3 (trimers)", markercolor = :purple) +plot!(ϕ, sol[3,:] / Nₒ, line = (:dot, 4, :purple), label = "Analytical sol--X3", ylabel = "Normalized Concentration") ``` For the **additive kernel** we find From 7ec86bb4a195d1a4f823b5fd0dc550258afe71df Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Fri, 7 Jun 2024 13:33:22 -0400 Subject: [PATCH 190/446] fix parameter declaration --- .../examples/smoluchowski_coagulation_equation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md index a226da718e..5ec69e5467 100644 --- a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md +++ b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md @@ -64,7 +64,8 @@ We'll store the reaction rates in `pars` as `Pair`s, and set the initial conditi ```julia # unknown variables are X, pars stores rate parameters for each rx t = default_t() -@species k[1:nr] (X(t))[1:N] +@parameters k[1:nr] +@species (X(t))[1:N] pars = Pair.(collect(k), kv) # time-span From 15610c09627d54289bf2be348f4c00b5c5c149fe Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Fri, 7 Jun 2024 15:06:26 -0400 Subject: [PATCH 191/446] try using vector parameters --- .../examples/smoluchowski_coagulation_equation.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md index 5ec69e5467..5d2b0755db 100644 --- a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md +++ b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md @@ -60,13 +60,14 @@ elseif i==2 kv = fill(C / V, nr) end ``` -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`. +We'll set the parameters and the initial condition that only monomers are present at ``t=0`` in `u₀map`. ```julia -# unknown variables are X, pars stores rate parameters for each rx +# k is a vector of the parameters, with values given by the vector kv +@parameters k[1:nr] = kv + +# create the vector of species X_1,...,X_N t = default_t() -@parameters k[1:nr] @species (X(t))[1:N] -pars = Pair.(collect(k), kv) # time-span if i == 1 @@ -78,7 +79,7 @@ end # initial condition of monomers u₀ = zeros(Int64, N) u₀[1] = uₒ -u₀map = Pair.(collect(X), u₀) # map variable to its initial value +u₀map = Pair.(collect(X), u₀) # map species to its initial value ``` 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. ```julia @@ -100,7 +101,7 @@ We now convert the [`ReactionSystem`](@ref) into a `ModelingToolkit.JumpSystem`, ```julia # solving the system jumpsys = convert(JumpSystem, rs) -dprob = DiscreteProblem(jumpsys, u₀map, tspan, pars) +dprob = DiscreteProblem(jumpsys, u₀map, tspan) jprob = JumpProblem(jumpsys, dprob, Direct(), save_positions = (false, false)) jsol = solve(jprob, SSAStepper(), saveat = tspan[2] / 30) ``` From 295678db402f61398ba9a4b62d3c2a3e2c8b4651 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 15:09:00 -0400 Subject: [PATCH 192/446] remove SymbolicUtils and related stuff --- Project.toml | 2 -- src/Catalyst.jl | 1 - src/chemistry_functionality.jl | 2 +- .../serialisation_support.jl | 8 ++--- .../serialise_fields.jl | 2 +- src/spatial_reaction_systems/utility.jl | 2 +- .../reactionsystem_serialisation.jl | 5 +-- .../parameter_type_designation.jl | 22 ++++++------ .../symbolic_stoichiometry.jl | 6 ++-- .../lattice_reaction_systems.jl | 34 +++++++++---------- 10 files changed, 41 insertions(+), 43 deletions(-) diff --git a/Project.toml b/Project.toml index 2ccc5fbc24..defcce26a8 100644 --- a/Project.toml +++ b/Project.toml @@ -21,7 +21,6 @@ Requires = "ae029012-a4dd-5104-9daa-d747884805df" RuntimeGeneratedFunctions = "7e49a35a-f44a-4d26-94aa-eba1b4ca6b47" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" @@ -57,7 +56,6 @@ RuntimeGeneratedFunctions = "0.5.12" Setfield = "1" StructuralIdentifiability = "0.5.1" Symbolics = "5.27" -SymbolicUtils = "2.0.2" TermInterface = "0.4.1" Unitful = "1.12.4" julia = "1.10" diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 6cfba05a9b..be74c2964e 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -23,7 +23,6 @@ using RuntimeGeneratedFunctions RuntimeGeneratedFunctions.init(@__MODULE__) import Symbolics: BasicSymbolic -import SymbolicUtils using TermInterface: iscall using ModelingToolkit: Symbolic, value, get_unknowns, get_ps, get_iv, get_systems, get_eqs, get_defaults, toparam, get_var_to_name, get_observed, diff --git a/src/chemistry_functionality.jl b/src/chemistry_functionality.jl index 941038046b..9cac3186ab 100644 --- a/src/chemistry_functionality.jl +++ b/src/chemistry_functionality.jl @@ -182,7 +182,7 @@ function make_compounds(expr) end push!(compound_declarations.args, :($(Expr(:escape, :($(compound_syms)))))) - # The output needs to be converted to Vector{Num} (from Vector{SymbolicUtils.BasicSymbolic{Real}}) to be consistent with e.g. @variables. + # The output needs to be converted to Vector{Num} (from Vector{BasicSymbolic{Real}}) to be consistent with e.g. @variables. compound_declarations.args[end] = :([ModelingToolkit.wrap(cmp) for cmp in $(compound_declarations.args[end])]) # Returns output that. diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 62b366ca5b..c38e2d650e 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -103,9 +103,9 @@ function sym_2_declaration_string(sym; multiline_format = false) dec_string = "$(sym)" # If the symbol have a non-default type, appends the declaration of this. - # Assumes that the type is on the form `SymbolicUtils.BasicSymbolic{X}`. Contain error checks + # Assumes that the type is on the form `BasicSymbolic{X}`. Contain error checks # to ensure that this is the case. - if !(sym isa SymbolicUtils.BasicSymbolic{Real}) + if !(sym isa BasicSymbolic{Real}) sym_type = String(Symbol(typeof(Symbolics.unwrap(sym)))) if (get_substring(sym_type, 1, 28) != "SymbolicUtils.BasicSymbolic{") || (get_char_end(sym_type, 0) != '}') error("Encountered symbolic of unexpected type: $sym_type.") @@ -139,7 +139,7 @@ end # and metadata values (which hopefully almost exclusively) has simple, supported, types. Ideally, # more supported types can be added here. x_2_string(x::Num) = expression_2_string(x) -x_2_string(x::SymbolicUtils.BasicSymbolic{<:Real}) = expression_2_string(x) +x_2_string(x::BasicSymbolic{<:Real}) = expression_2_string(x) x_2_string(x::Bool) = string(x) x_2_string(x::String) = "\"$x\"" x_2_string(x::Char) = "\'$x\'" @@ -293,6 +293,6 @@ end function complicated_declaration(sym) isempty(get_metadata_to_declare(sym)) || (return true) ModelingToolkit.hasdefault(sym) && (return true) - (sym isa SymbolicUtils.BasicSymbolic{Real}) || (return true) + (sym isa BasicSymbolic{Real}) || (return true) return false end \ No newline at end of file diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index 964e3bb617..ba28d20629 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -445,7 +445,7 @@ function discrete_event_string(discrete_event, strip_call_dict) # Creates the string corresponding to the conditions. The special check is if the condition is # an expression like `X > 5.0`. Here, "(...)" is added for purely aesthetic reasons. condition_string = x_2_string(discrete_event.condition) - if discrete_event.condition isa SymbolicUtils.BasicSymbolic + if discrete_event.condition isa BasicSymbolic @string_prepend! "(" condition_string @string_append! condition_string ")" end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 691ba79a3c..f750c93140 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -203,7 +203,7 @@ end # If all parameters the rate depend on are uniform all edges, this becomes a length 1 vector. # Else a vector with each value corresponding to the rate at one specific edge. function compute_transport_rates(rate_law::Num, - p_val_dict::Dict{SymbolicUtils.BasicSymbolic{Real}, Vector{Float64}}, num_edges::Int64) + p_val_dict::Dict{BasicSymbolic{Real}, Vector{Float64}}, num_edges::Int64) # Finds parameters involved in rate and create a function evaluating the rate law. relevant_ps = Symbolics.get_variables(rate_law) rate_law_func = drop_expr(@RuntimeGeneratedFunction(build_function(rate_law, relevant_ps...))) diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index eab88d0abf..3af24e2b64 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -4,10 +4,11 @@ using Catalyst, Test using Catalyst: get_rxs using ModelingToolkit: getdefault, getdescription, get_metadata +using Symbolics: getmetadata # Creates missing getters for MTK metadata (can be removed once added to MTK). -getmisc(x) = SymbolicUtils.getmetadata(Symbolics.unwrap(x), ModelingToolkit.VariableMisc, nothing) -getinput(x) = SymbolicUtils.getmetadata(Symbolics.unwrap(x), ModelingToolkit.VariableInput, nothing) +getmisc(x) = getmetadata(Symbolics.unwrap(x), ModelingToolkit.VariableMisc, nothing) +getinput(x) = getmetadata(Symbolics.unwrap(x), ModelingToolkit.VariableInput, nothing) # Sets the default `t` and `D` to use. t = default_t() diff --git a/test/reactionsystem_core/parameter_type_designation.jl b/test/reactionsystem_core/parameter_type_designation.jl index d6f7a7d13d..204d0f5095 100644 --- a/test/reactionsystem_core/parameter_type_designation.jl +++ b/test/reactionsystem_core/parameter_type_designation.jl @@ -2,7 +2,7 @@ # Fetch packages. using Catalyst, JumpProcesses, NonlinearSolve, OrdinaryDiffEq, StochasticDiffEq, Test -using Symbolics: unwrap +using Symbolics: BasicSymbolic, unwrap # Sets stable rng number. using StableRNGs @@ -47,16 +47,16 @@ end # Tests that parameters stored in the system have the correct type. let - @test Symbolics.unwrap(rs.p1) isa SymbolicUtils.BasicSymbolic{Real} - @test Symbolics.unwrap(rs.d1) isa SymbolicUtils.BasicSymbolic{Real} - @test Symbolics.unwrap(rs.p2) isa SymbolicUtils.BasicSymbolic{Float64} - @test Symbolics.unwrap(rs.d2) isa SymbolicUtils.BasicSymbolic{Float64} - @test Symbolics.unwrap(rs.p3) isa SymbolicUtils.BasicSymbolic{Int64} - @test Symbolics.unwrap(rs.d3) isa SymbolicUtils.BasicSymbolic{Int64} - @test Symbolics.unwrap(rs.p4) isa SymbolicUtils.BasicSymbolic{Float32} - @test Symbolics.unwrap(rs.d4) isa SymbolicUtils.BasicSymbolic{Rational{Int64}} - @test Symbolics.unwrap(rs.p5) isa SymbolicUtils.BasicSymbolic{Rational{Int64}} - @test Symbolics.unwrap(rs.d5) isa SymbolicUtils.BasicSymbolic{Float32} + @test Symbolics.unwrap(rs.p1) isa BasicSymbolic{Real} + @test Symbolics.unwrap(rs.d1) isa BasicSymbolic{Real} + @test Symbolics.unwrap(rs.p2) isa BasicSymbolic{Float64} + @test Symbolics.unwrap(rs.d2) isa BasicSymbolic{Float64} + @test Symbolics.unwrap(rs.p3) isa BasicSymbolic{Int64} + @test Symbolics.unwrap(rs.d3) isa BasicSymbolic{Int64} + @test Symbolics.unwrap(rs.p4) isa BasicSymbolic{Float32} + @test Symbolics.unwrap(rs.d4) isa BasicSymbolic{Rational{Int64}} + @test Symbolics.unwrap(rs.p5) isa BasicSymbolic{Rational{Int64}} + @test Symbolics.unwrap(rs.d5) isa BasicSymbolic{Float32} end # Tests that simulations with differentially typed variables yields correct results. diff --git a/test/reactionsystem_core/symbolic_stoichiometry.jl b/test/reactionsystem_core/symbolic_stoichiometry.jl index 797a78f239..c2ec784919 100644 --- a/test/reactionsystem_core/symbolic_stoichiometry.jl +++ b/test/reactionsystem_core/symbolic_stoichiometry.jl @@ -2,7 +2,7 @@ # Fetch packages. using Catalyst, JumpProcesses, OrdinaryDiffEq, StochasticDiffEq, Statistics, Test -using Symbolics: unwrap +using Symbolics: BasicSymbolic, unwrap # Sets stable rng number. using StableRNGs @@ -46,8 +46,8 @@ let @test rs1 == rs2 == rs3 @test issetequal(unknowns(rs1), [X, Y]) @test issetequal(parameters(rs1), [p, k, d, n1, n2, n3]) - @test unwrap(d) isa SymbolicUtils.BasicSymbolic{Float64} - @test unwrap(n1) isa SymbolicUtils.BasicSymbolic{Int64} + @test unwrap(d) isa BasicSymbolic{Float64} + @test unwrap(n1) isa BasicSymbolic{Int64} end # Declares a network, parameter values, and initial conditions, to be used for the next couple of tests. diff --git a/test/spatial_modelling/lattice_reaction_systems.jl b/test/spatial_modelling/lattice_reaction_systems.jl index a1fce40846..a467baae61 100644 --- a/test/spatial_modelling/lattice_reaction_systems.jl +++ b/test/spatial_modelling/lattice_reaction_systems.jl @@ -2,7 +2,7 @@ # Fetch packages. using Catalyst, Graphs, Test -using Symbolics: unwrap +using Symbolics: BasicSymbolic, unwrap t = default_t() # Pre declares a grid. @@ -311,19 +311,19 @@ end # Loops through all parameters, ensuring that they have the correct type p_types = Dict([ModelingToolkit.nameof(p) => typeof(unwrap(p)) for p in parameters(lrs)]) - @test p_types[:k1] == SymbolicUtils.BasicSymbolic{Real} - @test p_types[:l1] == SymbolicUtils.BasicSymbolic{Real} - @test p_types[:k2] == SymbolicUtils.BasicSymbolic{Float64} - @test p_types[:l2] == SymbolicUtils.BasicSymbolic{Float64} - @test p_types[:k3] == SymbolicUtils.BasicSymbolic{Int64} - @test p_types[:l3] == SymbolicUtils.BasicSymbolic{Int64} - @test p_types[:k4] == SymbolicUtils.BasicSymbolic{Float32} - @test p_types[:l4] == SymbolicUtils.BasicSymbolic{Float32} - @test p_types[:k5] == SymbolicUtils.BasicSymbolic{Rational{Int64}} - @test p_types[:l5] == SymbolicUtils.BasicSymbolic{Rational{Int64}} - @test p_types[:D1] == SymbolicUtils.BasicSymbolic{Float32} - @test p_types[:D2] == SymbolicUtils.BasicSymbolic{Real} - @test p_types[:D3] == SymbolicUtils.BasicSymbolic{Rational{Int64}} + @test p_types[:k1] == BasicSymbolic{Real} + @test p_types[:l1] == BasicSymbolic{Real} + @test p_types[:k2] == BasicSymbolic{Float64} + @test p_types[:l2] == BasicSymbolic{Float64} + @test p_types[:k3] == BasicSymbolic{Int64} + @test p_types[:l3] == BasicSymbolic{Int64} + @test p_types[:k4] == BasicSymbolic{Float32} + @test p_types[:l4] == BasicSymbolic{Float32} + @test p_types[:k5] == BasicSymbolic{Rational{Int64}} + @test p_types[:l5] == BasicSymbolic{Rational{Int64}} + @test p_types[:D1] == BasicSymbolic{Float32} + @test p_types[:D2] == BasicSymbolic{Real} + @test p_types[:D3] == BasicSymbolic{Rational{Int64}} end # Checks that programmatically declared parameters (with types) can be used in `TransportReaction`s. @@ -338,9 +338,9 @@ end lrs = LatticeReactionSystem(rs, [tr], grid) p_types = Dict([ModelingToolkit.nameof(p) => typeof(unwrap(p)) for p in parameters(lrs)]) - @test p_types[:p] == SymbolicUtils.BasicSymbolic{Float32} - @test p_types[:d] == SymbolicUtils.BasicSymbolic{Real} - @test p_types[:D] == SymbolicUtils.BasicSymbolic{Rational{Int64}} + @test p_types[:p] == BasicSymbolic{Float32} + @test p_types[:d] == BasicSymbolic{Real} + @test p_types[:D] == BasicSymbolic{Rational{Int64}} u0 = [:X => [0.25, 0.5, 2.0, 4.0]] ps = [rs.p => 2.0, rs.d => 1.0, D => 1//2] From f4842e8146bdf938ac82a5cfa8aa70df4c5d1e6d Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 15:14:43 -0400 Subject: [PATCH 193/446] init --- .../introduction_to_catalyst.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index 40c2900988..94b36738ca 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -12,9 +12,9 @@ We first import the basic packages we'll need: ```@example tut1 # If not already installed, first hit "]" within a Julia REPL. Then type: -# add Catalyst DifferentialEquations Plots Latexify +# add Catalyst OrdinaryDiffEq Plots Latexify -using Catalyst, DifferentialEquations, Plots, Latexify +using Catalyst, OrdinaryDiffEq, Plots, Latexify ``` We now construct the reaction network. The basic types of arrows and predefined @@ -160,7 +160,7 @@ underlying problem. At this point we are all set to solve the ODEs. We can now use any ODE solver from within the -[DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) +[OrdinaryDiffEq.jl](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/) package. We'll use the recommended default explicit solver, `Tsit5()`, and then plot the solutions: @@ -169,7 +169,7 @@ sol = solve(oprob, Tsit5(), saveat=10.) plot(sol) ``` We see the well-known oscillatory behavior of the repressilator! For more on the -choices of ODE solvers, see the [DifferentialEquations.jl +choices of ODE solvers, see the [OrdinaryDiffEq.jl documentation](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/). --- @@ -182,6 +182,9 @@ Gillespie's `Direct` method, and then solve it to generate one realization of the jump process: ```@example tut1 +# imports the JumpProcesses packages +using JumpProcesses + # redefine the initial condition to be integer valued u₀map = [:m₁ => 0, :m₂ => 0, :m₃ => 0, :P₁ => 20, :P₂ => 0, :P₃ => 0] @@ -237,6 +240,9 @@ model by creating an `SDEProblem` and solving it similarly to what we did for OD above: ```@example tut1 +# imports the StochasticDiffEq package for SDE simulations +using StochasticDiffEq + # SDEProblem for CLE sprob = SDEProblem(bdp, u₀, tspan, p) From 43a4ae4ef08d0647b0a867f37493b7167f4ca497 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 15:25:41 -0400 Subject: [PATCH 194/446] init --- docs/src/model_creation/constraint_equations.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/src/model_creation/constraint_equations.md b/docs/src/model_creation/constraint_equations.md index 25ef0b8d7b..b8a08769cc 100644 --- a/docs/src/model_creation/constraint_equations.md +++ b/docs/src/model_creation/constraint_equations.md @@ -28,7 +28,7 @@ creating these two systems. Here, to create differentials with respect to time (for our differential equations), we must import the time differential operator from Catalyst. We do this through `D = default_time_deriv()`. Here, `D(V)` denotes the differential of the variable `V` with respect to time. ```@example ceq1 -using Catalyst, DifferentialEquations, Plots +using Catalyst, OrdinaryDiffEq, Plots t = default_t() D = default_time_deriv() @@ -58,6 +58,7 @@ We can now merge the two systems into one complete `ReactionSystem` model using [`ModelingToolkit.extend`](@ref): ```@example ceq1 @named growing_cell = extend(osys, rn) +growing_cell = complete(growing_cell) ``` We see that the combined model now has both the reactions and ODEs as its @@ -72,7 +73,7 @@ plot(sol) As an alternative to the previous approach, we could have constructed our `ReactionSystem` all at once by directly using the symbolic interface: ```@example ceq2 -using Catalyst, DifferentialEquations, Plots +using Catalyst, OrdinaryDiffEq, Plots t = default_t() D = default_time_deriv() @@ -83,6 +84,7 @@ rx1 = @reaction $V, 0 --> P rx2 = @reaction 1.0, P --> 0 @named growing_cell = ReactionSystem([rx1, rx2, eq], t) setdefaults!(growing_cell, [:P => 0.0]) +growing_cell = complete(growing_cell) oprob = ODEProblem(growing_cell, [], (0.0, 1.0)) sol = solve(oprob, Tsit5()) @@ -100,12 +102,11 @@ the associated [ModelingToolkit tutorial](https://docs.sciml.ai/ModelingToolkit/stable/basics/Events/) for more details on the types of events that can be represented symbolically. A lower-level approach for creating events via the DifferentialEquations.jl -callback interface is illustrated in the [Advanced Simulation Options](@ref -advanced_simulations) tutorial. +callback interface is illustrated [here](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/) tutorial. Let's first create our equations and unknowns/species again ```@example ceq3 -using Catalyst, DifferentialEquations, Plots +using Catalyst, OrdinaryDiffEq, Plots t = default_t() D = default_time_deriv() @@ -124,6 +125,7 @@ continuous_events = [V ~ 2.0] => [V ~ V/2, P ~ P/2] We can now create and simulate our model ```@example ceq3 @named rs = ReactionSystem([rx1, rx2, eq], t; continuous_events) +rs = complete(rs) oprob = ODEProblem(rs, [], (0.0, 10.0)) sol = solve(oprob, Tsit5()) @@ -148,6 +150,7 @@ p = [k_on => 100.0, switch_time => 2.0, k_off => 10.0] Simulating our model, ```@example ceq3 @named osys = ReactionSystem(rxs, t, [A, B], [k_on, k_off, switch_time]; discrete_events) +osys = complete(osys) oprob = ODEProblem(osys, u0, tspan, p) sol = solve(oprob, Tsit5(); tstops = 2.0) From 251eea38d9820a095555ee17d56c642edbe902a0 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Fri, 7 Jun 2024 15:43:24 -0400 Subject: [PATCH 195/446] complete jsys --- docs/src/assets/Project.toml | 3 +-- .../smoluchowski_coagulation_equation.md | 25 +++++++++++-------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index ebb086fda6..83ecae3cc6 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -5,7 +5,6 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DiffEqParamEstim = "1130ab10-4a5a-5621-a13d-e4788d82bd4c" -DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DynamicalSystems = "61744808-ddfa-5f27-97ff-6e42cc95d634" @@ -38,7 +37,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [compat] BenchmarkTools = "1.5" -BifurcationKit = "0.3" +BifurcationKit = "0.3.4" CairoMakie = "0.12" Catalyst = "13" DataFrames = "1.6" diff --git a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md index 5d2b0755db..23fa2c4f63 100644 --- a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md +++ b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md @@ -4,13 +4,13 @@ This tutorial shows how to programmatically construct a [`ReactionSystem`](@ref) 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. We begin by importing some necessary packages. -```julia +```@example smcoag1 using ModelingToolkit, Catalyst, LinearAlgebra using JumpProcesses using Plots, SpecialFunctions ``` 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 -```julia +```@example smcoag1 # maximum cluster size N = 10 @@ -29,12 +29,13 @@ n = floor(Int, N / 2) # No. of forward reactions nr = ((N % 2) == 0) ? (n*(n + 1) - n) : (n*(n + 1)) +nothing #hide ``` 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ⱼ` -```julia +```@example smcoag1 # possible pairs of reactant multimers -pair = Int[] +pair = [] for i = 2:N halfi = floor(Int, i/2) push!(pair, [(1:halfi) (i .- (1:halfi))]) @@ -45,9 +46,10 @@ vⱼ = @view pair[:, 2] # Reactant 2 indices volᵢ = Vₒ * vᵢ # cm⁻³ volⱼ = Vₒ * vⱼ # cm⁻³ sum_vᵢvⱼ = @. vᵢ + vⱼ # Product index +nothing #hide ``` 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) -```julia +```@example smcoag1 # set i to 1 for additive kernel, 2 for constant i = 1 if i == 1 @@ -61,7 +63,7 @@ elseif i==2 end ``` We'll set the parameters and the initial condition that only monomers are present at ``t=0`` in `u₀map`. -```julia +```@example smcoag1 # k is a vector of the parameters, with values given by the vector kv @parameters k[1:nr] = kv @@ -80,9 +82,10 @@ end u₀ = zeros(Int64, N) u₀[1] = uₒ u₀map = Pair.(collect(X), u₀) # map species to its initial value +nothing #hide ``` 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. -```julia +```@example smcoag1 # vector to store the Reactions in rx = [] for n = 1:nr @@ -98,15 +101,15 @@ end rs = complete(rs) ``` We now convert the [`ReactionSystem`](@ref) into a `ModelingToolkit.JumpSystem`, and solve it using Gillespie's direct method. For details on other possible solvers (SSAs), see the [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/types/jump_types/) documentation -```julia +```@example smcoag1 # solving the system -jumpsys = convert(JumpSystem, rs) -dprob = DiscreteProblem(jumpsys, u₀map, tspan) +jumpsys = complete(convert(JumpSystem, rs)) +dprob = DiscreteProblem(rs, u₀map, tspan) jprob = JumpProblem(jumpsys, dprob, Direct(), save_positions = (false, false)) jsol = solve(jprob, SSAStepper(), saveat = tspan[2] / 30) ``` Lets check the results for the first three polymers/cluster sizes. We compare to the analytical solution for this system: -```julia +```@example smcoag1 # Results for first three polymers...i.e. monomers, dimers and trimers v_res = [1; 2; 3] From b64a3a7afae29b02477dee8924809da863b271d8 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Fri, 7 Jun 2024 15:48:56 -0400 Subject: [PATCH 196/446] tutorial renders locally --- .../examples/smoluchowski_coagulation_equation.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md index 23fa2c4f63..dc9427c979 100644 --- a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md +++ b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md @@ -61,6 +61,7 @@ elseif i==2 C = 1.84e-04 # cm³ s⁻¹ kv = fill(C / V, nr) end +nothing #hide ``` We'll set the parameters and the initial condition that only monomers are present at ``t=0`` in `u₀map`. ```@example smcoag1 @@ -107,6 +108,7 @@ jumpsys = complete(convert(JumpSystem, rs)) dprob = DiscreteProblem(rs, u₀map, tspan) jprob = JumpProblem(jumpsys, dprob, Direct(), save_positions = (false, false)) jsol = solve(jprob, SSAStepper(), saveat = tspan[2] / 30) +nothing #hide ``` Lets check the results for the first three polymers/cluster sizes. We compare to the analytical solution for this system: ```@example smcoag1 @@ -142,9 +144,6 @@ scatter!(ϕ, jsol(t)[3,:] / uₒ, label = "X3 (trimers)", markercolor = :purple) plot!(ϕ, sol[3,:] / Nₒ, line = (:dot, 4, :purple), label = "Analytical sol--X3", ylabel = "Normalized Concentration") ``` -For the **additive kernel** we find - -![additive_kernel](../../assets/additive_kernel.svg) --- ## References From 7a2a341f415f9616235fc1a76533c75732cc5003 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Fri, 7 Jun 2024 15:51:20 -0400 Subject: [PATCH 197/446] turn off warnings not leading to doc failures --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index bd486f9711..b0754e7df9 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -41,7 +41,7 @@ makedocs(sitename = "Catalyst.jl", clean = true, pages = pages, pagesonly = true, - warnonly = true) + warnonly = [:missing_docs]) deploydocs(repo = "github.com/SciML/Catalyst.jl.git"; push_preview = true) From e6e1612c15f4db57108ff60087cd818f13c5b849 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 16:00:50 -0400 Subject: [PATCH 198/446] enforce latest SI version --- Project.toml | 2 +- docs/Project.toml | 2 +- src/Catalyst.jl | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index defcce26a8..c3868ef59c 100644 --- a/Project.toml +++ b/Project.toml @@ -54,7 +54,7 @@ Reexport = "0.2, 1.0" Requires = "1.0" RuntimeGeneratedFunctions = "0.5.12" Setfield = "1" -StructuralIdentifiability = "0.5.1" +StructuralIdentifiability = "0.5.8" Symbolics = "5.27" TermInterface = "0.4.1" Unitful = "1.12.4" diff --git a/docs/Project.toml b/docs/Project.toml index 83ecae3cc6..60c13c5e50 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -68,5 +68,5 @@ SpecialFunctions = "2.4" StaticArrays = "1.9" SteadyStateDiffEq = "2.2" StochasticDiffEq = "6.65" -StructuralIdentifiability = "0.5.7" +StructuralIdentifiability = "0.5.8" Symbolics = "5.28" diff --git a/src/Catalyst.jl b/src/Catalyst.jl index be74c2964e..c3b66c0be9 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -44,6 +44,7 @@ import Graphs: DiGraph, SimpleGraph, SimpleDiGraph, vertices, edges, add_vertice import DataStructures: OrderedDict, OrderedSet import Parameters: @with_kw_noshow import Symbolics: occursin, wrap +import Symbolics.RewriteHelpers: hasnode, replacenode # globals for the modulate function default_time_deriv() From 6563e760b235cc623c50746fcd949b9ba15bf096 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 16:27:59 -0400 Subject: [PATCH 199/446] extension fixes --- .../bifurcation_kit_extension.jl | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl index 5be1c9c600..37887932a8 100644 --- a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl +++ b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl @@ -10,19 +10,21 @@ function BK.BifurcationProblem(rs::ReactionSystem, u0_bif, ps, bif_par, args...; # Converts symbols to symbolics. (bif_par isa Symbol) && (bif_par = ModelingToolkit.get_var_to_name(rs)[bif_par]) (plot_var isa Symbol) && (plot_var = ModelingToolkit.get_var_to_name(rs)[plot_var]) - ((u0_bif isa Vector{<:Pair{Symbol, <:Any}}) || (u0_bif isa Dict{Symbol, <:Any})) && - (u0_bif = symmap_to_varmap(rs, u0_bif)) - ((ps isa Vector{<:Pair{Symbol, <:Any}}) || (ps isa Dict{Symbol, <:Any})) && - (ps = symmap_to_varmap(rs, ps)) - ((u0 isa Vector{<:Pair{Symbol, <:Any}}) || (u0 isa Dict{Symbol, <:Any})) && - (u0 = symmap_to_varmap(rs, u0)) + if (u0_bif isa Vector{<:Pair{Symbol, <:Any}}) || (u0_bif isa Dict{Symbol, <:Any}) + u0_bif = symmap_to_varmap(rs, u0_bif) + end + if (ps isa Vector{<:Pair{Symbol, <:Any}}) || (ps isa Dict{Symbol, <:Any}) + ps = symmap_to_varmap(rs, ps) + end + if (u0 isa Vector{<:Pair{Symbol, <:Any}}) || (u0 isa Dict{Symbol, <:Any}) + u0 = symmap_to_varmap(rs, u0) + end # Creates NonlinearSystem. Catalyst.conservationlaw_errorcheck(rs, vcat(ps, u0)) - nsys = complete(convert( - NonlinearSystem, rs; remove_conserved = true, defaults = Dict(u0))) + nsys = convert(NonlinearSystem, rs; remove_conserved = true, defaults = Dict(u0)) + nsys = complete(nsys) # Makes BifurcationProblem (this call goes through the ModelingToolkit-based BifurcationKit extension). - return BK.BifurcationProblem(nsys, u0_bif, ps, bif_par, args...; plot_var = plot_var, - record_from_solution = record_from_solution, jac = jac, kwargs...) + return BK.BifurcationProblem(nsys, u0_bif, ps, bif_par, args...; plot_var, record_from_solution, jac, kwargs...) end From 19706924773e6e669d3c959fe33704eba70e6c78 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 7 Jun 2024 16:29:05 -0400 Subject: [PATCH 200/446] Update src/network_analysis.jl Co-authored-by: Sam Isaacson --- src/network_analysis.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index f60bfcbc4a..c4f70f3861 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -398,7 +398,7 @@ end # Used in the subsequent function. function subnetworkmapping(linkageclass, allrxs, complextorxsmap, p) rxinds = sort!(collect(Set(rxidx for rcidx in linkageclass - for rxidx in complextorxsmap[rcidx]))) + for rxidx in complextorxsmap[rcidx]))) rxs = allrxs[rxinds] specset = Set(s for rx in rxs for s in rx.substrates if !isconstant(s)) for rx in rxs From 1004a3757f8bbcafa31ed2b517c07709ffe7fd9e Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 7 Jun 2024 16:29:14 -0400 Subject: [PATCH 201/446] Update src/reaction.jl Co-authored-by: Sam Isaacson --- src/reaction.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reaction.jl b/src/reaction.jl index d8ff6c6e8e..142242212e 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -334,8 +334,8 @@ function MT.namespace_equation(rx::Reaction, name; kw...) ns = similar(rx.netstoich) map!(n -> f(n[1]) => f(n[2]), ns, rx.netstoich) end - Reaction( - rate, subs, prods, substoich, prodstoich, netstoich, rx.only_use_rate, rx.metadata) + Reaction(rate, subs, prods, substoich, prodstoich, netstoich, + rx.only_use_rate, rx.metadata) end # Overwrites equation-type functions to give the correct input for `Reaction`s. From acd244d2f16af248c2430d64736d3c2127dc72f6 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 7 Jun 2024 16:29:22 -0400 Subject: [PATCH 202/446] Update src/reaction.jl Co-authored-by: Sam Isaacson --- src/reaction.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reaction.jl b/src/reaction.jl index 142242212e..1f9e19c1b7 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -497,7 +497,7 @@ function getmetadata(reaction::Reaction, md_key::Symbol) end metadata = getmetadata_dict(reaction) return metadata[findfirst(isequal(md_key, entry[1]) - for entry in getmetadata_dict(reaction))][2] + for entry in getmetadata_dict(reaction))][2] end ### Implemented Reaction Metadata ### From 16c85c0e128b68b1e2205c64426658aac24991dc Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 7 Jun 2024 16:29:42 -0400 Subject: [PATCH 203/446] Update src/reactionsystem.jl Co-authored-by: Sam Isaacson --- src/reactionsystem.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index c84b1feb3f..93bb38c1b4 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -1251,9 +1251,8 @@ function set_default_metadata(rs::ReactionSystem; default_reaction_metadata = [] ns_syms = [Symbolics.unwrap(sym) for sym in get_variables(ns_expr)] ns_ps = Iterators.filter(ModelingToolkit.isparameter, ns_syms) ns_sps = Iterators.filter(Catalyst.isspecies, ns_syms) - ns_vs = Iterators.filter( - sym -> !Catalyst.isspecies(sym) && - !ModelingToolkit.isparameter(sym), ns_syms) + ns_vs = Iterators.filter(sym -> !Catalyst.isspecies(sym) && + !ModelingToolkit.isparameter(sym), ns_syms) # Adds parameters, species, and variables to the `ReactionSystem`. @set! rs.ps = union(get_ps(rs), ns_ps) sps_new = union(get_species(rs), ns_sps) From e0e3ff59c68b529721d4cca525e24e548befebea Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 7 Jun 2024 16:29:49 -0400 Subject: [PATCH 204/446] Update src/reactionsystem_conversions.jl Co-authored-by: Sam Isaacson --- src/reactionsystem_conversions.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index af3b61eb12..635733ac32 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -472,8 +472,8 @@ Keyword args and default values: function Base.convert(::Type{<:ODESystem}, rs::ReactionSystem; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), include_zero_odes = true, remove_conserved = false, checks = false, - default_u0 = Dict(), default_p = Dict(), defaults = _merge( - Dict(default_u0), Dict(default_p)), + default_u0 = Dict(), default_p = Dict(), + defaults = _merge(Dict(default_u0), Dict(default_p)), kwargs...) iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, ODESystem) From 3106d1dfd253b15ced8d130232e08f65baa27345 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 7 Jun 2024 16:29:55 -0400 Subject: [PATCH 205/446] Update src/reactionsystem_conversions.jl Co-authored-by: Sam Isaacson --- src/reactionsystem_conversions.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 635733ac32..22f05601c8 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -596,8 +596,8 @@ Notes: function Base.convert(::Type{<:SDESystem}, rs::ReactionSystem; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), include_zero_odes = true, checks = false, remove_conserved = false, - default_u0 = Dict(), default_p = Dict(), defaults = _merge( - Dict(default_u0), Dict(default_p)), + default_u0 = Dict(), default_p = Dict(), + defaults = _merge(Dict(default_u0), Dict(default_p)), kwargs...) iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, SDESystem) From db585fe905dd6ca7207eb8ed2a924c6482832537 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 7 Jun 2024 16:30:04 -0400 Subject: [PATCH 206/446] Update src/reactionsystem_conversions.jl Co-authored-by: Sam Isaacson --- src/reactionsystem_conversions.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 22f05601c8..dea9d87290 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -647,8 +647,8 @@ Notes: function Base.convert(::Type{<:JumpSystem}, rs::ReactionSystem; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), remove_conserved = nothing, checks = false, - default_u0 = Dict(), default_p = Dict(), defaults = _merge( - Dict(default_u0), Dict(default_p)), + default_u0 = Dict(), default_p = Dict(), + defaults = _merge(Dict(default_u0), Dict(default_p)), kwargs...) iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, JumpSystem) From 617a567b5be38348562d1304d0b268d2cb174830 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 7 Jun 2024 16:30:19 -0400 Subject: [PATCH 207/446] Update src/reactionsystem_conversions.jl Co-authored-by: Sam Isaacson --- src/reactionsystem_conversions.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index dea9d87290..c35692a52b 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -514,8 +514,8 @@ Keyword args and default values: function Base.convert(::Type{<:NonlinearSystem}, rs::ReactionSystem; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), include_zero_odes = true, remove_conserved = false, checks = false, - default_u0 = Dict(), default_p = Dict(), defaults = _merge( - Dict(default_u0), Dict(default_p)), + default_u0 = Dict(), default_p = Dict(), + defaults = _merge(Dict(default_u0), Dict(default_p)), all_differentials_permitted = false, kwargs...) # Error checks. iscomplete(rs) || error(COMPLETENESS_ERROR) From 69cc9b0377eb5e422142164c32e7ea93919b2941 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 16:32:34 -0400 Subject: [PATCH 208/446] incoporate suggestions --- src/reactionsystem.jl | 7 +++---- src/reactionsystem_conversions.jl | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 93bb38c1b4..2dacf36211 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -59,10 +59,9 @@ function Base.getindex(rc::ReactionComplex, i...) end function Base.setindex!(rc::ReactionComplex, t::ReactionComplexElement, i...) - (setindex!(rc.speciesids, t.speciesid, i...); - setindex!(rc.speciesstoichs, - t.speciesstoich, i...); - rc) + setindex!(rc.speciesids, t.speciesid, i...) + setindex!(rc.speciesstoichs, t.speciesstoich, i...) + rc end function Base.isless(a::ReactionComplexElement, b::ReactionComplexElement) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index c35692a52b..ed95176be7 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -358,8 +358,7 @@ function assemble_jumps(rs; combinatoric_ratelaws = true) (rx.rate isa Symbolic) && get_variables!(rxvars, rx.rate) isvrj = isvrjvec[i] - if (!isvrj) && ismassaction(rx, rs; rxvars = rxvars, haveivdep = false, - unknownset = unknownset) + if (!isvrj) && ismassaction(rx, rs; rxvars, haveivdep = false, unknownset) push!(meqs, makemajump(rx; combinatoric_ratelaw = combinatoric_ratelaws)) else rl = jumpratelaw(rx; combinatoric_ratelaw = combinatoric_ratelaws) From 3e270fd4b9c387c625d883183ab89311466f16f0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 16:35:06 -0400 Subject: [PATCH 209/446] fomratting update --- .../bifurcation_kit_extension.jl | 3 ++- src/network_analysis.jl | 2 +- src/reaction.jl | 4 ++-- src/reactionsystem.jl | 5 +++-- src/reactionsystem_conversions.jl | 8 ++++---- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl index 37887932a8..18814e0edf 100644 --- a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl +++ b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl @@ -26,5 +26,6 @@ function BK.BifurcationProblem(rs::ReactionSystem, u0_bif, ps, bif_par, args...; nsys = complete(nsys) # Makes BifurcationProblem (this call goes through the ModelingToolkit-based BifurcationKit extension). - return BK.BifurcationProblem(nsys, u0_bif, ps, bif_par, args...; plot_var, record_from_solution, jac, kwargs...) + return BK.BifurcationProblem( + nsys, u0_bif, ps, bif_par, args...; plot_var, record_from_solution, jac, kwargs...) end diff --git a/src/network_analysis.jl b/src/network_analysis.jl index c4f70f3861..f60bfcbc4a 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -398,7 +398,7 @@ end # Used in the subsequent function. function subnetworkmapping(linkageclass, allrxs, complextorxsmap, p) rxinds = sort!(collect(Set(rxidx for rcidx in linkageclass - for rxidx in complextorxsmap[rcidx]))) + for rxidx in complextorxsmap[rcidx]))) rxs = allrxs[rxinds] specset = Set(s for rx in rxs for s in rx.substrates if !isconstant(s)) for rx in rxs diff --git a/src/reaction.jl b/src/reaction.jl index 1f9e19c1b7..2f3d6251a6 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -334,7 +334,7 @@ function MT.namespace_equation(rx::Reaction, name; kw...) ns = similar(rx.netstoich) map!(n -> f(n[1]) => f(n[2]), ns, rx.netstoich) end - Reaction(rate, subs, prods, substoich, prodstoich, netstoich, + Reaction(rate, subs, prods, substoich, prodstoich, netstoich, rx.only_use_rate, rx.metadata) end @@ -497,7 +497,7 @@ function getmetadata(reaction::Reaction, md_key::Symbol) end metadata = getmetadata_dict(reaction) return metadata[findfirst(isequal(md_key, entry[1]) - for entry in getmetadata_dict(reaction))][2] + for entry in getmetadata_dict(reaction))][2] end ### Implemented Reaction Metadata ### diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 2dacf36211..917e0284ea 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -1250,8 +1250,9 @@ function set_default_metadata(rs::ReactionSystem; default_reaction_metadata = [] ns_syms = [Symbolics.unwrap(sym) for sym in get_variables(ns_expr)] ns_ps = Iterators.filter(ModelingToolkit.isparameter, ns_syms) ns_sps = Iterators.filter(Catalyst.isspecies, ns_syms) - ns_vs = Iterators.filter(sym -> !Catalyst.isspecies(sym) && - !ModelingToolkit.isparameter(sym), ns_syms) + ns_vs = Iterators.filter( + sym -> !Catalyst.isspecies(sym) && + !ModelingToolkit.isparameter(sym), ns_syms) # Adds parameters, species, and variables to the `ReactionSystem`. @set! rs.ps = union(get_ps(rs), ns_ps) sps_new = union(get_species(rs), ns_sps) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index ed95176be7..75ac1ef0a6 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -471,7 +471,7 @@ Keyword args and default values: function Base.convert(::Type{<:ODESystem}, rs::ReactionSystem; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), include_zero_odes = true, remove_conserved = false, checks = false, - default_u0 = Dict(), default_p = Dict(), + default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)), kwargs...) iscomplete(rs) || error(COMPLETENESS_ERROR) @@ -513,7 +513,7 @@ Keyword args and default values: function Base.convert(::Type{<:NonlinearSystem}, rs::ReactionSystem; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), include_zero_odes = true, remove_conserved = false, checks = false, - default_u0 = Dict(), default_p = Dict(), + default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)), all_differentials_permitted = false, kwargs...) # Error checks. @@ -595,7 +595,7 @@ Notes: function Base.convert(::Type{<:SDESystem}, rs::ReactionSystem; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), include_zero_odes = true, checks = false, remove_conserved = false, - default_u0 = Dict(), default_p = Dict(), + default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)), kwargs...) iscomplete(rs) || error(COMPLETENESS_ERROR) @@ -646,7 +646,7 @@ Notes: function Base.convert(::Type{<:JumpSystem}, rs::ReactionSystem; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), remove_conserved = nothing, checks = false, - default_u0 = Dict(), default_p = Dict(), + default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)), kwargs...) iscomplete(rs) || error(COMPLETENESS_ERROR) From db75226024b8831715fc53ea80e1be42e07ddfca Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 17:28:25 -0400 Subject: [PATCH 210/446] check all files round 1 --- .../bifurcation_kit_extension.jl | 4 +- .../homotopy_continuation_extension.jl | 4 +- .../structural_identifiability_extension.jl | 44 ++++++++-------- src/chemistry_functionality.jl | 18 +++---- src/dsl.jl | 52 +++++++++---------- src/expression_utils.jl | 4 +- src/reaction.jl | 3 -- src/reactionsystem.jl | 6 +-- src/reactionsystem_conversions.jl | 4 +- .../serialisation_support.jl | 10 ++-- .../serialise_fields.jl | 37 ++++++------- .../serialise_reactionsystem.jl | 39 +++++++------- src/registered_functions.jl | 5 +- .../lattice_jump_systems.jl | 16 +++--- .../lattice_reaction_systems.jl | 10 ++-- .../spatial_ODE_systems.jl | 25 +++++---- .../spatial_reactions.jl | 29 ++++++----- src/spatial_reaction_systems/utility.jl | 45 ++++++++-------- src/steady_state_stability.jl | 5 +- 19 files changed, 176 insertions(+), 184 deletions(-) diff --git a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl index 18814e0edf..412caadb3a 100644 --- a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl +++ b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl @@ -26,6 +26,6 @@ function BK.BifurcationProblem(rs::ReactionSystem, u0_bif, ps, bif_par, args...; nsys = complete(nsys) # Makes BifurcationProblem (this call goes through the ModelingToolkit-based BifurcationKit extension). - return BK.BifurcationProblem( - nsys, u0_bif, ps, bif_par, args...; plot_var, record_from_solution, jac, kwargs...) + return BK.BifurcationProblem(nsys, u0_bif, ps, bif_par, args...; plot_var, + record_from_solution, jac, kwargs...) end diff --git a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl index dc61472809..66864f93b9 100644 --- a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl +++ b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl @@ -52,8 +52,8 @@ function steady_state_polynomial(rs::ReactionSystem, ps, u0) ns = complete(convert(NonlinearSystem, rs; remove_conserved = true)) pre_varmap = [symmap_to_varmap(rs, u0)..., symmap_to_varmap(rs, ps)...] Catalyst.conservationlaw_errorcheck(rs, pre_varmap) - p_vals = ModelingToolkit.varmap_to_vars( - pre_varmap, parameters(ns); defaults = ModelingToolkit.defaults(ns)) + p_vals = ModelingToolkit.varmap_to_vars(pre_varmap, parameters(ns); + defaults = ModelingToolkit.defaults(ns)) p_dict = Dict(parameters(ns) .=> p_vals) eqs_pars_funcs = vcat(equations(ns), conservedequations(rs)) eqs = map(eq -> substitute(eq.rhs - eq.lhs, p_dict), eqs_pars_funcs) diff --git a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl index 26d4ad1e89..c94be66d2b 100644 --- a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl +++ b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl @@ -31,8 +31,8 @@ function Catalyst.make_si_ode(rs::ReactionSystem; measured_quantities = [], know # Creates a MTK ODESystem, and a list of measured quantities (there are equations). # Gives these to SI to create an SI ode model of its preferred form. osys, conseqs, _ = make_osys(rs; remove_conserved) - measured_quantities = make_measured_quantities( - rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) + measured_quantities = make_measured_quantities( rs, measured_quantities, known_p, conseqs; + ignore_no_measured_warn) return SI.mtk_to_si(osys, measured_quantities)[1] end @@ -63,19 +63,18 @@ Notes: - This function is part of the StructuralIdentifiability.jl extension. StructuralIdentifiability.jl must be imported to access it. - `measured_quantities` and `known_p` input may also be symbolic (e.g. measured_quantities = [rs.X]) """ -function SI.assess_local_identifiability( - rs::ReactionSystem, args...; measured_quantities = [], - known_p = [], funcs_to_check = Vector(), remove_conserved = true, - ignore_no_measured_warn = false, kwargs...) +function SI.assess_local_identifiability(rs::ReactionSystem, args...; + measured_quantities = [], known_p = [], funcs_to_check = Vector(), + remove_conserved = true, ignore_no_measured_warn = false, kwargs...) # Creates a ODESystem, list of measured quantities, and functions to check, of SI's preferred form. osys, conseqs, vars = make_osys(rs; remove_conserved) - measured_quantities = make_measured_quantities( - rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) + measured_quantities = make_measured_quantities( rs, measured_quantities, known_p, conseqs; + ignore_no_measured_warn) funcs_to_check = make_ftc(funcs_to_check, conseqs, vars) # Computes identifiability and converts it to a easy to read form. - out = SI.assess_local_identifiability( - osys, args...; measured_quantities, funcs_to_check, kwargs...) + out = SI.assess_local_identifiability( sys, args...; measured_quantities, + funcs_to_check, kwargs...) return make_output(out, funcs_to_check, reverse.(conseqs)) end @@ -104,19 +103,18 @@ Notes: - This function is part of the StructuralIdentifiability.jl extension. StructuralIdentifiability.jl must be imported to access it. - `measured_quantities` and `known_p` input may also be symbolic (e.g. measured_quantities = [rs.X]) """ -function SI.assess_identifiability( - rs::ReactionSystem, args...; measured_quantities = [], known_p = [], - funcs_to_check = Vector(), remove_conserved = true, - ignore_no_measured_warn = false, kwargs...) +function SI.assess_identifiability( rs::ReactionSystem, args...; + measured_quantities = [], known_p = [], funcs_to_check = Vector(), + remove_conserved = true, ignore_no_measured_warn = false, kwargs...) # Creates a ODESystem, list of measured quantities, and functions to check, of SI's preferred form. osys, conseqs, vars = make_osys(rs; remove_conserved) - measured_quantities = make_measured_quantities( - rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) + measured_quantities = make_measured_quantities(rs, measured_quantities, known_p, conseqs; + ignore_no_measured_warn) funcs_to_check = make_ftc(funcs_to_check, conseqs, vars) # Computes identifiability and converts it to a easy to read form. - out = SI.assess_identifiability( - osys, args...; measured_quantities, funcs_to_check, kwargs...) + out = SI.assess_identifiability(osys, args...; measured_quantities, + funcs_to_check, kwargs...) return make_output(out, funcs_to_check, reverse.(conseqs)) end @@ -145,10 +143,9 @@ Notes: - This function is part of the StructuralIdentifiability.jl extension. StructuralIdentifiability.jl must be imported to access it. - `measured_quantities` and `known_p` input may also be symbolic (e.g. measured_quantities = [rs.X]) """ -function SI.find_identifiable_functions( - rs::ReactionSystem, args...; measured_quantities = [], - known_p = [], remove_conserved = true, ignore_no_measured_warn = false, - kwargs...) +function SI.find_identifiable_functions(rs::ReactionSystem, args...; + measured_quantities = [], known_p = [], remove_conserved = true, + ignore_no_measured_warn = false, kwargs...) # Creates a ODESystem, and list of measured quantities, of SI's preferred form. osys, conseqs = make_osys(rs; remove_conserved) measured_quantities = make_measured_quantities( @@ -166,8 +163,9 @@ end function make_osys(rs::ReactionSystem; remove_conserved = true) # Creates the ODESystem corresponding to the ReactionSystem (expanding functions and flattening it). # Creates a list of the systems all symbols (unknowns and parameters). - ModelingToolkit.iscomplete(rs) || + if !ModelingToolkit.iscomplete(rs) error("Identifiability should only be computed for complete systems. A ReactionSystem can be marked as complete using the `complete` function.") + end rs = complete(Catalyst.expand_registered_functions(flatten(rs))) osys = complete(convert(ODESystem, rs; remove_conserved)) vars = [unknowns(rs); parameters(rs)] diff --git a/src/chemistry_functionality.jl b/src/chemistry_functionality.jl index 768ee66f67..50c551e505 100644 --- a/src/chemistry_functionality.jl +++ b/src/chemistry_functionality.jl @@ -85,8 +85,8 @@ function make_compound(expr) # Loops through all components, add the component and the coefficients to the corresponding vectors # Cannot extract directly using e.g. "getfield.(composition, :reactant)" because then # we get something like :([:C, :O]), rather than :([C, O]). - composition = Catalyst.recursive_find_reactants!( - expr.args[3], 1, Vector{ReactantStruct}(undef, 0)) + composition = Catalyst.recursive_find_reactants!( xpr.args[3], 1, + Vector{ReactantStruct}(undef, 0)) components = :([]) # Becomes something like :([C, O]). coefficients = :([]) # Becomes something like :([1, 2]). for comp in composition @@ -105,8 +105,8 @@ function make_compound(expr) isempty(ivs) && (species_expr = insert_independent_variable(species_expr, :(..))) # Expression which when evaluated gives a vector with all the ivs of the components. - ivs_get_expr = :(unique(reduce( - vcat, [arguments(ModelingToolkit.unwrap(comp)) for comp in $components]))) + ivs_get_expr = :(unique(reduce(vcat, [arguments(ModelingToolkit.unwrap(comp)) + for comp in $components]))) # Creates the found expressions that will create the compound species. # The `Expr(:escape, :(...))` is required so that the expressions are evaluated in @@ -122,12 +122,12 @@ function make_compound(expr) species_declaration_expr = Expr(:escape, :(@species $species_expr)) multiple_ivs_error_check_expr = Expr(:escape, :($(isempty(ivs)) && (length($ivs_get_expr) > 1) && - error($COMPOUND_CREATION_ERROR_DEPENDENT_VAR_REQUIRED))) + error($COMPOUND_CREATION_ERROR_DEPENDENT_VAR_REQUIRED))) iv_designation_expr = Expr(:escape, :($(isempty(ivs)) && ($species_name = $(species_name)($(ivs_get_expr)...)))) iv_check_expr = Expr(:escape, :(issetequal(arguments(ModelingToolkit.unwrap($species_name)), $ivs_get_expr) || - error("The independent variable(S) provided to the compound ($(arguments(ModelingToolkit.unwrap($species_name)))), and those of its components ($($ivs_get_expr)))), are not identical."))) + error("The independent variable(S) provided to the compound ($(arguments(ModelingToolkit.unwrap($species_name)))), and those of its components ($($ivs_get_expr)))), are not identical."))) compound_designation_expr = Expr(:escape, :($species_name = ModelingToolkit.setmetadata( $species_name, Catalyst.CompoundSpecies, true))) @@ -197,7 +197,7 @@ function make_compounds(expr) # The output needs to be converted to Vector{Num} (from Vector{SymbolicUtils.BasicSymbolic{Real}}) to be consistent with e.g. @variables. compound_declarations.args[end] = :([ModelingToolkit.wrap(cmp) - for cmp in $(compound_declarations.args[end])]) + for cmp in $(compound_declarations.args[end])]) # Returns output that. return compound_declarations @@ -264,8 +264,8 @@ function balance_reaction(reaction::Reaction) prodstoich = stoich[(length(reaction.substrates) + 1):end] # Create a new reaction with the balanced stoichiometries - balancedrx = Reaction(reaction.rate, reaction.substrates, - reaction.products, substoich, prodstoich) + balancedrx = Reaction(reaction.rate, reaction.substrates, reaction.products, + substoich, prodstoich) # Add the reaction to the vector of all reactions balancedrxs[i] = balancedrx diff --git a/src/dsl.jl b/src/dsl.jl index 370ab2902d..441f749459 100644 --- a/src/dsl.jl +++ b/src/dsl.jl @@ -87,9 +87,7 @@ macro species(ex...) resize!(vars.args, idx + length(lastarg.args) + 1) for sym in lastarg.args vars.args[idx] = :($sym = ModelingToolkit.wrap(setmetadata( - ModelingToolkit.value($sym), - Catalyst.VariableSpecies, - true))) + ModelingToolkit.value($sym), Catalyst.VariableSpecies, true))) idx += 1 end @@ -419,11 +417,11 @@ function get_reactions(exprs::Vector{Expr}, reactions = Vector{ReactionStruct}(u push_reactions!(reactions, reaction.args[3], reaction.args[2], rate.args[2], metadata.args[2], arrow) elseif in(arrow, fwd_arrows) - push_reactions!( - reactions, reaction.args[2], reaction.args[3], rate, metadata, arrow) + push_reactions!( reactions, reaction.args[2], reaction.args[3], + rate, metadata, arrow) elseif in(arrow, bwd_arrows) - push_reactions!( - reactions, reaction.args[3], reaction.args[2], rate, metadata, arrow) + push_reactions!(reactions, reaction.args[3], reaction.args[2], + rate, metadata, arrow) else throw("Malformed reaction, invalid arrow type used in: $(MacroTools.striplines(line))") end @@ -437,8 +435,9 @@ function read_reaction_line(line::Expr) # Special routine required for the`-->` case, which creates different expression from all other cases. rate = line.args[1] reaction = line.args[2] - (reaction.head == :-->) && - (reaction = Expr(:call, :→, reaction.args[1], reaction.args[2])) + if reaction.head == :--> + reaction = Expr(:call, :→, reaction.args[1], reaction.args[2]) + end arrow = reaction.args[1] # Handles metadata. If not provided, empty metadata is created. @@ -455,9 +454,8 @@ end # Takes a reaction line and creates reaction(s) from it and pushes those to the reaction array. # Used to create multiple reactions from, for instance, `k, (X,Y) --> 0`. -function push_reactions!( - reactions::Vector{ReactionStruct}, sub_line::ExprValues, prod_line::ExprValues, - rate::ExprValues, metadata::ExprValues, arrow::Symbol) +function push_reactions!(reactions::Vector{ReactionStruct}, sub_line::ExprValues, + prod_line::ExprValues, rate::ExprValues, metadata::ExprValues, arrow::Symbol) # The rates, substrates, products, and metadata may be in a tupple form (e.g. `k, (X,Y) --> 0`). # This finds the length of these tuples (or 1 if not in tuple forms). Errors if lengs inconsistent. lengs = (tup_leng(sub_line), tup_leng(prod_line), tup_leng(rate), tup_leng(metadata)) @@ -478,9 +476,8 @@ function push_reactions!( error("Some reaction metadata fields where repeated: $(metadata_entries)") end - push!(reactions, - ReactionStruct(get_tup_arg(sub_line, i), get_tup_arg(prod_line, i), - get_tup_arg(rate, i), metadata_i)) + push!(reactions, ReactionStruct(get_tup_arg(sub_line, i), + get_tup_arg(prod_line, i), get_tup_arg(rate, i), metadata_i)) end end @@ -630,8 +627,9 @@ end # Read the events (continious or discrete) provided as options to the DSL. Returns an expression which evalutes to these. function read_events_option(options, event_type::Symbol) # Prepares the events, if required to, converts them to block form. - (event_type in [:continuous_events, :discrete_events]) || + if event_type ∉ [:continuous_events, :discrete_events] error("Trying to read an unsupported event type.") + end events_input = haskey(options, event_type) ? options[event_type].args[3] : MacroTools.striplines(:(begin end)) events_input = option_block_form(events_input) @@ -642,11 +640,11 @@ function read_events_option(options, event_type::Symbol) # Formatting error checks. # NOTE: Maybe we should move these deeper into the system (rather than the DSL), throwing errors more generally? if (arg isa Expr) && (arg.head != :call) || (arg.args[1] != :(=>)) || - length(arg.args) != 3 + (length(arg.args) != 3) error("Events should be on form `condition => affect`, separated by a `=>`. This appears not to be the case for: $(arg).") end if (arg isa Expr) && (arg.args[2] isa Expr) && (arg.args[2].head != :vect) && - (event_type == :continuous_events) + (event_type == :continuous_events) error("The condition part of continious events (the left-hand side) must be a vector. This is not the case for: $(arg).") end if (arg isa Expr) && (arg.args[3] isa Expr) && (arg.args[3].head != :vect) @@ -670,8 +668,8 @@ function read_equations_options(options, variables_declared) eqs_input = haskey(options, :equations) ? options[:equations].args[3] : :(begin end) eqs_input = option_block_form(eqs_input) equations = Expr[] - ModelingToolkit.parse_equations!( - Expr(:block), equations, Dict{Symbol, Any}(), eqs_input) + ModelingToolkit.parse_equations!(Expr(:block), equations, + Dict{Symbol, Any}(), eqs_input) # Loops through all equations, checks for lhs of the form `D(X) ~ ...`. # When this is the case, the variable X and differential D are extracted (for automatic declaration). @@ -689,7 +687,7 @@ function read_equations_options(options, variables_declared) lhs = eq.args[2] # if lhs: is an expression. Is a function call. The function's name is D. Calls a single symbol. if (lhs isa Expr) && (lhs.head == :call) && (lhs.args[1] == :D) && - (lhs.args[2] isa Symbol) + (lhs.args[2] isa Symbol) diff_var = lhs.args[2] if in(diff_var, forbidden_symbols_error) error("A forbidden symbol ($(diff_var)) was used as an variable in this differential equation: $eq") @@ -749,7 +747,7 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted) error("An observable ($obs_name) was given independent variable(s). These should not be given, as they are inferred automatically.") isnothing(defaults) || error("An observable ($obs_name) was given a default value. This is forbidden.") - in(obs_name, forbidden_symbols_error) && + (obs_name in forbidden_symbols_error) && error("A forbidden symbol ($(obs_eq.args[2])) was used as an observable name.") # Error checks. @@ -757,7 +755,7 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted) error("An interpoalted observable have been used, which has also been explicitly delcared within the system using eitehr @species or @variables. This is not permited.") end if ((obs_name in species_n_vars_declared) || is_escaped_expr(obs_eq.args[2])) && - !isnothing(metadata) + !isnothing(metadata) error("Metadata was provided to observable $obs_name in the `@observables` macro. However, the obervable was also declared separately (using either @species or @variables). When this is done, metadata should instead be provided within the original @species or @variable declaration.") end @@ -773,10 +771,10 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted) # Adds a line to the `observed_vars` expression, setting the ivs for this observable. # Cannot extract directly using e.g. "getfield.(dependants_structs, :reactant)" because # then we get something like :([:X1, :X2]), rather than :([X1, X2]). - dep_var_expr = :(filter( - !MT.isparameter, Symbolics.get_variables($(obs_eq.args[3])))) - ivs_get_expr = :(unique(reduce( - vcat, [arguments(MT.unwrap(dep)) for dep in $dep_var_expr]))) + dep_var_expr = :(filter(!MT.isparameter, + Symbolics.get_variables($(obs_eq.args[3])))) + ivs_get_expr = :(unique(reduce(vcat, [arguments(MT.unwrap(dep)) + for dep in $dep_var_expr]))) ivs_get_expr_sorted = :(sort($(ivs_get_expr); by = iv -> findfirst(MT.getname(iv) == ivs for ivs in $ivs_sorted))) push!(observed_vars.args, diff --git a/src/expression_utils.jl b/src/expression_utils.jl index b5d93e5c0d..a475677468 100644 --- a/src/expression_utils.jl +++ b/src/expression_utils.jl @@ -82,7 +82,7 @@ function find_varinfo_in_declaration(expr) # Case: X(t) = 1.0 (expr.args[1].head == :call) && (return expr.args[1].args[1], expr.args[1].args[2:end], expr.args[2].args[1], - nothing) + nothing) end if expr.head == :tuple # Case: X, [metadata=true] @@ -90,7 +90,7 @@ function find_varinfo_in_declaration(expr) # Case: X(t), [metadata=true] (expr.args[1].head == :call) && (return expr.args[1].args[1], expr.args[1].args[2:end], nothing, expr.args[2]) - if (expr.args[1].head == :(=)) + if expr.args[1].head == :(=) # Case: X = 1.0, [metadata=true] (expr.args[1].args[1] isa Symbol) && (return expr.args[1].args[1], [], expr.args[1].args[2], expr.args[2]) diff --git a/src/reaction.jl b/src/reaction.jl index 2f3d6251a6..f5ca98f67c 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -367,9 +367,6 @@ encountered in: - Among potential noise scaling metadata. """ function ModelingToolkit.get_variables!(set, rx::Reaction) - if isdefined(Main, :Infiltrator) - Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__) - end get_variables!(set, rx.rate) foreach(sub -> push!(set, sub), rx.substrates) foreach(prod -> push!(set, prod), rx.products) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 917e0284ea..4aa5169166 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -462,9 +462,9 @@ end # the model creation) and creates the corresponding vectors. # While species are ordered before variables in the unknowns vector, this ordering is not imposed here, # but carried out at a later stage. -function make_ReactionSystem_internal( - rxs_and_eqs::Vector, iv, us_in, ps_in; spatial_ivs = nothing, - continuous_events = [], discrete_events = [], observed = [], kwargs...) +function make_ReactionSystem_internal(rxs_and_eqs::Vector, iv, us_in, ps_in; + spatial_ivs = nothing, continuous_events = [], discrete_events = [], + observed = [], kwargs...) # Filters away any potential obervables from `states` and `spcs`. obs_vars = [obs_eq.lhs for obs_eq in observed] diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 75ac1ef0a6..8a5d772220 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -607,8 +607,8 @@ function Base.convert(::Type{<:SDESystem}, rs::ReactionSystem; ists, ispcs = get_indep_sts(flatrs, remove_conserved) eqs = assemble_drift(flatrs, ispcs; combinatoric_ratelaws, include_zero_odes, remove_conserved) - noiseeqs = assemble_diffusion( - flatrs, ists, ispcs; combinatoric_ratelaws, remove_conserved) + noiseeqs = assemble_diffusion(flatrs, ists, ispcs; + combinatoric_ratelaws, remove_conserved) eqs, us, ps, obs, defs = addconstraints!(eqs, flatrs, ists, ispcs; remove_conserved) if any(isbc, get_unknowns(flatrs)) diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 319045dcb7..4307820fbb 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -69,8 +69,8 @@ end # Converts a numeric expression (e.g. p*X + 2Y) to a string (e.g. "p*X + 2Y"). Also ensures that for # any variables (e.g. X(t)) the call part is stripped, and only variable name (e.g. X) is written. -function expression_2_string( - expr; strip_call_dict = make_strip_call_dict(Symbolics.get_variables(expr))) +function expression_2_string(expr; + strip_call_dict = make_strip_call_dict(Symbolics.get_variables(expr))) strip_called_expr = substitute(expr, strip_call_dict) return repr(strip_called_expr) end @@ -89,8 +89,8 @@ function syms_2_declaration_string(syms; multiline_format = false) decs_string = (multiline_format ? " begin" : "") for sym in syms delimiter = (multiline_format ? "\n\t" : " ") - @string_append! decs_string delimiter sym_2_declaration_string( - sym; multiline_format) + @string_append! decs_string delimiter sym_2_declaration_string(sym; + multiline_format) end multiline_format && (@string_append! decs_string "\nend") return decs_string @@ -109,7 +109,7 @@ function sym_2_declaration_string(sym; multiline_format = false) if !(sym isa SymbolicUtils.BasicSymbolic{Real}) sym_type = String(Symbol(typeof(Symbolics.unwrap(sym)))) if (get_substring(sym_type, 1, 28) != "SymbolicUtils.BasicSymbolic{") || - (get_char_end(sym_type, 0) != '}') + (get_char_end(sym_type, 0) != '}') error("Encountered symbolic of unexpected type: $sym_type.") end @string_append! dec_string "::" get_substring_end(sym_type, 29, -1) diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index bb2110db3f..ea14661696 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -43,8 +43,8 @@ SIVS_FS = (has_sivs, get_sivs_string, get_sivs_annotation) # Function which handles the addition of species, variable, and parameter declarations to the file # text. These must be handled as a unity in case there are default value dependencies between these. -function handle_us_n_ps( - file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) +function handle_us_n_ps(file_text::String, rn::ReactionSystem, + annotate::Bool, top_level::Bool) # Fetches the systems parameters, species, and variables. Computes the `has_` `Bool`s. ps_all = get_ps(rn) sps_all = get_species(rn) @@ -102,12 +102,12 @@ function handle_us_n_ps( while !(isempty(remaining_ps) && isempty(remaining_sps) && isempty(remaining_vars)) # Checks which parameters/species/variables can be written. The `dependency_split` # function updates the `remaining_` input. - writable_ps = dependency_split!( - remaining_ps, [remaining_ps; remaining_sps; remaining_vars]) - writable_sps = dependency_split!( - remaining_sps, [remaining_ps; remaining_sps; remaining_vars]) - writable_vars = dependency_split!( - remaining_vars, [remaining_ps; remaining_sps; remaining_vars]) + writable_ps = dependency_split!(remaining_ps, + [remaining_ps; remaining_sps; remaining_vars]) + writable_sps = dependency_split!(remaining_sps, + [remaining_ps; remaining_sps; remaining_vars]) + writable_vars = dependency_split!(remaining_vars, + [remaining_ps; remaining_sps; remaining_vars]) # Writes those that can be written. isempty(writable_ps) || @@ -409,8 +409,8 @@ function get_continuous_events_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -CONTINUOUS_EVENTS_FS = ( - has_continuous_events, get_continuous_events_string, get_continuous_events_annotation) +CONTINUOUS_EVENTS_FS = (has_continuous_events, get_continuous_events_string, + get_continuous_events_annotation) ### Handles Discrete Events ### @@ -466,16 +466,16 @@ function get_discrete_events_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -DISCRETE_EVENTS_FS = ( - has_discrete_events, get_discrete_events_string, get_discrete_events_annotation) +DISCRETE_EVENTS_FS = (has_discrete_events, get_discrete_events_string, + get_discrete_events_annotation) ### Handles Systems ### # Specific `push_field` function, which is used for the system field (where the annotation option # must be passed to the `get_component_string` function). Since non-ReactionSystem systems cannot be # written to file, this functions throws an error if any such systems are encountered. -function push_systems_field( - file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) +function push_systems_field(file_text::String, rn::ReactionSystem, + annotate::Bool, top_level::Bool) # Checks whther there are any subsystems, and if these are ReactionSystems. has_systems(rn) || (return (file_text, false)) if any(!(system isa ReactionSystem) for system in MT.get_systems(rn)) @@ -502,8 +502,9 @@ function get_systems_string(rn::ReactionSystem, annotate::Bool) # Loops through all systems, adding their declaration to the system string. for (idx, system) in enumerate(MT.get_systems(rn)) - annotate && - (@string_append! systems_string "\n\n# Declares subsystem: $(getname(system))") + if annotate + @string_append! systems_string "\n\n# Declares subsystem: $(getname(system))" + end # Manipulates the subsystem declaration to make it nicer. subsystem_string = get_full_system_string(system, annotate, false) @@ -541,5 +542,5 @@ function get_connection_type_annotation(rn::ReactionSystem) end # Combines the 3 connection types-related functions in a constant tuple. -CONNECTION_TYPE_FS = ( - has_connection_type, get_connection_type_string, get_connection_type_annotation) +CONNECTION_TYPE_FS = (has_connection_type, get_connection_type_string, + get_connection_type_annotation) diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index 2afeb4bb50..4eb8b74793 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -32,8 +32,8 @@ Notes: - Reaction systems with components that have units cannot currently be saved. - The `ReactionSystem` is saved using *programmatic* (not DSL) format for model creation. """ -function save_reactionsystem( - filename::String, rn::ReactionSystem; annotate = true, safety_check = true) +function save_reactionsystem(filename::String, rn::ReactionSystem; + annotate = true, safety_check = true) reactionsystem_uptodate_check() open(filename, "w") do file write(file, get_full_system_string(rn, annotate, true)) @@ -65,22 +65,23 @@ function get_full_system_string(rn::ReactionSystem, annotate::Bool, top_level::B file_text, has_reactions = push_field(file_text, rn, annotate, top_level, REACTIONS_FS) file_text, has_equations = push_field(file_text, rn, annotate, top_level, EQUATIONS_FS) file_text, has_observed = push_field(file_text, rn, annotate, top_level, OBSERVED_FS) - file_text, has_continuous_events = push_field( - file_text, rn, annotate, top_level, CONTINUOUS_EVENTS_FS) - file_text, has_discrete_events = push_field( - file_text, rn, annotate, top_level, DISCRETE_EVENTS_FS) + file_text, has_continuous_events = push_field(file_text, rn, annotate, + top_level, CONTINUOUS_EVENTS_FS) + file_text, has_discrete_events = push_field(file_text, rn, annotate, + top_level, DISCRETE_EVENTS_FS) file_text, has_systems = push_systems_field(file_text, rn, annotate, top_level) - file_text, has_connection_type = push_field( - file_text, rn, annotate, top_level, CONNECTION_TYPE_FS) + file_text, has_connection_type = push_field(file_text, rn, annotate, + top_level, CONNECTION_TYPE_FS) # Finalises the system. Creates the final `ReactionSystem` call. # Enclose everything ing a `let ... end` block. - rs_creation_code = make_reaction_system_call( - rn, annotate, top_level, has_sivs, has_species, - has_variables, has_parameters, has_reactions, - has_equations, has_observed, has_continuous_events, - has_discrete_events, has_systems, has_connection_type) - annotate || (@string_prepend! "\n" file_text) + rs_creation_code = make_reaction_system_call(rn, annotate, top_level, + has_sivs, has_species, has_variables, has_parameters, has_reactions, + has_equations, has_observed, has_continuous_events, has_discrete_events, + has_systems, has_connection_type) + if !annotate + @string_prepend! "\n" file_text + end @string_prepend! "let" file_text @string_append! file_text "\n\n" rs_creation_code "\n\nend" @@ -147,12 +148,14 @@ function make_reaction_system_call( has_connection_type && (@string_append! reaction_system_string ", connection_type") # Potentially appends a combinatoric_ratelaws statement. - Symbolics.unwrap(rs.combinatoric_ratelaws) || - (@string_append! reaction_system_string ", combinatoric_ratelaws = false") + if !Symbolics.unwrap(rs.combinatoric_ratelaws) + @string_append! reaction_system_string ", combinatoric_ratelaws = false" + end # Potentially appends `ReactionSystem` metadata value(s). Weird composite types are not supported. - isnothing(rs.metadata) || - (@string_append! reaction_system_string ", metadata = $(x_2_string(rs.metadata))") + if !isnothing(rs.metadata) + @string_append! reaction_system_string ", metadata = $(x_2_string(rs.metadata))" + end # Finalises the call. Appends potential annotation. If the system is complete, add a call for this. @string_append! reaction_system_string ")" diff --git a/src/registered_functions.jl b/src/registered_functions.jl index 934cb202f7..d3baa6cf0b 100644 --- a/src/registered_functions.jl +++ b/src/registered_functions.jl @@ -138,9 +138,8 @@ function expand_registered_functions(expr) end # If applied to a Reaction, return a reaction with its rate modified. function expand_registered_functions(rx::Reaction) - Reaction( - expand_registered_functions(rx.rate), rx.substrates, rx.products, rx.substoich, - rx.prodstoich, rx.netstoich, rx.only_use_rate, rx.metadata) + Reaction(expand_registered_functions(rx.rate), rx.substrates, rx.products, + rx.substoich, rx.prodstoich, rx.netstoich, rx.only_use_rate, rx.metadata) end # If applied to a Equation, returns it with it applied to lhs and rhs function expand_registered_functions(eq::Equation) diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 51b106da32..4d2453b8d5 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -20,8 +20,8 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. # These elements are length 1 vectors (if the parameter is uniform), # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). - vert_ps, edge_ps = lattice_process_p( - p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), + edge_parameters(lrs), lrs) # Returns a DiscreteProblem. # Previously, a Tuple was used for (vert_ps, edge_ps), but this was converted to a Vector internally. @@ -29,8 +29,8 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, end # Builds a spatial JumpProblem from a DiscreteProblem containg a Lattice Reaction System. -function JumpProcesses.JumpProblem( - lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(lrs.rs), +function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, + args...; name = nameof(lrs.rs), combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), kwargs...) # Error checks. if !isnothing(dprob.f.sys) @@ -83,8 +83,8 @@ end # Not sure if there is any form of performance improvement from that though. Possibly is not the case. function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) # Creates a vector, storing which reactions have spatial components. - is_spatials = [Catalyst.has_spatial_vertex_component( - rx.rate, lrs; vert_ps = dprob.p[1]) for rx in reactions(lrs.rs)] + is_spatials = [Catalyst.has_spatial_vertex_component(rx.rate, lrs; + vert_ps = dprob.p[1]) for rx in reactions(lrs.rs)] # Creates templates for the rates (uniform and spatial) and the stoichiometries. # We cannot fetch reactant_stoich and net_stoich from a (non-spatial) MassActionJump. @@ -107,8 +107,8 @@ function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) # Loops through reactions with spatial rates, computes their rates and stoichiometries. for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs)) is_spat || continue - s_rates[cur_rx - length(u_rates), :] = compute_vertex_value( - rx.rate, lrs; vert_ps = dprob.p[1]) + s_rates[cur_rx - length(u_rates), :] = compute_vertex_value(rx.rate, lrs; + vert_ps = dprob.p[1]) substoich_map = Pair.(rx.substrates, rx.substoich) reactant_stoich[cur_rx] = int_map(substoich_map, lrs.rs) net_stoich[cur_rx] = int_map(rx.netstoich, lrs.rs) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 695bb2a28e..1665a726a5 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -47,8 +47,8 @@ struct LatticeReactionSystem{S, T} # <: MT.AbstractTimeDependentSystem if isempty(spatial_reactions) spat_species = Vector{BasicSymbolic{Real}}[] else - spat_species = unique(reduce( - vcat, [spatial_species(sr) for sr in spatial_reactions])) + spat_species = unique(reduce(vcat, + [spatial_species(sr) for sr in spatial_reactions])) end num_species = length(unique([species(rs); spat_species])) rs_edge_parameters = filter(isedgeparameter, parameters(rs)) @@ -63,9 +63,9 @@ struct LatticeReactionSystem{S, T} # <: MT.AbstractTimeDependentSystem # Ensures the parameter order begins similarly to in the non-spatial ReactionSystem. ps = [parameters(rs); setdiff([edge_parameters; vertex_parameters], parameters(rs))] - foreach( - sr -> check_spatial_reaction_validity(rs, sr; edge_parameters = edge_parameters), - spatial_reactions) + for sr in spatial_reactions + check_spatial_reaction_validity(rs, sr; edge_parameters = edge_parameters) + end return new{S, T}( rs, spatial_reactions, lattice, nv(lattice), ne(lattice), num_species, init_digraph, spat_species, ps, vertex_parameters, edge_parameters) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 14f48fa53b..d1266e4dfd 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -61,9 +61,8 @@ struct LatticeTransportODEf{Q, R, S, T} # 1 if ps are constant across the graph, 0 else. v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) eds = edges(lrs.lattice) - new{Q, R, typeof(eds), T}( - ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, - v_ps_idx_types, transport_rates, leaving_rates, eds, edge_ps) + new{Q, R, typeof(eds), T}( ofunc, lrs.num_verts, lrs.num_species, vert_ps, + work_vert_ps, v_ps_idx_types, transport_rates, leaving_rates, eds, edge_ps) end end @@ -160,20 +159,20 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) # Prepares the Jacobian and forcing functions (depending on jacobian and sparsity selection). - osys = complete(convert( - ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks)) + osys = complete(convert(ODESystem, lrs.rs; + name, combinatoric_ratelaws, include_zero_odes, checks)) if jac # `build_jac_prototype` currently assumes a sparse (non-spatial) Jacobian. Hence compute this. # `LatticeTransportODEjac` currently assumes a dense (non-spatial) Jacobian. Hence compute this. # Long term we could write separate version of these functions for generic input. ofunc_dense = ODEFunction(osys; jac = true, sparse = false) ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) - jac_vals = build_jac_prototype( - ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) + jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; + set_nonzero = true) if sparse f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) - jac_vals = build_jac_prototype( - ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) + jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; + set_nonzero = true) J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, edge_ps, true) jac_prototype = jac_vals else @@ -185,8 +184,8 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} if sparse ofunc_sparse = ODEFunction(osys; jac = false, sparse = true) f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) - jac_prototype = build_jac_prototype( - ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = false) + jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, + lrs; set_nonzero = false) else ofunc_dense = ODEFunction(osys; jac = false, sparse = false) f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, edge_ps, lrs) @@ -306,8 +305,8 @@ function (jac_func::LatticeTransportODEjac)(J, u, p, t) # Update the Jacobian from reaction terms for vert_i in 1:(jac_func.num_verts) idxs = get_indexes(vert_i, jac_func.num_species) - vert_ps = view_vert_ps_vector!( - jac_func.work_vert_ps, p, vert_i, enumerate(jac_func.v_ps_idx_types)) + vert_ps = view_vert_ps_vector!(jac_func.work_vert_ps, p, vert_i, + enumerate(jac_func.v_ps_idx_types)) jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), vert_ps, t) end diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index daf3f48bf9..5779f8bebd 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -29,9 +29,8 @@ struct TransportReaction <: AbstractSpatialReaction # Creates a diffusion reaction. function TransportReaction(rate, species) - if any(!ModelingToolkit.isparameter(var) - for var in ModelingToolkit.get_variables(rate)) - error("TransportReaction rate contains variables: $(filter(var -> !ModelingToolkit.isparameter(var), ModelingToolkit.get_variables(rate))). The rate must consist of parameters only.") + if any(!MT.isparameter(var) for var in MT.get_variables(rate)) + error("TransportReaction rate contains variables: $(filter(var -> !MT.isparameter(var), MT.get_variables(rate))). The rate must consist of parameters only.") end new(rate, species.val) end @@ -78,8 +77,8 @@ ModelingToolkit.parameters(tr::TransportReaction) = Symbolics.get_variables(tr.r spatial_species(tr::TransportReaction) = [tr.species] # Checks that a transport reaction is valid for a given reaction system. -function check_spatial_reaction_validity( - rs::ReactionSystem, tr::TransportReaction; edge_parameters = []) +function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction; + edge_parameters = []) # Checks that the species exist in the reaction system. # (ODE simulation code becomes difficult if this is not required, # as non-spatial jacobian and f function generated from rs is of wrong size). @@ -97,15 +96,15 @@ function check_spatial_reaction_validity( if any(isequal(tr.species, s) && !isequivalent(tr.species, s) for s in species(rs)) error("A transport reaction used a species, $(tr.species), with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") end - if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) - for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)) + if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) for rs_p in parameters(rs), + tr_p in Symbolics.get_variables(tr.rate)) error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") end # Checks that no edge parameter occur among rates of non-spatial reactions. if any(!isempty(intersect(Symbolics.get_variables(r.rate), edge_parameters)) - for r in reactions(rs)) - error("Edge paramter(s) were found as a rate of a non-spatial reaction.") + for r in reactions(rs)) + error("Edge parameter(s) were found as a rate of a non-spatial reaction.") end end @@ -114,11 +113,13 @@ end const ep_metadata = Catalyst.EdgeParameter => true function isequivalent(sym1, sym2) isequal(sym1, sym2) || (return false) - any((md1 != ep_metadata) && !(md1 in sym2.metadata) for md1 in sym1.metadata) && - (return false) - any((md2 != ep_metadata) && !(md2 in sym1.metadata) for md2 in sym2.metadata) && - (return false) - (typeof(sym1) != typeof(sym2)) && (return false) + if any((md1 != ep_metadata) && !(md1 in sym2.metadata) for md1 in sym1.metadata) + return false + elseif any((md2 != ep_metadata) && !(md2 in sym1.metadata) for md2 in sym2.metadata) + return false + elseif typeof(sym1) != typeof(sym2) + return false + end return true end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index d798d933f4..b7f34b8fc5 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -94,8 +94,8 @@ end # If the input is given in a map form, the vector needs sorting and the first value removed. # The creates a Vector{Vector{Value}} or Vector{value} form, which is then again sent to lattice_process_input for reprocessing. -function lattice_process_input( - input::Vector{<:Pair}, syms::Vector{BasicSymbolic{Real}}, args...) +function lattice_process_input(input::Vector{<:Pair}, syms::Vector{BasicSymbolic{Real}}, + args...) if !isempty(setdiff(first.(input), syms)) error("Some input symbols are not recognised: $(setdiff(first.(input), syms)).") end @@ -119,9 +119,9 @@ function lattice_process_input(input::Vector{<:Any}, args...) args...) end # If the input is of the correct form already, return it. -function lattice_process_input( - input::Vector{<:Vector}, syms::Vector{BasicSymbolic{Real}}, n::Int64) - input +function lattice_process_input(input::Vector{<:Vector}, syms::Vector{BasicSymbolic{Real}}, + n::Int64) + return input end # Checks that a value vector have the right length, as well as that of all its sub vectors. @@ -142,8 +142,8 @@ end # If transport parameters are given with n values, we want to use the same value for both directions. # Since the order of edges in the new graph is non-trivial, this function # distributes the n input values to a 2n length vector, putting the correct value in each position. -function duplicate_trans_params!( - edge_ps::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) +function duplicate_trans_params!(edge_ps::Vector{Vector{Float64}}, + lrs::LatticeReactionSystem) cum_adjacency_counts = [0; cumsum(length.(lrs.lattice.fadjlist[1:(end - 1)]))] for idx in 1:length(edge_ps) # If the edge parameter already values for each directed edge, we can continue. @@ -235,14 +235,11 @@ end # If the rate is uniform across all edges, the vector will be length 1 (with this value), # else there will be a separate value for each edge. # Pair{Int64, Vector{T}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). -function make_sidxs_to_transrate_map( - vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{T}}, - lrs::LatticeReactionSystem) where {T} +function make_sidxs_to_transrate_map(vert_ps::Vector{Vector{Float64}}, + edge_ps::Vector{Vector{T}}, lrs::LatticeReactionSystem) where {T} transport_rates_speciesmap = compute_all_transport_rates(vert_ps, edge_ps, lrs) - return Pair{Int64, Vector{T}}[ - speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] - for spat_rates in transport_rates_speciesmap - ] + return Pair{Int64, Vector{T}}[speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] + for spat_rates in transport_rates_speciesmap] end ### Accessing Unknown & Parameter Array Values ### @@ -251,7 +248,7 @@ end get_index(vert::Int64, s::Int64, num_species::Int64) = (vert - 1) * num_species + s # Gets the indexes in the u array of all species in vertex vert (when their are num_species species). function get_indexes(vert::Int64, num_species::Int64) - ((vert - 1) * num_species + 1):(vert * num_species) + return ((vert - 1) * num_species + 1):(vert * num_species) end # For vectors of length 1 or n, we want to get value idx (or the one value, if length is 1). @@ -265,31 +262,31 @@ end # The first two function takes the full value vector, and call the function of at the components specific index. function get_component_value(values::Vector{<:Vector}, component_idx::Int64, location_idx::Int64) - get_component_value(values[component_idx], location_idx) + return get_component_value(values[component_idx], location_idx) end # Sometimes we have pre-computed, for each component, whether it's vector is length 1 or not. # This is stored in location_types. function get_component_value(values::Vector{<:Vector}, component_idx::Int64, location_idx::Int64, location_types::Vector{Bool}) - get_component_value(values[component_idx], location_idx, location_types[component_idx]) + return get_component_value(values[component_idx], location_idx, location_types[component_idx]) end # For a components value (which is a vector of either length 1 or some other length), retrieves its value. function get_component_value(values::Vector{<:Number}, location_idx::Int64) - get_component_value(values, location_idx, length(values) == 1) + return get_component_value(values, location_idx, length(values) == 1) end # Again, the location type (length of the value vector) may be pre-computed. function get_component_value(values::Vector{<:Number}, location_idx::Int64, location_type::Bool) - location_type ? values[1] : values[location_idx] + return location_type ? values[1] : values[location_idx] end # Converts a vector of vectors to a long vector. # These are used when the initial condition is converted to a single vector (from vector of vector form). function expand_component_values(values::Vector{<:Vector}, n) - vcat([get_component_value.(values, comp) for comp in 1:n]...) + return vcat([get_component_value.(values, comp) for comp in 1:n]...) end function expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) - vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) + return vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) end # Creates a view of the vert_ps vector at a given location. @@ -308,7 +305,7 @@ end # Expands a u0/p information stored in Vector{Vector{}} for to Matrix form # (currently only used in Spatial Jump systems). function matrix_expand_component_values(values::Vector{<:Vector}, n) - reshape(expand_component_values(values, n), length(values), n) + return reshape(expand_component_values(values, n), length(values), n) end # For an expression, computes its values using the provided state and parameter vectors. @@ -388,8 +385,8 @@ end # For a Symbolic expression, a LatticeReactionSystem, and a parameter list of the internal format (vector of vectors): # Checks if any vertex parameter in the expression have a spatial component (that is, is not uniform). -function has_spatial_vertex_component( - exp, lrs::LatticeReactionSystem; u = nothing, vert_ps = nothing) +function has_spatial_vertex_component(exp, lrs::LatticeReactionSystem; + u = nothing, vert_ps = nothing) # Finds all the symbols in the expression. exp_syms = Symbolics.get_variables(exp) diff --git a/src/steady_state_stability.jl b/src/steady_state_stability.jl index 8227d41179..8103d4c61f 100644 --- a/src/steady_state_stability.jl +++ b/src/steady_state_stability.jl @@ -44,9 +44,8 @@ these. Furthermore, Catalyst uses a tolerance `tol = 10*sqrt(eps())` to determin computed eigenvalue is far away enough from 0 to be reliably used. This selected threshold can be changed through the `tol` argument. ``` """ -function steady_state_stability( - u::Vector, rs::ReactionSystem, ps; tol = 10 * sqrt(eps(ss_val_type(u))), - ss_jac = steady_state_jac(rs; u0 = u)) +function steady_state_stability(u::Vector, rs::ReactionSystem, ps; + tol = 10 * sqrt(eps(ss_val_type(u))), ss_jac = steady_state_jac(rs; u0 = u)) # Warning checks. if !isautonomous(rs) error("Attempting to compute stability for a non-autonomous system (e.g. where some rate depend on $(rs.iv)). This is not possible.") From d60cb785600f4c33c4973a2b0dadc3398ac58242 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 17:34:37 -0400 Subject: [PATCH 211/446] round 2 --- .../bifurcation_kit_extension.jl | 2 +- .../homotopy_continuation_extension.jl | 2 +- .../structural_identifiability_extension.jl | 28 +++++++++---------- src/chemistry_functionality.jl | 10 +++---- src/dsl.jl | 16 +++++------ src/expression_utils.jl | 2 +- src/reactionsystem.jl | 2 +- src/reactionsystem_conversions.jl | 2 +- .../serialisation_support.jl | 6 ++-- .../serialise_fields.jl | 16 +++++------ .../serialise_reactionsystem.jl | 12 ++++---- src/registered_functions.jl | 2 +- .../lattice_jump_systems.jl | 12 ++++---- .../lattice_reaction_systems.jl | 2 +- .../spatial_ODE_systems.jl | 4 +-- .../spatial_reactions.jl | 6 ++-- src/spatial_reaction_systems/utility.jl | 3 +- 17 files changed, 64 insertions(+), 63 deletions(-) diff --git a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl index 412caadb3a..af2c0bb47e 100644 --- a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl +++ b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl @@ -26,6 +26,6 @@ function BK.BifurcationProblem(rs::ReactionSystem, u0_bif, ps, bif_par, args...; nsys = complete(nsys) # Makes BifurcationProblem (this call goes through the ModelingToolkit-based BifurcationKit extension). - return BK.BifurcationProblem(nsys, u0_bif, ps, bif_par, args...; plot_var, + return BK.BifurcationProblem(nsys, u0_bif, ps, bif_par, args...; plot_var, record_from_solution, jac, kwargs...) end diff --git a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl index 66864f93b9..91e868c25e 100644 --- a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl +++ b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl @@ -52,7 +52,7 @@ function steady_state_polynomial(rs::ReactionSystem, ps, u0) ns = complete(convert(NonlinearSystem, rs; remove_conserved = true)) pre_varmap = [symmap_to_varmap(rs, u0)..., symmap_to_varmap(rs, ps)...] Catalyst.conservationlaw_errorcheck(rs, pre_varmap) - p_vals = ModelingToolkit.varmap_to_vars(pre_varmap, parameters(ns); + p_vals = ModelingToolkit.varmap_to_vars(pre_varmap, parameters(ns); defaults = ModelingToolkit.defaults(ns)) p_dict = Dict(parameters(ns) .=> p_vals) eqs_pars_funcs = vcat(equations(ns), conservedequations(rs)) diff --git a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl index c94be66d2b..d790db263c 100644 --- a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl +++ b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl @@ -31,8 +31,8 @@ function Catalyst.make_si_ode(rs::ReactionSystem; measured_quantities = [], know # Creates a MTK ODESystem, and a list of measured quantities (there are equations). # Gives these to SI to create an SI ode model of its preferred form. osys, conseqs, _ = make_osys(rs; remove_conserved) - measured_quantities = make_measured_quantities( rs, measured_quantities, known_p, conseqs; - ignore_no_measured_warn) + measured_quantities = make_measured_quantities(rs, measured_quantities, known_p, + conseqs; ignore_no_measured_warn) return SI.mtk_to_si(osys, measured_quantities)[1] end @@ -64,16 +64,16 @@ Notes: - `measured_quantities` and `known_p` input may also be symbolic (e.g. measured_quantities = [rs.X]) """ function SI.assess_local_identifiability(rs::ReactionSystem, args...; - measured_quantities = [], known_p = [], funcs_to_check = Vector(), + measured_quantities = [], known_p = [], funcs_to_check = Vector(), remove_conserved = true, ignore_no_measured_warn = false, kwargs...) # Creates a ODESystem, list of measured quantities, and functions to check, of SI's preferred form. osys, conseqs, vars = make_osys(rs; remove_conserved) - measured_quantities = make_measured_quantities( rs, measured_quantities, known_p, conseqs; - ignore_no_measured_warn) + measured_quantities = make_measured_quantities(rs, measured_quantities, known_p, + conseqs; ignore_no_measured_warn) funcs_to_check = make_ftc(funcs_to_check, conseqs, vars) # Computes identifiability and converts it to a easy to read form. - out = SI.assess_local_identifiability( sys, args...; measured_quantities, + out = SI.assess_local_identifiability(sys, args...; measured_quantities, funcs_to_check, kwargs...) return make_output(out, funcs_to_check, reverse.(conseqs)) end @@ -103,13 +103,13 @@ Notes: - This function is part of the StructuralIdentifiability.jl extension. StructuralIdentifiability.jl must be imported to access it. - `measured_quantities` and `known_p` input may also be symbolic (e.g. measured_quantities = [rs.X]) """ -function SI.assess_identifiability( rs::ReactionSystem, args...; - measured_quantities = [], known_p = [], funcs_to_check = Vector(), +function SI.assess_identifiability(rs::ReactionSystem, args...; + measured_quantities = [], known_p = [], funcs_to_check = Vector(), remove_conserved = true, ignore_no_measured_warn = false, kwargs...) # Creates a ODESystem, list of measured quantities, and functions to check, of SI's preferred form. osys, conseqs, vars = make_osys(rs; remove_conserved) - measured_quantities = make_measured_quantities(rs, measured_quantities, known_p, conseqs; - ignore_no_measured_warn) + measured_quantities = make_measured_quantities(rs, measured_quantities, known_p, + conseqs; ignore_no_measured_warn) funcs_to_check = make_ftc(funcs_to_check, conseqs, vars) # Computes identifiability and converts it to a easy to read form. @@ -143,13 +143,13 @@ Notes: - This function is part of the StructuralIdentifiability.jl extension. StructuralIdentifiability.jl must be imported to access it. - `measured_quantities` and `known_p` input may also be symbolic (e.g. measured_quantities = [rs.X]) """ -function SI.find_identifiable_functions(rs::ReactionSystem, args...; - measured_quantities = [], known_p = [], remove_conserved = true, +function SI.find_identifiable_functions(rs::ReactionSystem, args...; + measured_quantities = [], known_p = [], remove_conserved = true, ignore_no_measured_warn = false, kwargs...) # Creates a ODESystem, and list of measured quantities, of SI's preferred form. osys, conseqs = make_osys(rs; remove_conserved) - measured_quantities = make_measured_quantities( - rs, measured_quantities, known_p, conseqs; ignore_no_measured_warn) + measured_quantities = make_measured_quantities(rs, measured_quantities, known_p, + conseqs; ignore_no_measured_warn) # Computes identifiable functions and converts it to a easy to read form. out = SI.find_identifiable_functions(osys, args...; measured_quantities, kwargs...) diff --git a/src/chemistry_functionality.jl b/src/chemistry_functionality.jl index 50c551e505..ddf1d2f3c5 100644 --- a/src/chemistry_functionality.jl +++ b/src/chemistry_functionality.jl @@ -85,7 +85,7 @@ function make_compound(expr) # Loops through all components, add the component and the coefficients to the corresponding vectors # Cannot extract directly using e.g. "getfield.(composition, :reactant)" because then # we get something like :([:C, :O]), rather than :([C, O]). - composition = Catalyst.recursive_find_reactants!( xpr.args[3], 1, + composition = Catalyst.recursive_find_reactants!(xpr.args[3], 1, Vector{ReactantStruct}(undef, 0)) components = :([]) # Becomes something like :([C, O]). coefficients = :([]) # Becomes something like :([1, 2]). @@ -122,12 +122,12 @@ function make_compound(expr) species_declaration_expr = Expr(:escape, :(@species $species_expr)) multiple_ivs_error_check_expr = Expr(:escape, :($(isempty(ivs)) && (length($ivs_get_expr) > 1) && - error($COMPOUND_CREATION_ERROR_DEPENDENT_VAR_REQUIRED))) + error($COMPOUND_CREATION_ERROR_DEPENDENT_VAR_REQUIRED))) iv_designation_expr = Expr(:escape, :($(isempty(ivs)) && ($species_name = $(species_name)($(ivs_get_expr)...)))) iv_check_expr = Expr(:escape, :(issetequal(arguments(ModelingToolkit.unwrap($species_name)), $ivs_get_expr) || - error("The independent variable(S) provided to the compound ($(arguments(ModelingToolkit.unwrap($species_name)))), and those of its components ($($ivs_get_expr)))), are not identical."))) + error("The independent variable(S) provided to the compound ($(arguments(ModelingToolkit.unwrap($species_name)))), and those of its components ($($ivs_get_expr)))), are not identical."))) compound_designation_expr = Expr(:escape, :($species_name = ModelingToolkit.setmetadata( $species_name, Catalyst.CompoundSpecies, true))) @@ -197,7 +197,7 @@ function make_compounds(expr) # The output needs to be converted to Vector{Num} (from Vector{SymbolicUtils.BasicSymbolic{Real}}) to be consistent with e.g. @variables. compound_declarations.args[end] = :([ModelingToolkit.wrap(cmp) - for cmp in $(compound_declarations.args[end])]) + for cmp in $(compound_declarations.args[end])]) # Returns output that. return compound_declarations @@ -264,7 +264,7 @@ function balance_reaction(reaction::Reaction) prodstoich = stoich[(length(reaction.substrates) + 1):end] # Create a new reaction with the balanced stoichiometries - balancedrx = Reaction(reaction.rate, reaction.substrates, reaction.products, + balancedrx = Reaction(reaction.rate, reaction.substrates, reaction.products, substoich, prodstoich) # Add the reaction to the vector of all reactions diff --git a/src/dsl.jl b/src/dsl.jl index 441f749459..ddb0fc48e8 100644 --- a/src/dsl.jl +++ b/src/dsl.jl @@ -417,7 +417,7 @@ function get_reactions(exprs::Vector{Expr}, reactions = Vector{ReactionStruct}(u push_reactions!(reactions, reaction.args[3], reaction.args[2], rate.args[2], metadata.args[2], arrow) elseif in(arrow, fwd_arrows) - push_reactions!( reactions, reaction.args[2], reaction.args[3], + push_reactions!(reactions, reaction.args[2], reaction.args[3], rate, metadata, arrow) elseif in(arrow, bwd_arrows) push_reactions!(reactions, reaction.args[3], reaction.args[2], @@ -476,7 +476,7 @@ function push_reactions!(reactions::Vector{ReactionStruct}, sub_line::ExprValues error("Some reaction metadata fields where repeated: $(metadata_entries)") end - push!(reactions, ReactionStruct(get_tup_arg(sub_line, i), + push!(reactions,ReactionStruct(get_tup_arg(sub_line, i), get_tup_arg(prod_line, i), get_tup_arg(rate, i), metadata_i)) end end @@ -640,11 +640,11 @@ function read_events_option(options, event_type::Symbol) # Formatting error checks. # NOTE: Maybe we should move these deeper into the system (rather than the DSL), throwing errors more generally? if (arg isa Expr) && (arg.head != :call) || (arg.args[1] != :(=>)) || - (length(arg.args) != 3) + (length(arg.args) != 3) error("Events should be on form `condition => affect`, separated by a `=>`. This appears not to be the case for: $(arg).") end if (arg isa Expr) && (arg.args[2] isa Expr) && (arg.args[2].head != :vect) && - (event_type == :continuous_events) + (event_type == :continuous_events) error("The condition part of continious events (the left-hand side) must be a vector. This is not the case for: $(arg).") end if (arg isa Expr) && (arg.args[3] isa Expr) && (arg.args[3].head != :vect) @@ -668,7 +668,7 @@ function read_equations_options(options, variables_declared) eqs_input = haskey(options, :equations) ? options[:equations].args[3] : :(begin end) eqs_input = option_block_form(eqs_input) equations = Expr[] - ModelingToolkit.parse_equations!(Expr(:block), equations, + ModelingToolkit.parse_equations!(Expr(:block), equations, Dict{Symbol, Any}(), eqs_input) # Loops through all equations, checks for lhs of the form `D(X) ~ ...`. @@ -687,7 +687,7 @@ function read_equations_options(options, variables_declared) lhs = eq.args[2] # if lhs: is an expression. Is a function call. The function's name is D. Calls a single symbol. if (lhs isa Expr) && (lhs.head == :call) && (lhs.args[1] == :D) && - (lhs.args[2] isa Symbol) + (lhs.args[2] isa Symbol) diff_var = lhs.args[2] if in(diff_var, forbidden_symbols_error) error("A forbidden symbol ($(diff_var)) was used as an variable in this differential equation: $eq") @@ -755,7 +755,7 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted) error("An interpoalted observable have been used, which has also been explicitly delcared within the system using eitehr @species or @variables. This is not permited.") end if ((obs_name in species_n_vars_declared) || is_escaped_expr(obs_eq.args[2])) && - !isnothing(metadata) + !isnothing(metadata) error("Metadata was provided to observable $obs_name in the `@observables` macro. However, the obervable was also declared separately (using either @species or @variables). When this is done, metadata should instead be provided within the original @species or @variable declaration.") end @@ -771,7 +771,7 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted) # Adds a line to the `observed_vars` expression, setting the ivs for this observable. # Cannot extract directly using e.g. "getfield.(dependants_structs, :reactant)" because # then we get something like :([:X1, :X2]), rather than :([X1, X2]). - dep_var_expr = :(filter(!MT.isparameter, + dep_var_expr = :(filter(!MT.isparameter, Symbolics.get_variables($(obs_eq.args[3])))) ivs_get_expr = :(unique(reduce(vcat, [arguments(MT.unwrap(dep)) for dep in $dep_var_expr]))) diff --git a/src/expression_utils.jl b/src/expression_utils.jl index a475677468..e1e9d0fe79 100644 --- a/src/expression_utils.jl +++ b/src/expression_utils.jl @@ -82,7 +82,7 @@ function find_varinfo_in_declaration(expr) # Case: X(t) = 1.0 (expr.args[1].head == :call) && (return expr.args[1].args[1], expr.args[1].args[2:end], expr.args[2].args[1], - nothing) + nothing) end if expr.head == :tuple # Case: X, [metadata=true] diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 4aa5169166..6e7f446267 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -463,7 +463,7 @@ end # While species are ordered before variables in the unknowns vector, this ordering is not imposed here, # but carried out at a later stage. function make_ReactionSystem_internal(rxs_and_eqs::Vector, iv, us_in, ps_in; - spatial_ivs = nothing, continuous_events = [], discrete_events = [], + spatial_ivs = nothing, continuous_events = [], discrete_events = [], observed = [], kwargs...) # Filters away any potential obervables from `states` and `spcs`. diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 8a5d772220..99fceb6b58 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -607,7 +607,7 @@ function Base.convert(::Type{<:SDESystem}, rs::ReactionSystem; ists, ispcs = get_indep_sts(flatrs, remove_conserved) eqs = assemble_drift(flatrs, ispcs; combinatoric_ratelaws, include_zero_odes, remove_conserved) - noiseeqs = assemble_diffusion(flatrs, ists, ispcs; + noiseeqs = assemble_diffusion(flatrs, ists, ispcs; combinatoric_ratelaws, remove_conserved) eqs, us, ps, obs, defs = addconstraints!(eqs, flatrs, ists, ispcs; remove_conserved) diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 4307820fbb..5480f8f38e 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -69,7 +69,7 @@ end # Converts a numeric expression (e.g. p*X + 2Y) to a string (e.g. "p*X + 2Y"). Also ensures that for # any variables (e.g. X(t)) the call part is stripped, and only variable name (e.g. X) is written. -function expression_2_string(expr; +function expression_2_string(expr; strip_call_dict = make_strip_call_dict(Symbolics.get_variables(expr))) strip_called_expr = substitute(expr, strip_call_dict) return repr(strip_called_expr) @@ -89,7 +89,7 @@ function syms_2_declaration_string(syms; multiline_format = false) decs_string = (multiline_format ? " begin" : "") for sym in syms delimiter = (multiline_format ? "\n\t" : " ") - @string_append! decs_string delimiter sym_2_declaration_string(sym; + @string_append! decs_string delimiter sym_2_declaration_string(sym; multiline_format) end multiline_format && (@string_append! decs_string "\nend") @@ -109,7 +109,7 @@ function sym_2_declaration_string(sym; multiline_format = false) if !(sym isa SymbolicUtils.BasicSymbolic{Real}) sym_type = String(Symbol(typeof(Symbolics.unwrap(sym)))) if (get_substring(sym_type, 1, 28) != "SymbolicUtils.BasicSymbolic{") || - (get_char_end(sym_type, 0) != '}') + (get_char_end(sym_type, 0) != '}') error("Encountered symbolic of unexpected type: $sym_type.") end @string_append! dec_string "::" get_substring_end(sym_type, 29, -1) diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index ea14661696..d45959bedd 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -43,7 +43,7 @@ SIVS_FS = (has_sivs, get_sivs_string, get_sivs_annotation) # Function which handles the addition of species, variable, and parameter declarations to the file # text. These must be handled as a unity in case there are default value dependencies between these. -function handle_us_n_ps(file_text::String, rn::ReactionSystem, +function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) # Fetches the systems parameters, species, and variables. Computes the `has_` `Bool`s. ps_all = get_ps(rn) @@ -102,11 +102,11 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, while !(isempty(remaining_ps) && isempty(remaining_sps) && isempty(remaining_vars)) # Checks which parameters/species/variables can be written. The `dependency_split` # function updates the `remaining_` input. - writable_ps = dependency_split!(remaining_ps, + writable_ps = dependency_split!(remaining_ps, [remaining_ps; remaining_sps; remaining_vars]) - writable_sps = dependency_split!(remaining_sps, + writable_sps = dependency_split!(remaining_sps, [remaining_ps; remaining_sps; remaining_vars]) - writable_vars = dependency_split!(remaining_vars, + writable_vars = dependency_split!(remaining_vars, [remaining_ps; remaining_sps; remaining_vars]) # Writes those that can be written. @@ -409,7 +409,7 @@ function get_continuous_events_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -CONTINUOUS_EVENTS_FS = (has_continuous_events, get_continuous_events_string, +CONTINUOUS_EVENTS_FS = (has_continuous_events, get_continuous_events_string, get_continuous_events_annotation) ### Handles Discrete Events ### @@ -466,7 +466,7 @@ function get_discrete_events_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -DISCRETE_EVENTS_FS = (has_discrete_events, get_discrete_events_string, +DISCRETE_EVENTS_FS = (has_discrete_events, get_discrete_events_string, get_discrete_events_annotation) ### Handles Systems ### @@ -474,7 +474,7 @@ DISCRETE_EVENTS_FS = (has_discrete_events, get_discrete_events_string, # Specific `push_field` function, which is used for the system field (where the annotation option # must be passed to the `get_component_string` function). Since non-ReactionSystem systems cannot be # written to file, this functions throws an error if any such systems are encountered. -function push_systems_field(file_text::String, rn::ReactionSystem, +function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) # Checks whther there are any subsystems, and if these are ReactionSystems. has_systems(rn) || (return (file_text, false)) @@ -542,5 +542,5 @@ function get_connection_type_annotation(rn::ReactionSystem) end # Combines the 3 connection types-related functions in a constant tuple. -CONNECTION_TYPE_FS = (has_connection_type, get_connection_type_string, +CONNECTION_TYPE_FS = (has_connection_type, get_connection_type_string, get_connection_type_annotation) diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index 4eb8b74793..86268dc854 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -32,7 +32,7 @@ Notes: - Reaction systems with components that have units cannot currently be saved. - The `ReactionSystem` is saved using *programmatic* (not DSL) format for model creation. """ -function save_reactionsystem(filename::String, rn::ReactionSystem; +function save_reactionsystem(filename::String, rn::ReactionSystem; annotate = true, safety_check = true) reactionsystem_uptodate_check() open(filename, "w") do file @@ -65,21 +65,21 @@ function get_full_system_string(rn::ReactionSystem, annotate::Bool, top_level::B file_text, has_reactions = push_field(file_text, rn, annotate, top_level, REACTIONS_FS) file_text, has_equations = push_field(file_text, rn, annotate, top_level, EQUATIONS_FS) file_text, has_observed = push_field(file_text, rn, annotate, top_level, OBSERVED_FS) - file_text, has_continuous_events = push_field(file_text, rn, annotate, + file_text, has_continuous_events = push_field(file_text, rn, annotate, top_level, CONTINUOUS_EVENTS_FS) file_text, has_discrete_events = push_field(file_text, rn, annotate, top_level, DISCRETE_EVENTS_FS) file_text, has_systems = push_systems_field(file_text, rn, annotate, top_level) - file_text, has_connection_type = push_field(file_text, rn, annotate, + file_text, has_connection_type = push_field(file_text, rn, annotate, top_level, CONNECTION_TYPE_FS) # Finalises the system. Creates the final `ReactionSystem` call. # Enclose everything ing a `let ... end` block. - rs_creation_code = make_reaction_system_call(rn, annotate, top_level, + rs_creation_code = make_reaction_system_call(rn, annotate, top_level, has_sivs, has_species, has_variables, has_parameters, has_reactions, - has_equations, has_observed, has_continuous_events, has_discrete_events, + has_equations, has_observed, has_continuous_events, has_discrete_events, has_systems, has_connection_type) - if !annotate + if !annotate @string_prepend! "\n" file_text end @string_prepend! "let" file_text diff --git a/src/registered_functions.jl b/src/registered_functions.jl index d3baa6cf0b..348623f90f 100644 --- a/src/registered_functions.jl +++ b/src/registered_functions.jl @@ -138,7 +138,7 @@ function expand_registered_functions(expr) end # If applied to a Reaction, return a reaction with its rate modified. function expand_registered_functions(rx::Reaction) - Reaction(expand_registered_functions(rx.rate), rx.substrates, rx.products, + Reaction(expand_registered_functions(rx.rate), rx.substrates, rx.products, rx.substoich, rx.prodstoich, rx.netstoich, rx.only_use_rate, rx.metadata) end # If applied to a Equation, returns it with it applied to lhs and rhs diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 4d2453b8d5..099484cb13 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -20,7 +20,7 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. # These elements are length 1 vectors (if the parameter is uniform), # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). - vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Returns a DiscreteProblem. @@ -29,8 +29,8 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, end # Builds a spatial JumpProblem from a DiscreteProblem containg a Lattice Reaction System. -function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, - args...; name = nameof(lrs.rs), +function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, + args...; name = nameof(lrs.rs), combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), kwargs...) # Error checks. if !isnothing(dprob.f.sys) @@ -83,8 +83,8 @@ end # Not sure if there is any form of performance improvement from that though. Possibly is not the case. function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) # Creates a vector, storing which reactions have spatial components. - is_spatials = [Catalyst.has_spatial_vertex_component(rx.rate, lrs; - vert_ps = dprob.p[1]) for rx in reactions(lrs.rs)] + is_spatials = [Catalyst.has_spatial_vertex_component(rx.rate, lrs; + vert_ps = dprob.p[1]) for rx in reactions(lrs.rs)] # Creates templates for the rates (uniform and spatial) and the stoichiometries. # We cannot fetch reactant_stoich and net_stoich from a (non-spatial) MassActionJump. @@ -107,7 +107,7 @@ function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) # Loops through reactions with spatial rates, computes their rates and stoichiometries. for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs)) is_spat || continue - s_rates[cur_rx - length(u_rates), :] = compute_vertex_value(rx.rate, lrs; + s_rates[cur_rx - length(u_rates), :] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1]) substoich_map = Pair.(rx.substrates, rx.substoich) reactant_stoich[cur_rx] = int_map(substoich_map, lrs.rs) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 1665a726a5..102b586a7c 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -47,7 +47,7 @@ struct LatticeReactionSystem{S, T} # <: MT.AbstractTimeDependentSystem if isempty(spatial_reactions) spat_species = Vector{BasicSymbolic{Real}}[] else - spat_species = unique(reduce(vcat, + spat_species = unique(reduce(vcat, [spatial_species(sr) for sr in spatial_reactions])) end num_species = length(unique([species(rs); spat_species])) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index d1266e4dfd..9244eaa9ea 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -61,7 +61,7 @@ struct LatticeTransportODEf{Q, R, S, T} # 1 if ps are constant across the graph, 0 else. v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) eds = edges(lrs.lattice) - new{Q, R, typeof(eds), T}( ofunc, lrs.num_verts, lrs.num_species, vert_ps, + new{Q, R, typeof(eds), T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, v_ps_idx_types, transport_rates, leaving_rates, eds, edge_ps) end end @@ -159,7 +159,7 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) # Prepares the Jacobian and forcing functions (depending on jacobian and sparsity selection). - osys = complete(convert(ODESystem, lrs.rs; + osys = complete(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks)) if jac # `build_jac_prototype` currently assumes a sparse (non-spatial) Jacobian. Hence compute this. diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 5779f8bebd..cccd789bb2 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -77,7 +77,7 @@ ModelingToolkit.parameters(tr::TransportReaction) = Symbolics.get_variables(tr.r spatial_species(tr::TransportReaction) = [tr.species] # Checks that a transport reaction is valid for a given reaction system. -function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction; +function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction; edge_parameters = []) # Checks that the species exist in the reaction system. # (ODE simulation code becomes difficult if this is not required, @@ -96,14 +96,14 @@ function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReacti if any(isequal(tr.species, s) && !isequivalent(tr.species, s) for s in species(rs)) error("A transport reaction used a species, $(tr.species), with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") end - if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) for rs_p in parameters(rs), + if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)) error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") end # Checks that no edge parameter occur among rates of non-spatial reactions. if any(!isempty(intersect(Symbolics.get_variables(r.rate), edge_parameters)) - for r in reactions(rs)) + for r in reactions(rs)) error("Edge parameter(s) were found as a rate of a non-spatial reaction.") end end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index b7f34b8fc5..3e9d962db3 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -268,7 +268,8 @@ end # This is stored in location_types. function get_component_value(values::Vector{<:Vector}, component_idx::Int64, location_idx::Int64, location_types::Vector{Bool}) - return get_component_value(values[component_idx], location_idx, location_types[component_idx]) + return get_component_value(values[component_idx], location_idx, + location_types[component_idx]) end # For a components value (which is a vector of either length 1 or some other length), retrieves its value. function get_component_value(values::Vector{<:Number}, location_idx::Int64) From 8fae16823c28c33ad5d3cfa670ef4450a670eb28 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 17:37:36 -0400 Subject: [PATCH 212/446] round 3 --- src/dsl.jl | 2 +- src/spatial_reaction_systems/spatial_ODE_systems.jl | 8 ++++---- src/spatial_reaction_systems/spatial_reactions.jl | 2 +- src/spatial_reaction_systems/utility.jl | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dsl.jl b/src/dsl.jl index ddb0fc48e8..69123cdcab 100644 --- a/src/dsl.jl +++ b/src/dsl.jl @@ -476,7 +476,7 @@ function push_reactions!(reactions::Vector{ReactionStruct}, sub_line::ExprValues error("Some reaction metadata fields where repeated: $(metadata_entries)") end - push!(reactions,ReactionStruct(get_tup_arg(sub_line, i), + push!(reactions, ReactionStruct(get_tup_arg(sub_line, i), get_tup_arg(prod_line, i), get_tup_arg(rate, i), metadata_i)) end end diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 9244eaa9ea..b0e5bd736d 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -171,8 +171,8 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} set_nonzero = true) if sparse f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) - jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; - set_nonzero = true) + jac_vals = build_jac_prototype( ofunc_sparse.jac_prototype, transport_rates, + lrs; set_nonzero = true) J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, edge_ps, true) jac_prototype = jac_vals else @@ -184,8 +184,8 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} if sparse ofunc_sparse = ODEFunction(osys; jac = false, sparse = true) f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) - jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, - lrs; set_nonzero = false) + jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, + transport_rates, lrs; set_nonzero = false) else ofunc_dense = ODEFunction(osys; jac = false, sparse = false) f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, edge_ps, lrs) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index cccd789bb2..ff8259536f 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -96,7 +96,7 @@ function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReacti if any(isequal(tr.species, s) && !isequivalent(tr.species, s) for s in species(rs)) error("A transport reaction used a species, $(tr.species), with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") end - if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) for rs_p in parameters(rs), + if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)) error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 3e9d962db3..e803383e92 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -268,7 +268,7 @@ end # This is stored in location_types. function get_component_value(values::Vector{<:Vector}, component_idx::Int64, location_idx::Int64, location_types::Vector{Bool}) - return get_component_value(values[component_idx], location_idx, + return get_component_value(values[component_idx], location_idx, location_types[component_idx]) end # For a components value (which is a vector of either length 1 or some other length), retrieves its value. From cf06b225f8375312a258f947249e238e0976ab43 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 17:39:07 -0400 Subject: [PATCH 213/446] round 4 --- src/chemistry_functionality.jl | 5 +++-- src/dsl.jl | 10 ++++++---- src/spatial_reaction_systems/spatial_ODE_systems.jl | 4 ++-- src/spatial_reaction_systems/spatial_reactions.jl | 4 ++-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/chemistry_functionality.jl b/src/chemistry_functionality.jl index ddf1d2f3c5..71cc14586b 100644 --- a/src/chemistry_functionality.jl +++ b/src/chemistry_functionality.jl @@ -105,8 +105,9 @@ function make_compound(expr) isempty(ivs) && (species_expr = insert_independent_variable(species_expr, :(..))) # Expression which when evaluated gives a vector with all the ivs of the components. - ivs_get_expr = :(unique(reduce(vcat, [arguments(ModelingToolkit.unwrap(comp)) - for comp in $components]))) + ivs_get_expr = :(unique(reduce( + vcat, [arguments(ModelingToolkit.unwrap(comp)) + for comp in $components]))) # Creates the found expressions that will create the compound species. # The `Expr(:escape, :(...))` is required so that the expressions are evaluated in diff --git a/src/dsl.jl b/src/dsl.jl index 69123cdcab..2107179dab 100644 --- a/src/dsl.jl +++ b/src/dsl.jl @@ -476,8 +476,9 @@ function push_reactions!(reactions::Vector{ReactionStruct}, sub_line::ExprValues error("Some reaction metadata fields where repeated: $(metadata_entries)") end - push!(reactions, ReactionStruct(get_tup_arg(sub_line, i), - get_tup_arg(prod_line, i), get_tup_arg(rate, i), metadata_i)) + push!(reactions, + ReactionStruct(get_tup_arg(sub_line, i), + get_tup_arg(prod_line, i), get_tup_arg(rate, i), metadata_i)) end end @@ -773,8 +774,9 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted) # then we get something like :([:X1, :X2]), rather than :([X1, X2]). dep_var_expr = :(filter(!MT.isparameter, Symbolics.get_variables($(obs_eq.args[3])))) - ivs_get_expr = :(unique(reduce(vcat, [arguments(MT.unwrap(dep)) - for dep in $dep_var_expr]))) + ivs_get_expr = :(unique(reduce( + vcat, [arguments(MT.unwrap(dep)) + for dep in $dep_var_expr]))) ivs_get_expr_sorted = :(sort($(ivs_get_expr); by = iv -> findfirst(MT.getname(iv) == ivs for ivs in $ivs_sorted))) push!(observed_vars.args, diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index b0e5bd736d..73f23c4624 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -171,7 +171,7 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} set_nonzero = true) if sparse f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) - jac_vals = build_jac_prototype( ofunc_sparse.jac_prototype, transport_rates, + jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, edge_ps, true) jac_prototype = jac_vals @@ -184,7 +184,7 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} if sparse ofunc_sparse = ODEFunction(osys; jac = false, sparse = true) f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) - jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, + jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = false) else ofunc_dense = ODEFunction(osys; jac = false, sparse = false) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index ff8259536f..a8dd6bac97 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -96,8 +96,8 @@ function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReacti if any(isequal(tr.species, s) && !isequivalent(tr.species, s) for s in species(rs)) error("A transport reaction used a species, $(tr.species), with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") end - if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) for rs_p in parameters(rs), - tr_p in Symbolics.get_variables(tr.rate)) + if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) + for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)) error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") end From 816bf96bd6a8d358c69143de0380d3ee5ce03409 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 17:41:42 -0400 Subject: [PATCH 214/446] add comment about weird formatting --- src/spatial_reaction_systems/spatial_reactions.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index a8dd6bac97..b897cd11fd 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -96,12 +96,14 @@ function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReacti if any(isequal(tr.species, s) && !isequivalent(tr.species, s) for s in species(rs)) error("A transport reaction used a species, $(tr.species), with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") end + # No `for` loop, just weird formatting by the formatter. if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) - for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)) + for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)) error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") end # Checks that no edge parameter occur among rates of non-spatial reactions. + # No `for` loop, just weird formatting by the formatter. if any(!isempty(intersect(Symbolics.get_variables(r.rate), edge_parameters)) for r in reactions(rs)) error("Edge parameter(s) were found as a rate of a non-spatial reaction.") From e96df2fa3a405bf49c42cf6f11420395115b8bf2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 18:15:00 -0400 Subject: [PATCH 215/446] merge fixes --- docs/make.jl | 3 +-- docs/pages.jl | 2 +- .../serialise_fields.jl | 17 +++++++++----- .../serialise_reactionsystem.jl | 22 ++++++++++--------- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index dae005132f..8595cd6ffa 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -41,8 +41,7 @@ makedocs(sitename = "Catalyst.jl", clean = true, pages = pages, pagesonly = true, - warnonly = true) - warnonly = [:missing_docs]) + warnonly = [:missing_docs]) deploydocs(repo = "github.com/SciML/Catalyst.jl.git"; push_preview = true) diff --git a/docs/pages.jl b/docs/pages.jl index d5db05674d..693b8b8411 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -26,7 +26,7 @@ pages = Any[ "model_simulation/simulation_plotting.md", "model_simulation/simulation_structure_interfacing.md", "model_simulation/ensemble_simulations.md", - "model_simulation/ode_simulation_performance.md", + "model_simulation/ode_simulation_performance.md" ], "Steady state analysis" => Any[ "steady_state_functionality/homotopy_continuation.md", diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index ba03b7ac32..e74ae6ca01 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -43,7 +43,8 @@ SIVS_FS = (seri_has_sivs, get_sivs_string, get_sivs_annotation) # Function which handles the addition of species, variable, and parameter declarations to the file # text. These must be handled as a unity in case there are default value dependencies between these. -function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) +function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, + top_level::Bool) # Fetches the system's parameters, species, and variables. Computes the `has_` `Bool`s. ps_all = get_ps(rn) sps_all = get_species(rn) @@ -408,7 +409,8 @@ function get_continuous_events_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -CONTINUOUS_EVENTS_FS = (seri_has_continuous_events, get_continuous_events_string, get_continuous_events_annotation) +CONTINUOUS_EVENTS_FS = (seri_has_continuous_events, get_continuous_events_string, + get_continuous_events_annotation) ### Handles Discrete Events ### @@ -464,17 +466,19 @@ function get_discrete_events_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -DISCRETE_EVENTS_FS = (seri_has_discrete_events, get_discrete_events_string, get_discrete_events_annotation) +DISCRETE_EVENTS_FS = (seri_has_discrete_events, get_discrete_events_string, + get_discrete_events_annotation) ### Handles Systems ### # Specific `push_field` function, which is used for the system field (where the annotation option # must be passed to the `get_component_string` function). Since non-ReactionSystem systems cannot be # written to file, this function throws an error if any such systems are encountered. -function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) +function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Bool, + top_level::Bool) # Checks whether there are any subsystems, and if these are ReactionSystems. seri_has_systems(rn) || (return (file_text, false)) - if any(!(system isa ReactionSystem) for system in MT.get_systems(rn)) + if any(!(system isa ReactionSystem) for system in MT.get_systems(rn)) error("Tries to write a ReactionSystem to file which have non-ReactionSystem subs-systems. This is currently not possible.") end @@ -538,4 +542,5 @@ function get_connection_type_annotation(rn::ReactionSystem) end # Combines the 3 connection types-related functions in a constant tuple. -CONNECTION_TYPE_FS = (seri_has_connection_type, get_connection_type_string, get_connection_type_annotation) \ No newline at end of file +CONNECTION_TYPE_FS = ( + seri_has_connection_type, get_connection_type_string, get_connection_type_annotation) diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index 005f658b3c..7c647ef2a2 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -75,12 +75,13 @@ function get_full_system_string(rn::ReactionSystem, annotate::Bool, top_level::B # Finalise the system. Creates the final `ReactionSystem` call. # Enclose everything in a `let ... end` block. - rs_creation_code = make_reaction_system_call(rn, annotate, top_level, has_sivs, has_species, - has_variables, has_parameters, has_reactions, - has_equations, has_observed, has_continuous_events, - has_discrete_events, has_systems, has_connection_type) - annotate || (@string_prepend! "\n" file_text) - @string_prepend! "let" file_text + rs_creation_code = make_reaction_system_call( + rn, annotate, top_level, has_sivs, has_species, + has_variables, has_parameters, has_reactions, + has_equations, has_observed, has_continuous_events, + has_discrete_events, has_systems, has_connection_type) + annotate || (@string_prepend! "\n" file_text) + @string_prepend! "let" file_text @string_append! file_text "\n\n" rs_creation_code "\n\nend" return file_text @@ -88,10 +89,11 @@ end # Creates a ReactionSystem call for creating the model. Adds all the correct inputs to it. The input # `has_` `Bool`s described which inputs are used. If the model is `complete`, this is handled here. -function make_reaction_system_call(rs::ReactionSystem, annotate, top_level, has_sivs, has_species, - has_variables, has_parameters, has_reactions, has_equations, - has_observed, has_continuous_events, has_discrete_events, - has_systems, has_connection_type) +function make_reaction_system_call( + rs::ReactionSystem, annotate, top_level, has_sivs, has_species, + has_variables, has_parameters, has_reactions, has_equations, + has_observed, has_continuous_events, has_discrete_events, + has_systems, has_connection_type) # Gets the independent variable input. iv = x_2_string(get_iv(rs)) From d997050d46711f55d2f49df5a38058c5aea1b59c Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 18:15:23 -0400 Subject: [PATCH 216/446] last formatting --- src/reactionsystem_serialisation/serialise_fields.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index e74ae6ca01..b37249dc23 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -466,7 +466,7 @@ function get_discrete_events_annotation(rn::ReactionSystem) end # Combines the 3 -related functions in a constant tuple. -DISCRETE_EVENTS_FS = (seri_has_discrete_events, get_discrete_events_string, +DISCRETE_EVENTS_FS = (seri_has_discrete_events, get_discrete_events_string, get_discrete_events_annotation) ### Handles Systems ### @@ -474,7 +474,7 @@ DISCRETE_EVENTS_FS = (seri_has_discrete_events, get_discrete_events_string, # Specific `push_field` function, which is used for the system field (where the annotation option # must be passed to the `get_component_string` function). Since non-ReactionSystem systems cannot be # written to file, this function throws an error if any such systems are encountered. -function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Bool, +function push_systems_field(file_text::String, rn::ReactionSystem, annotate::Bool, top_level::Bool) # Checks whether there are any subsystems, and if these are ReactionSystems. seri_has_systems(rn) || (return (file_text, false)) From 201a675444841eea2e70ed14ddbf5f6795934414 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 18:23:45 -0400 Subject: [PATCH 217/446] re enable all tests --- src/reactionsystem_conversions.jl | 4 ++-- test/runtests.jl | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index c37c5b12b3..f32cab808a 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -443,8 +443,8 @@ end # Finds and differentials in an expression, and sets these to 0. function remove_diffs(expr) - if Symbolics.hasnode(Symbolics.is_derivative, expr) - return Symbolics.replacenode(expr, diff_2_zero) + if hasnode(Symbolics.is_derivative, expr) + return replacenode(expr, diff_2_zero) else return expr end diff --git a/test/runtests.jl b/test/runtests.jl index 10a174ad3d..6743d7fa4b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,6 +11,13 @@ using SafeTestsets, Test @time begin #if GROUP == "All" || GROUP == "ModelCreation" + # Tests the `ReactionSystem` structure and its properties. + @time @safetestset "Reaction Structure" begin include("reactionsystem_core/reaction.jl") end + @time @safetestset "ReactionSystem Structure" begin include("reactionsystem_core/reactionsystem.jl") end + @time @safetestset "Higher Order Reactions" begin include("reactionsystem_core/higher_order_reactions.jl") end + @time @safetestset "Symbolic Stoichiometry" begin include("reactionsystem_core/symbolic_stoichiometry.jl") end + @time @safetestset "Parameter Type Designation" begin include("reactionsystem_core/parameter_type_designation.jl") end + @time @safetestset "Custom CRN Functions" begin include("reactionsystem_core/custom_crn_functions.jl") end @time @safetestset "Coupled CRN/Equation Systems" begin include("reactionsystem_core/coupled_equation_crn_systems.jl") end @time @safetestset "Events" begin include("reactionsystem_core/events.jl") end @@ -72,4 +79,4 @@ using SafeTestsets, Test @time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end #end -end # @time +end # @time \ No newline at end of file From 0d11f21b98bc8be8f92a2809628e7e3bf8be91fc Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 7 Jun 2024 18:25:32 -0400 Subject: [PATCH 218/446] init --- test/runtests.jl | 110 +++++++++++++++++++++-------------------------- 1 file changed, 50 insertions(+), 60 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 6d084286c5..647243a3fc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,73 +10,63 @@ using SafeTestsets, Test ### Run Tests ### @time begin - #if GROUP == "All" || GROUP == "ModelCreation" - # Tests the `ReactionSystem` structure and its properties. - @time @safetestset "Reaction Structure" begin include("reactionsystem_core/reaction.jl") end - @time @safetestset "ReactionSystem Structure" begin include("reactionsystem_core/reactionsystem.jl") end - @time @safetestset "Higher Order Reactions" begin include("reactionsystem_core/higher_order_reactions.jl") end - @time @safetestset "Symbolic Stoichiometry" begin include("reactionsystem_core/symbolic_stoichiometry.jl") end - @time @safetestset "Parameter Type Designation" begin include("reactionsystem_core/parameter_type_designation.jl") end - @time @safetestset "Custom CRN Functions" begin include("reactionsystem_core/custom_crn_functions.jl") end - # @time @safetestset "Coupled CRN/Equation Systems" begin include("reactionsystem_core/coupled_equation_crn_systems.jl") end - @time @safetestset "Events" begin include("reactionsystem_core/events.jl") end - - # Tests model creation via the @reaction_network DSL. - @time @safetestset "DSL Basic Model Construction" begin include("dsl/dsl_basic_model_construction.jl") end - @time @safetestset "DSL Advanced Model Construction" begin include("dsl/dsl_advanced_model_construction.jl") end - @time @safetestset "DSL Options" begin include("dsl/dsl_options.jl") end + # Tests the `ReactionSystem` structure and its properties. + @time @safetestset "Reaction Structure" begin include("reactionsystem_core/reaction.jl") end + @time @safetestset "ReactionSystem Structure" begin include("reactionsystem_core/reactionsystem.jl") end + @time @safetestset "Higher Order Reactions" begin include("reactionsystem_core/higher_order_reactions.jl") end + @time @safetestset "Symbolic Stoichiometry" begin include("reactionsystem_core/symbolic_stoichiometry.jl") end + @time @safetestset "Parameter Type Designation" begin include("reactionsystem_core/parameter_type_designation.jl") end + @time @safetestset "Custom CRN Functions" begin include("reactionsystem_core/custom_crn_functions.jl") end + # @time @safetestset "Coupled CRN/Equation Systems" begin include("reactionsystem_core/coupled_equation_crn_systems.jl") end + @time @safetestset "Events" begin include("reactionsystem_core/events.jl") end - # Tests compositional and hierarchical modelling. - @time @safetestset "ReactionSystem Components Based Creation" begin include("compositional_modelling/component_based_model_creation.jl") end - #end + # Tests model creation via the @reaction_network DSL. + @time @safetestset "DSL Basic Model Construction" begin include("dsl/dsl_basic_model_construction.jl") end + @time @safetestset "DSL Advanced Model Construction" begin include("dsl/dsl_advanced_model_construction.jl") end + @time @safetestset "DSL Options" begin include("dsl/dsl_options.jl") end - #if GROUP == "All" || GROUP == "Miscellaneous-NetworkAnalysis" - # Tests various miscellaneous features. - @time @safetestset "API" begin include("miscellaneous_tests/api.jl") end - @time @safetestset "Units" begin include("miscellaneous_tests/units.jl") end - @time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end - @time @safetestset "Compound Species" begin include("miscellaneous_tests/compound_macro.jl") end - @time @safetestset "Reaction Balancing" begin include("miscellaneous_tests/reaction_balancing.jl") end - @time @safetestset "ReactionSystem Serialisation" begin include("miscellaneous_tests/reactionsystem_serialisation.jl") end + # Tests compositional and hierarchical modelling. + @time @safetestset "ReactionSystem Components Based Creation" begin include("compositional_modelling/component_based_model_creation.jl") end - # Tests reaction network analysis features. - @time @safetestset "Conservation Laws" begin include("network_analysis/conservation_laws.jl") end - @time @safetestset "Network Properties" begin include("network_analysis/network_properties.jl") end - #end + # Tests various miscellaneous features. + @time @safetestset "API" begin include("miscellaneous_tests/api.jl") end + @time @safetestset "Units" begin include("miscellaneous_tests/units.jl") end + @time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end + @time @safetestset "Compound Species" begin include("miscellaneous_tests/compound_macro.jl") end + @time @safetestset "Reaction Balancing" begin include("miscellaneous_tests/reaction_balancing.jl") end + @time @safetestset "ReactionSystem Serialisation" begin include("miscellaneous_tests/reactionsystem_serialisation.jl") end - #if GROUP == "All" || GROUP == "Simulation" - # Tests ODE, SDE, jump simulations, nonlinear solving, and steady state simulations. - @time @safetestset "ODE System Simulations" begin include("simulation_and_solving/simulate_ODEs.jl") end - @time @safetestset "Automatic Jacobian Construction" begin include("simulation_and_solving/jacobian_construction.jl") end - @time @safetestset "SDE System Simulations" begin include("simulation_and_solving/simulate_SDEs.jl") end - @time @safetestset "Jump System Simulations" begin include("simulation_and_solving/simulate_jumps.jl") end - @time @safetestset "Nonlinear and SteadyState System Solving" begin include("simulation_and_solving/solve_nonlinear.jl") end + # Tests reaction network analysis features. + @time @safetestset "Conservation Laws" begin include("network_analysis/conservation_laws.jl") end + @time @safetestset "Network Properties" begin include("network_analysis/network_properties.jl") end - # Tests upstream SciML and DiffEq stuff. - @time @safetestset "MTK Structure Indexing" begin include("upstream/mtk_structure_indexing.jl") end - @time @safetestset "MTK Problem Inputs" begin include("upstream/mtk_problem_inputs.jl") end - #end + # Tests ODE, SDE, jump simulations, nonlinear solving, and steady state simulations. + @time @safetestset "ODE System Simulations" begin include("simulation_and_solving/simulate_ODEs.jl") end + @time @safetestset "Automatic Jacobian Construction" begin include("simulation_and_solving/jacobian_construction.jl") end + @time @safetestset "SDE System Simulations" begin include("simulation_and_solving/simulate_SDEs.jl") end + @time @safetestset "Jump System Simulations" begin include("simulation_and_solving/simulate_jumps.jl") end + @time @safetestset "Nonlinear and SteadyState System Solving" begin include("simulation_and_solving/solve_nonlinear.jl") end - #if GROUP == "All" || GROUP == "Spatial" - # Tests spatial modelling and simulations. - @time @safetestset "PDE Systems Simulations" begin include("spatial_modelling/simulate_PDEs.jl") end - @time @safetestset "Lattice Reaction Systems" begin include("spatial_modelling/lattice_reaction_systems.jl") end - @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_ODEs.jl") end - @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end - #end + # Tests upstream SciML and DiffEq stuff. + @time @safetestset "MTK Structure Indexing" begin include("upstream/mtk_structure_indexing.jl") end + @time @safetestset "MTK Problem Inputs" begin include("upstream/mtk_problem_inputs.jl") end - #if GROUP == "All" || GROUP == "Visualisation-Extensions" - # Tests network visualisation. - @time @safetestset "Latexify" begin include("visualisation/latexify.jl") end - # Disable on Macs as can't install GraphViz via jll - if !Sys.isapple() - @time @safetestset "Graphs Visualisations" begin include("visualisation/graphs.jl") end - end + # Tests spatial modelling and simulations. + @time @safetestset "PDE Systems Simulations" begin include("spatial_modelling/simulate_PDEs.jl") end + @time @safetestset "Lattice Reaction Systems" begin include("spatial_modelling/lattice_reaction_systems.jl") end + @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_ODEs.jl") end + @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end - # Tests extensions. - # @time @safetestset "BifurcationKit Extension" begin include("extensions/bifurcation_kit.jl") end - # @time @safetestset "HomotopyContinuation Extension" begin include("extensions/homotopy_continuation.jl") end - # @time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end - #end + # Tests network visualisation. + @time @safetestset "Latexify" begin include("visualisation/latexify.jl") end + # Disable on Macs as can't install GraphViz via jll + if !Sys.isapple() + @time @safetestset "Graphs Visualisations" begin include("visualisation/graphs.jl") end + end + + # Tests extensions. + # @time @safetestset "BifurcationKit Extension" begin include("extensions/bifurcation_kit.jl") end + # @time @safetestset "HomotopyContinuation Extension" begin include("extensions/homotopy_continuation.jl") end + # @time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end end # @time From a6f1cf4c9796bce1294cd6146fb965e05df91aec Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sat, 8 Jun 2024 00:48:42 +0200 Subject: [PATCH 219/446] Update reactionsystem_conversions.jl --- src/reactionsystem_conversions.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index f32cab808a..b4f506a6c5 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -559,7 +559,7 @@ function nonlinear_convert_differentials_check(rs::ReactionSystem) # If the lhs upper level function is not a differential w.r.t. time. # If the contenct of the differential is not a variable (and nothing more). # If either of this is a case, throws the warning. - if Symbolics._occursin(Symbolics.is_derivative, eq.rhs) || + if hasnode(Symbolics.is_derivative, eq.rhs) || !Symbolics.is_derivative(eq.lhs) || !isequal(Symbolics.operation(eq.lhs), Differential(get_iv(rs))) || (length(arguments(eq.lhs)) != 1) || @@ -903,4 +903,4 @@ function to_multivariate_poly(polyeqs::AbstractVector{Symbolics.BasicSymbolic{Re end ps -end \ No newline at end of file +end From 0cadeeafed8fa729d31c03ca76f6a09c0fafab6e Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sat, 8 Jun 2024 00:52:20 +0200 Subject: [PATCH 220/446] Update chemistry_functionality.jl --- src/chemistry_functionality.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chemistry_functionality.jl b/src/chemistry_functionality.jl index 71cc14586b..ff3b663fe6 100644 --- a/src/chemistry_functionality.jl +++ b/src/chemistry_functionality.jl @@ -85,7 +85,7 @@ function make_compound(expr) # Loops through all components, add the component and the coefficients to the corresponding vectors # Cannot extract directly using e.g. "getfield.(composition, :reactant)" because then # we get something like :([:C, :O]), rather than :([C, O]). - composition = Catalyst.recursive_find_reactants!(xpr.args[3], 1, + composition = Catalyst.recursive_find_reactants!(expr.args[3], 1, Vector{ReactantStruct}(undef, 0)) components = :([]) # Becomes something like :([C, O]). coefficients = :([]) # Becomes something like :([1, 2]). From e7fe9815207de6656f858ed98838d58586366359 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 09:36:59 -0400 Subject: [PATCH 221/446] up --- .../reactionsystem_serialisation.jl | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index 7a795a9a5a..46ec69863c 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -192,16 +192,16 @@ let @test isequal(getdefault(rs_loaded.rs2.W2), float_md) # Checks that `Reaction` metadata fields are correct. - @test isequal(getmetadata(get_rxs(rs_loaded)[1], :misc), bool_md) - @test isequal(getmetadata(get_rxs(rs_loaded)[2], :misc), int_md) - @test isequal(getmetadata(get_rxs(rs_loaded)[3], :misc), sym_md) - @test isequal(getmetadata(get_rxs(rs_loaded)[4], :misc), str_md) - @test isequal(getmetadata(get_rxs(rs_loaded)[5], :misc), nothing_md) - @test isequal(getmetadata(get_rxs(rs_loaded.rs2)[1], :misc), expr_md) - @test isequal(getmetadata(get_rxs(rs_loaded.rs2)[2], :misc), tup_md) - @test isequal(getmetadata(get_rxs(rs_loaded.rs2)[3], :misc), vec_md) - @test isequal(getmetadata(get_rxs(rs_loaded.rs2)[4], :misc), dict_md) - @test isequal(getmetadata(get_rxs(rs_loaded.rs2)[5], :misc), mat_md) + @test isequal(Catalyst.getmisc(get_rxs(rs_loaded)[1]), bool_md) + @test isequal(Catalyst.getmisc(get_rxs(rs_loaded)[2]), int_md) + @test isequal(Catalyst.getmisc(get_rxs(rs_loaded)[3]), sym_md) + @test isequal(Catalyst.getmisc(get_rxs(rs_loaded)[4]), str_md) + @test isequal(Catalyst.getmisc(get_rxs(rs_loaded)[5]), nothing_md) + @test isequal(Catalyst.getmisc(get_rxs(rs_loaded.rs2)[1]), expr_md) + @test isequal(Catalyst.getmisc(get_rxs(rs_loaded.rs2)[2]), tup_md) + @test isequal(Catalyst.getmisc(get_rxs(rs_loaded.rs2)[3]), vec_md) + @test isequal(Catalyst.getmisc(get_rxs(rs_loaded.rs2)[4]), dict_md) + @test isequal(Catalyst.getmisc(get_rxs(rs_loaded.rs2)[5]), mat_md) # Checks that `ReactionSystem` metadata fields are correct. @test isequal(get_metadata(rs_loaded), mat_md) From e21071093da97727c867d46f745c927a220bb518 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 10:17:34 -0400 Subject: [PATCH 222/446] format --- src/reactionsystem_conversions.jl | 8 ++++---- src/spatial_reaction_systems/utility.jl | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index dfec8f7e71..0dc1402bc7 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -557,10 +557,10 @@ function nonlinear_convert_differentials_check(rs::ReactionSystem) # If the contenct of the differential is not a variable (and nothing more). # If either of this is a case, throws the warning. if hasnode(Symbolics.is_derivative, eq.rhs) || - !Symbolics.is_derivative(eq.lhs) || - !isequal(Symbolics.operation(eq.lhs), Differential(get_iv(rs))) || - (length(arguments(eq.lhs)) != 1) || - !any(isequal(arguments(eq.lhs)[1]), nonspecies(rs)) + !Symbolics.is_derivative(eq.lhs) || + !isequal(Symbolics.operation(eq.lhs), Differential(get_iv(rs))) || + (length(arguments(eq.lhs)) != 1) || + !any(isequal(arguments(eq.lhs)[1]), nonspecies(rs)) error("You are attempting to convert a `ReactionSystem` coupled with differential equations to a `NonlinearSystem`. However, some of these differentials are not of the form `D(x) ~ ...` where: (1) The left-hand side is a differential of a single variable with respect to the time independent variable, and (2) The right-hand side does not contain any differentials. diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 26a729f9f0..bf6e0c2a2e 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -212,7 +212,7 @@ end # If all parameters the rate depend on are uniform all edges, this becomes a length 1 vector. # Else a vector with each value corresponding to the rate at one specific edge. function compute_transport_rates(rate_law::Num, - p_val_dict::Dict{BasicSymbolic{Real}, Vector{Float64}}, num_edges::Int64) + p_val_dict::Dict{BasicSymbolic{Real}, Vector{Float64}}, num_edges::Int64) # Finds parameters involved in rate and create a function evaluating the rate law. relevant_ps = Symbolics.get_variables(rate_law) rate_law_func = drop_expr(@RuntimeGeneratedFunction(build_function( From acdfca15da2c74b521898c4dc3403458ea8376e0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 11:16:46 -0400 Subject: [PATCH 223/446] formating fix --- .../structural_identifiability_extension.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl index d790db263c..d4cc1ead23 100644 --- a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl +++ b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl @@ -73,7 +73,7 @@ function SI.assess_local_identifiability(rs::ReactionSystem, args...; funcs_to_check = make_ftc(funcs_to_check, conseqs, vars) # Computes identifiability and converts it to a easy to read form. - out = SI.assess_local_identifiability(sys, args...; measured_quantities, + out = SI.assess_local_identifiability(osys, args...; measured_quantities, funcs_to_check, kwargs...) return make_output(out, funcs_to_check, reverse.(conseqs)) end From 3fb0339db9c638eab4f997b64028f4798d554597 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 12:16:41 -0400 Subject: [PATCH 224/446] save progress --- .../bifurcation_kit_extension.jl | 2 +- .../homotopy_continuation_extension.jl | 2 +- src/reactionsystem_conversions.jl | 2 +- .../serialise_reactionsystem.jl | 6 +- src/steady_state_stability.jl | 2 +- test/dsl/dsl_options.jl | 4 +- test/extensions/structural_identifiability.jl | 139 +++++++++--------- .../stability_computation.jl | 4 +- .../parameter_type_designation.jl | 2 +- 9 files changed, 83 insertions(+), 80 deletions(-) diff --git a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl index af2c0bb47e..be0becb3a9 100644 --- a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl +++ b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl @@ -4,7 +4,7 @@ function BK.BifurcationProblem(rs::ReactionSystem, u0_bif, ps, bif_par, args...; plot_var = nothing, record_from_solution = BK.record_sol_default, jac = true, u0 = [], kwargs...) if !isautonomous(rs) - error("Attempting to create a `BifurcationProblem` for a non-autonomous system (e.g. where some rate depend on $(rs.iv)). This is not possible.") + error("Attempting to create a `BifurcationProblem` for a non-autonomous system (e.g. where some rate depend on $(get_iv(rs))). This is not possible.") end # Converts symbols to symbolics. diff --git a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl index 91e868c25e..cc7ab65795 100644 --- a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl +++ b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl @@ -38,7 +38,7 @@ Notes: function Catalyst.hc_steady_states(rs::ReactionSystem, ps; filter_negative = true, neg_thres = -1e-20, u0 = [], kwargs...) if !isautonomous(rs) - error("Attempting to compute steady state for a non-autonomous system (e.g. where some rate depend on $(rs.iv)). This is not possible.") + error("Attempting to compute steady state for a non-autonomous system (e.g. where some rate depend on $(get_iv(rs))). This is not possible.") end ss_poly = steady_state_polynomial(rs, ps, u0) sols = HC.real_solutions(HC.solve(ss_poly; kwargs...)) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 99fceb6b58..c98254d442 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -520,7 +520,7 @@ function Base.convert(::Type{<:NonlinearSystem}, rs::ReactionSystem; name = name iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, NonlinearSystem) if !isautonomous(rs) - error("Attempting to convert a non-autonomous `ReactionSystem` (e.g. where some rate depend on $(rs.iv)) to a `NonlinearSystem`. This is not possible. if you are intending to compute system steady states, consider creating and solving a `SteadyStateProblem.") + error("Attempting to convert a non-autonomous `ReactionSystem` (e.g. where some rate depend on $(get_iv(rs))) to a `NonlinearSystem`. This is not possible. if you are intending to compute system steady states, consider creating and solving a `SteadyStateProblem.") end # Generates system equations. diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index 7c647ef2a2..e7ecd69eee 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -147,13 +147,13 @@ function make_reaction_system_call( has_connection_type && (@string_append! reaction_system_string ", connection_type") # Potentially appends a combinatoric_ratelaws statement. - if !Symbolics.unwrap(rs.combinatoric_ratelaws) + if !Symbolics.unwrap(combinatoric_ratelaws(rs)) @string_append! reaction_system_string ", combinatoric_ratelaws = false" end # Potentially appends `ReactionSystem` metadata value(s). Weird composite types are not supported. - if !isnothing(rs.metadata) - @string_append! reaction_system_string ", metadata = $(x_2_string(rs.metadata))" + if !isnothing(MT.get_metadata(rs)) + @string_append! reaction_system_string ", metadata = $(x_2_string(MT.get_metadata(rs)))" end # Finalises the call. Appends potential annotation. If the system is complete, add a call for this. diff --git a/src/steady_state_stability.jl b/src/steady_state_stability.jl index 8103d4c61f..42ffec2d0e 100644 --- a/src/steady_state_stability.jl +++ b/src/steady_state_stability.jl @@ -48,7 +48,7 @@ function steady_state_stability(u::Vector, rs::ReactionSystem, ps; tol = 10 * sqrt(eps(ss_val_type(u))), ss_jac = steady_state_jac(rs; u0 = u)) # Warning checks. if !isautonomous(rs) - error("Attempting to compute stability for a non-autonomous system (e.g. where some rate depend on $(rs.iv)). This is not possible.") + error("Attempting to compute stability for a non-autonomous system (e.g. where some rate depend on $(get_iv(rs))). This is not possible.") end # If `u` is a vector of values, we convert it to a map. Also, if there are conservation laws, diff --git a/test/dsl/dsl_options.jl b/test/dsl/dsl_options.jl index 93ce90b541..d12cbd4a45 100644 --- a/test/dsl/dsl_options.jl +++ b/test/dsl/dsl_options.jl @@ -574,8 +574,8 @@ let end end V,W = getfield.(observed(rn), :lhs) - @test isequal(arguments(ModelingToolkit.unwrap(V)), Any[rn.iv, rn.sivs[1], rn.sivs[2]]) - @test isequal(arguments(ModelingToolkit.unwrap(W)), Any[rn.iv, rn.sivs[2]]) + @test isequal(arguments(ModelingToolkit.unwrap(V)), Any[Catalyst.get_iv(rs), Catalyst.get_sivs(rn)[1], Catalyst.get_sivs(rn)[2]]) + @test isequal(arguments(ModelingToolkit.unwrap(W)), Any[Catalyst.get_iv(rs), Catalyst.get_sivs(rn)[2]]) end # Checks that metadata is written properly. diff --git a/test/extensions/structural_identifiability.jl b/test/extensions/structural_identifiability.jl index 8ef35c293b..f3617c3409 100644 --- a/test/extensions/structural_identifiability.jl +++ b/test/extensions/structural_identifiability.jl @@ -1,7 +1,10 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, StructuralIdentifiability, Test +using Catalyst, Logging, StructuralIdentifiability, Test + +# Sets the `loglevel`. +loglevel = Logging.Error # Helper function for checking that results are correct identifiability calls from different packages. # Converts the output dicts from StructuralIdentifiability functions from "weird symbol => stuff" to "symbol => stuff" (the output have some strange meta data which prevents equality checks, this enables this). @@ -28,12 +31,12 @@ let (pₑ*M,dₑ), 0 <--> E (pₚ*E,dₚ), 0 <--> P end - gi_1 = assess_identifiability(goodwind_oscillator_catalyst; measured_quantities=[:M]) - li_1 = assess_local_identifiability(goodwind_oscillator_catalyst; measured_quantities=[:M]) - ifs_1 = find_identifiable_functions(goodwind_oscillator_catalyst; measured_quantities=[:M]) + gi_1 = assess_identifiability(goodwind_oscillator_catalyst; measured_quantities = [:M], loglevel) + li_1 = assess_local_identifiability(goodwind_oscillator_catalyst; measured_quantities = [:M], loglevel) + ifs_1 = find_identifiable_functions(goodwind_oscillator_catalyst; measured_quantities = [:M], loglevel) # Identifiability analysis for Catalyst converted to StructuralIdentifiability.jl model. - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[:M]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M]) gi_2 = assess_identifiability(si_catalyst_ode) li_2 = assess_local_identifiability(si_catalyst_ode) ifs_2 = find_identifiable_functions(si_catalyst_ode) @@ -45,9 +48,9 @@ let P'(t) = -dₚ*P(t) + pₚ*E(t), y1(t) = M(t) ) - gi_3 = assess_identifiability(goodwind_oscillator_si) - li_3 = assess_local_identifiability(goodwind_oscillator_si) - ifs_3 = find_identifiable_functions(goodwind_oscillator_si) + gi_3 = assess_identifiability(goodwind_oscillator_si; loglevel) + li_3 = assess_local_identifiability(goodwind_oscillator_si; loglevel) + ifs_3 = find_identifiable_functions(goodwind_oscillator_si; loglevel) # Check outputs. @test sym_dict(gi_1) == sym_dict(gi_2) == sym_dict(gi_3) @@ -74,15 +77,15 @@ let d, X4 --> 0 end @unpack X2, X3 = rs_catalyst - gi_1 = assess_identifiability(rs_catalyst; measured_quantities=[X2, X3], known_p=[:k2f]) - li_1 = assess_local_identifiability(rs_catalyst; measured_quantities=[X2, X3], known_p=[:k2f]) - ifs_1 = find_identifiable_functions(rs_catalyst; measured_quantities=[X2, X3], known_p=[:k2f]) + gi_1 = assess_identifiability(rs_catalyst; measured_quantities = [X2, X3], known_p = [:k2f], loglevel) + li_1 = assess_local_identifiability(rs_catalyst; measured_quantities = [X2, X3], known_p = [:k2f], loglevel) + ifs_1 = find_identifiable_functions(rs_catalyst; measured_quantities = [X2, X3], known_p = [:k2f], loglevel) # Identifiability analysis for Catalyst converted to StructuralIdentifiability.jl model. - rs_ode = make_si_ode(rs_catalyst; measured_quantities=[X2, X3], known_p=[:k2f]) - gi_2 = assess_identifiability(rs_ode) - li_2 = assess_local_identifiability(rs_ode) - ifs_2 = find_identifiable_functions(rs_ode) + rs_ode = make_si_ode(rs_catalyst; measured_quantities = [X2, X3], known_p = [:k2f]) + gi_2 = assess_identifiability(rs_ode; loglevel) + li_2 = assess_local_identifiability(rs_ode; loglevel) + ifs_2 = find_identifiable_functions(rs_ode; loglevel) # Identifiability analysis for StructuralIdentifiability.jl model (declare this overwrites e.g. X2 variable etc.). rs_si = @ODEmodel( @@ -94,9 +97,9 @@ let y2(t) = X3, y3(t) = k2f ) - gi_3 = assess_identifiability(rs_si) - li_3 = assess_local_identifiability(rs_si) - ifs_3 = find_identifiable_functions(rs_si) + gi_3 = assess_identifiability(rs_si; loglevel) + li_3 = assess_local_identifiability(rs_si; loglevel) + ifs_3 = find_identifiable_functions(rs_si; loglevel) # Check outputs. @test sym_dict(gi_1) == sym_dict(gi_2) == sym_dict(gi_3) @@ -126,15 +129,15 @@ let (kA*X3, kD), Yi <--> Ya end @unpack X1, X2, X3, X4, k1, k2, Yi, Ya, k1, kD = rs_catalyst - gi_1 = assess_identifiability(rs_catalyst; measured_quantities=[X1 + Yi, Ya], known_p=[k1, kD]) - li_1 = assess_local_identifiability(rs_catalyst; measured_quantities=[X1 + Yi, Ya], known_p=[k1, kD]) - ifs_1 = find_identifiable_functions(rs_catalyst; measured_quantities=[X1 + Yi, Ya], known_p=[k1, kD]) + gi_1 = assess_identifiability(rs_catalyst; measured_quantities = [X1 + Yi, Ya], known_p = [k1, kD], loglevel) + li_1 = assess_local_identifiability(rs_catalyst; measured_quantities = [X1 + Yi, Ya], known_p = [k1, kD], loglevel) + ifs_1 = find_identifiable_functions(rs_catalyst; measured_quantities = [X1 + Yi, Ya], known_p = [k1, kD], loglevel) # Identifiability analysis for Catalyst converted to StructuralIdentifiability.jl model. rs_ode = make_si_ode(rs_catalyst; measured_quantities=[X1 + Yi, Ya], known_p=[k1, kD], remove_conserved=false) - gi_2 = assess_identifiability(rs_ode) - li_2 = assess_local_identifiability(rs_ode) - ifs_2 = find_identifiable_functions(rs_ode) + gi_2 = assess_identifiability(rs_ode; loglevel) + li_2 = assess_local_identifiability(rs_ode; loglevel) + ifs_2 = find_identifiable_functions(rs_ode; loglevel) # Identifiability analysis for StructuralIdentifiability.jl model (declare this overwrites e.g. X2 variable etc.). rs_si = @ODEmodel( @@ -150,9 +153,9 @@ let y3(t) = k1, y4(t) = kD ) - gi_3 = assess_identifiability(rs_si) - li_3 = assess_local_identifiability(rs_si) - ifs_3 = find_identifiable_functions(rs_si) + gi_3 = assess_identifiability(rs_si; loglevel) + li_3 = assess_local_identifiability(rs_si; loglevel) + ifs_3 = find_identifiable_functions(rs_si; loglevel) # Check outputs. @test sym_dict(gi_1) == sym_dict(gi_2) == sym_dict(gi_3) @@ -174,31 +177,31 @@ let (pₚ*E,dₚ), 0 <--> P end @unpack M, E, P, pₑ, pₚ, pₘ = goodwind_oscillator_catalyst - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[:M]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; known_p=[:pₑ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[:M], known_p=[:pₑ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[:M, :E], known_p=[:pₑ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[:M], known_p=[:pₑ, :pₚ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[:M, :E], known_p=[:pₑ, :pₚ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[M]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; known_p=[pₑ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[M], known_p=[pₑ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[M, E], known_p=[pₑ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[M], known_p=[pₑ, pₚ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[M, E], known_p=[pₑ, pₚ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[M + pₑ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[M + E, pₑ*M], known_p=[:pₑ]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities=[pₑ, pₚ], known_p=[pₑ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; known_p = [:pₑ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M], known_p = [:pₑ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M, :E], known_p = [:pₑ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M], known_p = [:pₑ, :pₚ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M, :E], known_p = [:pₑ, :pₚ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [M]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; known_p = [pₑ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [M], known_p = [pₑ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [M, E], known_p = [pₑ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [M], known_p = [pₑ, pₚ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [M, E], known_p = [pₑ, pₚ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [M + pₑ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [M + E, pₑ*M], known_p = [:pₑ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [pₑ, pₚ], known_p = [pₑ]) # Tests using model.component style (have to make system complete first). gw_osc_complt = complete(goodwind_oscillator_catalyst) - @test make_si_ode(gw_osc_complt; measured_quantities=[gw_osc_complt.M]) isa ODE - @test make_si_ode(gw_osc_complt; known_p=[gw_osc_complt.pₑ]) isa ODE - @test make_si_ode(gw_osc_complt; measured_quantities=[gw_osc_complt.M], known_p=[gw_osc_complt.pₑ]) isa ODE - @test make_si_ode(gw_osc_complt; measured_quantities=[gw_osc_complt.M, gw_osc_complt.E], known_p=[gw_osc_complt.pₑ]) isa ODE - @test make_si_ode(gw_osc_complt; measured_quantities=[gw_osc_complt.M], known_p=[gw_osc_complt.pₑ, gw_osc_complt.pₚ]) isa ODE - @test make_si_ode(gw_osc_complt; measured_quantities=[gw_osc_complt.M], known_p = [:pₚ]) isa ODE - @test make_si_ode(gw_osc_complt; measured_quantities=[gw_osc_complt.M*gw_osc_complt.E]) isa ODE + @test make_si_ode(gw_osc_complt; measured_quantities = [gw_osc_complt.M]) isa ODE + @test make_si_ode(gw_osc_complt; known_p = [gw_osc_complt.pₑ]) isa ODE + @test make_si_ode(gw_osc_complt; measured_quantities = [gw_osc_complt.M], known_p = [gw_osc_complt.pₑ]) isa ODE + @test make_si_ode(gw_osc_complt; measured_quantities = [gw_osc_complt.M, gw_osc_complt.E], known_p = [gw_osc_complt.pₑ]) isa ODE + @test make_si_ode(gw_osc_complt; measured_quantities = [gw_osc_complt.M], known_p = [gw_osc_complt.pₑ, gw_osc_complt.pₚ]) isa ODE + @test make_si_ode(gw_osc_complt; measured_quantities = [gw_osc_complt.M], known_p = [:pₚ]) isa ODE + @test make_si_ode(gw_osc_complt; measured_quantities = [gw_osc_complt.M*gw_osc_complt.E]) isa ODE end # Tests for hierarchical model with conservation laws at both top and internal levels. @@ -213,15 +216,15 @@ let @named rs_catalyst = compose(rs1, [rs2]) rs_catalyst = complete(rs_catalyst) @unpack X1, X2, k1, k2 = rs1 - gi_1 = assess_identifiability(rs_catalyst; measured_quantities=[X1, X2, rs2.X3], known_p=[k1]) - li_1 = assess_local_identifiability(rs_catalyst; measured_quantities=[X1, X2, rs2.X3], known_p=[k1]) - ifs_1 = find_identifiable_functions(rs_catalyst; measured_quantities=[X1, X2, rs2.X3], known_p=[k1]) + gi_1 = assess_identifiability(rs_catalyst; measured_quantities = [X1, X2, rs2.X3], known_p = [k1], loglevel) + li_1 = assess_local_identifiability(rs_catalyst; measured_quantities = [X1, X2, rs2.X3], known_p = [k1], loglevel) + ifs_1 = find_identifiable_functions(rs_catalyst; measured_quantities = [X1, X2, rs2.X3], known_p = [k1], loglevel) # Identifiability analysis for Catalyst converted to StructuralIdentifiability.jl model. - rs_ode = make_si_ode(rs_catalyst; measured_quantities=[X1, X2, rs2.X3], known_p=[k1]) - gi_2 = assess_identifiability(rs_ode) - li_2 = assess_local_identifiability(rs_ode) - ifs_2 = find_identifiable_functions(rs_ode) + rs_ode = make_si_ode(rs_catalyst; measured_quantities = [X1, X2, rs2.X3], known_p = [k1]) + gi_2 = assess_identifiability(rs_ode; loglevel) + li_2 = assess_local_identifiability(rs_ode; loglevel) + ifs_2 = find_identifiable_functions(rs_ode; loglevel) # Identifiability analysis for StructuralIdentifiability.jl model (declare this overwrites e.g. X2 variable etc.). rs_si = @ODEmodel( @@ -234,9 +237,9 @@ let y3(t) = rs2₊X3, y4(t) = k1 ) - gi_3 = assess_identifiability(rs_si) - li_3 = assess_local_identifiability(rs_si) - ifs_3 = find_identifiable_functions(rs_si) + gi_3 = assess_identifiability(rs_si; loglevel) + li_3 = assess_local_identifiability(rs_si; loglevel) + ifs_3 = find_identifiable_functions(rs_si; loglevel) # Check outputs. @test sym_dict(gi_1) == sym_dict(gi_3) @@ -326,9 +329,9 @@ let @unpack p, d, k, c1, c2 = rs # Tests identifiability assessment when all unknowns are measured. - gi_1 = assess_identifiability(rs; measured_quantities=[:X, :V, :C]) - li_1 = assess_local_identifiability(rs; measured_quantities=[:X, :V, :C]) - ifs_1 = find_identifiable_functions(rs; measured_quantities=[:X, :V, :C]) + gi_1 = assess_identifiability(rs; measured_quantities = [:X, :V, :C], loglevel) + li_1 = assess_local_identifiability(rs; measured_quantities = [:X, :V, :C], loglevel) + ifs_1 = find_identifiable_functions(rs; measured_quantities = [:X, :V, :C], loglevel) @test sym_dict(gi_1) == Dict([:X => :globally, :C => :globally, :V => :globally, :k => :globally, :c1 => :nonidentifiable, :c2 => :nonidentifiable, :p => :globally, :d => :globally]) @test sym_dict(li_1) == Dict([:X => 1, :C => 1, :V => 1, :k => 1, :c1 => 0, :c2 => 0, :p => 1, :d => 1]) @@ -336,9 +339,9 @@ let # Tests identifiability assessment when only variables are measured. # Checks that a parameter in an equation can be set as known. - gi_2 = assess_identifiability(rs; measured_quantities=[:V, :C], known_p = [:c1]) - li_2 = assess_local_identifiability(rs; measured_quantities=[:V, :C], known_p = [:c1]) - ifs_2 = find_identifiable_functions(rs; measured_quantities=[:V, :C], known_p = [:c1]) + gi_2 = assess_identifiability(rs; measured_quantities = [:V, :C], known_p = [:c1], loglevel) + li_2 = assess_local_identifiability(rs; measured_quantities = [:V, :C], known_p = [:c1], loglevel) + ifs_2 = find_identifiable_functions(rs; measured_quantities = [:V, :C], known_p = [:c1], loglevel) @test sym_dict(gi_2) == Dict([:X => :nonidentifiable, :C => :globally, :V => :globally, :k => :nonidentifiable, :c1 => :globally, :c2 => :nonidentifiable, :p => :nonidentifiable, :d => :globally]) @test sym_dict(li_2) == Dict([:X => 0, :C => 1, :V => 1, :k => 0, :c1 => 1, :c2 => 0, :p => 0, :d => 1]) @@ -354,7 +357,7 @@ let measured_quantities = [:X] # Computes bifurcation diagram. - @test_throws Exception assess_identifiability(incomplete_network; measured_quantities) - @test_throws Exception assess_local_identifiability(incomplete_network; measured_quantities) - @test_throws Exception find_identifiable_functions(incomplete_network; measured_quantities) + @test_throws Exception assess_identifiability(incomplete_network; measured_quantities, loglevel) + @test_throws Exception assess_local_identifiability(incomplete_network; measured_quantities, loglevels) + @test_throws Exception find_identifiable_functions(incomplete_network; measured_quantities, loglevel) end \ No newline at end of file diff --git a/test/miscellaneous_tests/stability_computation.jl b/test/miscellaneous_tests/stability_computation.jl index 36f6125f02..c786ec68cf 100644 --- a/test/miscellaneous_tests/stability_computation.jl +++ b/test/miscellaneous_tests/stability_computation.jl @@ -29,7 +29,7 @@ let :d => 0.5 + rand(rng)) # Computes stability using various jacobian options. - sss = hc_steady_states(rn, ps) + sss = hc_steady_states(rn, ps; show_progress=false) stabs_1 = [steady_state_stability(ss, rn, ps) for ss in sss] stabs_2 = [steady_state_stability(ss, rn, ps; ss_jac = ss_jac) for ss in sss] @@ -71,7 +71,7 @@ let ps_3 = [rn.k1 => 8.0, rn.k2 => 2.0, rn.k3 => 1.0, rn.k4 => 1.5, rn.kD1 => 0.5, rn.kD2 => 4.0] # Computes stability using various input forms, and checks that the output is correct. - sss = hc_steady_states(rn, ps_1; u0 = u0_1) + sss = hc_steady_states(rn, ps_1; u0 = u0_1, show_progress=false) for u0 in [u0_1, u0_2, u0_3, u0_4], ps in [ps_1, ps_2, ps_3] stab_1 = [steady_state_stability(ss, rn, ps) for ss in sss] ss_jac = steady_state_jac(rn; u0 = u0) diff --git a/test/reactionsystem_core/parameter_type_designation.jl b/test/reactionsystem_core/parameter_type_designation.jl index d6f7a7d13d..ca208a926e 100644 --- a/test/reactionsystem_core/parameter_type_designation.jl +++ b/test/reactionsystem_core/parameter_type_designation.jl @@ -64,7 +64,7 @@ let for p in p_alts oprob = ODEProblem(rs, u0, (0.0, 1000.0), p; abstol = 1e-10, reltol = 1e-10) sol = solve(oprob, Tsit5()) - @test all(sol[end] .≈ 1.0) + @test all(sol.u[end] .≈ 1.0) end end From 94d57a0cf371c1e290efc809b4f35cc43ab5bf4f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 12:39:39 -0400 Subject: [PATCH 225/446] remove term interface, update compat --- Project.toml | 8 +++----- ext/CatalystHomotopyContinuationExtension.jl | 2 +- src/Catalyst.jl | 2 +- test/dsl/dsl_basic_model_construction.jl | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Project.toml b/Project.toml index 11a402a18a..225d305c09 100644 --- a/Project.toml +++ b/Project.toml @@ -23,7 +23,6 @@ RuntimeGeneratedFunctions = "7e49a35a-f44a-4d26-94aa-eba1b4ca6b47" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" -TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [weakdeps] @@ -38,8 +37,8 @@ CatalystStructuralIdentifiabilityExtension = "StructuralIdentifiability" [compat] BifurcationKit = "0.3" -DataStructures = "0.18" Combinatorics = "1.0.2" +DataStructures = "0.18" DiffEqBase = "6.83.0" DocStringExtensions = "0.8, 0.9" DynamicPolynomials = "0.5" @@ -50,15 +49,14 @@ JumpProcesses = "9.3.2" LaTeXStrings = "1.3.0" Latexify = "0.14, 0.15, 0.16" MacroTools = "0.5.5" -ModelingToolkit = "9.11.0" +ModelingToolkit = "9.16.0" Parameters = "0.12" Reexport = "0.2, 1.0" Requires = "1.0" RuntimeGeneratedFunctions = "0.5.12" Setfield = "1" StructuralIdentifiability = "0.5.8" -Symbolics = "5.27" -TermInterface = "0.4.1" +Symbolics = "5.30.1" Unitful = "1.12.4" julia = "1.10" diff --git a/ext/CatalystHomotopyContinuationExtension.jl b/ext/CatalystHomotopyContinuationExtension.jl index 7fec5da6fb..a4785992fe 100644 --- a/ext/CatalystHomotopyContinuationExtension.jl +++ b/ext/CatalystHomotopyContinuationExtension.jl @@ -7,7 +7,7 @@ import ModelingToolkit as MT import HomotopyContinuation as HC import Setfield: @set import Symbolics: unwrap, wrap, Rewriters, symtype, issym -using TermInterface: iscall +using Symbolics: iscall # Creates and exports hc_steady_states function. include("CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl") diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 3308502e37..f37b67a7f5 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -23,7 +23,7 @@ using RuntimeGeneratedFunctions RuntimeGeneratedFunctions.init(@__MODULE__) import Symbolics: BasicSymbolic -using TermInterface: iscall +using Symbolics: iscall using ModelingToolkit: Symbolic, value, get_unknowns, get_ps, get_iv, get_systems, get_eqs, get_defaults, toparam, get_var_to_name, get_observed, getvar diff --git a/test/dsl/dsl_basic_model_construction.jl b/test/dsl/dsl_basic_model_construction.jl index d30a4ae05b..b79c229c6e 100644 --- a/test/dsl/dsl_basic_model_construction.jl +++ b/test/dsl/dsl_basic_model_construction.jl @@ -4,7 +4,7 @@ using DiffEqBase, Catalyst, Random, Test using ModelingToolkit: operation, get_unknowns, get_ps, get_eqs, get_systems, get_iv, nameof -using TermInterface: iscall +using Symbolics: iscall # Sets stable rng number. using StableRNGs From b97ee0711ce80196bcaba9099db5bec46a123013 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 12:45:22 -0400 Subject: [PATCH 226/446] init --- docs/src/model_creation/dsl_advanced.md | 13 ++++--------- docs/src/model_creation/dsl_basics.md | 10 +++++----- .../model_creation/examples/basic_CRN_library.md | 2 +- .../programmatic_generative_linear_pathway.md | 2 +- docs/src/model_creation/model_visualisation.md | 2 +- .../model_simulation/ode_simulation_performance.md | 4 ++-- .../src/model_simulation/simulation_introduction.md | 11 +++++------ docs/src/model_simulation/simulation_plotting.md | 2 +- .../simulation_structure_interfacing.md | 13 ++++++------- .../homotopy_continuation.md | 2 +- .../steady_state_functionality/nonlinear_solve.md | 2 +- 11 files changed, 28 insertions(+), 35 deletions(-) diff --git a/docs/src/model_creation/dsl_advanced.md b/docs/src/model_creation/dsl_advanced.md index f2d1d16bf5..a7dfc694c6 100644 --- a/docs/src/model_creation/dsl_advanced.md +++ b/docs/src/model_creation/dsl_advanced.md @@ -1,7 +1,7 @@ # [The Catalyst DSL - Advanced Features and Options](@id dsl_advanced_options) Within the Catalyst DSL, each line can represent either *a reaction* or *an option*. The [previous DSL tutorial](@ref dsl_description) described how to create reactions. This one will focus on options. These are typically used to supply a model with additional information. Examples include the declaration of initial condition/parameter default values, or the creation of observables. -All option designations begin with a declaration starting with `@`, followed by its input. E.g. the `@observables` option allows for the generation of observables. Each option can only be used once within each use of `@reaction_network`. A full list of options can be found [here](@ref ref), with most (but not all) being described in more detail below. This tutorial will also describe some additional advanced DSL features that do not involve using an option. +All option designations begin with a declaration starting with `@`, followed by its input. E.g. the `@observables` option allows for the generation of observables. Each option can only be used once within each use of `@reaction_network`. This tutorial will also describe some additional advanced DSL features that do not involve using an option. As a first step, we import Catalyst (which is required to run the tutorial): ```@example dsl_advanced_explicit_definitions @@ -130,7 +130,6 @@ oprob = ODEProblem(rn, u0, tspan, p) sol = solve(oprob) plot(sol) ``` -API for checking the default values of species and parameters can be found [here](@ref ref). ### [Setting parametric initial conditions](@id dsl_advanced_options_parametric_initial_conditions) In the previous section, we designated default values for initial conditions and parameters. However, the right-hand side of the designation accepts any valid expression (not only numeric values). While this can be used to set up some advanced default values, the most common use case is to designate a species's initial condition as a parameter. E.g. in the following example we represent the initial condition of `X` using the parameter `X₀`. @@ -198,13 +197,11 @@ two_state_system = @reaction_network begin end ``` -Each metadata has its own getter functions. E.g. we can get the description of the parameter `kA` using `ModelingToolkit.getdescription` (here we use [system indexing](@ref ref) to access the parameter): +Each metadata has its own getter functions. E.g. we can get the description of the parameter `kA` using `ModelingToolkit.getdescription`: ```@example dsl_advanced_metadata ModelingToolkit.getdescription(two_state_system.kA) ``` -It is not possible for the user to directly designate their own metadata. These have to first be added to Catalyst. Doing so is somewhat involved, and described in detail [here](@ref ref). A full list of metadata that can be used for species and/or parameters can be found [here](@ref ref). - ### [Designating constant-valued/fixed species parameters](@id dsl_advanced_options_constant_species) Catalyst enables the designation of parameters as `constantspecies`. These parameters can be used as species in reactions, however, their values are not changed by the reaction and remain constant throughout the simulation (unless changed by e.g. the [occurrence of an event]@ref constraint_equations_events). Practically, this is done by setting the parameter's `isconstantspecies` metadata to `true`. Here, we create a simple reaction where the species `X` is converted to `Xᴾ` at rate `k`. By designating `X` as a constant species parameter, we ensure that its quantity is unchanged by the occurrence of the reaction. @@ -391,11 +388,11 @@ Some final notes regarding observables: - The left-hand side of the observable declaration must contain a single symbol only (with the exception of metadata, which can also be supplied). - All quantities appearing on the right-hand side must be declared elsewhere within the `@reaction_network` call (either by being part of a reaction, or through the `@species`, `@parameters`, or `@variables` options). - Observables may not depend on other observables. -- Observables have their [dependent variable(s)](@ref ref) automatically assigned as the union of the dependent variables of the species and variables on which it depends. +- Observables have their dependent variable(s) automatically assigned as the union of the dependent variables of the species and variables on which it depends. ## [Specifying non-time independent variables](@id dsl_advanced_options_ivs) -As [described elsewhere](@ref ref), Catalyst's `ReactionSystem` models depend on a *time independent variable*, and potentially one or more *spatial independent variables*. By default, the independent variable `t` is used. We can declare another independent variable (which is automatically used as the default one) using the `@ivs` option. E.g. to use `τ` instead of `t` we can use +Catalyst's `ReactionSystem` models depend on a *time independent variable*, and potentially one or more *spatial independent variables*. By default, the independent variable `t` is used. We can declare another independent variable (which is automatically used as the default one) using the `@ivs` option. E.g. to use `τ` instead of `t` we can use ```@example dsl_advanced_ivs using Catalyst # hide rn = @reaction_network begin @@ -466,5 +463,3 @@ A reaction's metadata can be accessed using specific functions, e.g. `Catalyst.h rx = @reaction p, 0 --> X, [description="A production reaction"] Catalyst.getdescription(rx) ``` - -A list of all available reaction metadata can be found [here](@ref ref). \ No newline at end of file diff --git a/docs/src/model_creation/dsl_basics.md b/docs/src/model_creation/dsl_basics.md index a17e13a976..b3a3d2bea3 100644 --- a/docs/src/model_creation/dsl_basics.md +++ b/docs/src/model_creation/dsl_basics.md @@ -1,7 +1,7 @@ # [The Catalyst DSL - Introduction](@id dsl_description) In the [introduction to Catalyst](@ref introduction_to_catalyst) we described how the `@reaction_network` [macro](https://docs.julialang.org/en/v1/manual/metaprogramming/#man-macros) can be used to create chemical reaction network (CRN) models. This macro enables a so-called [domain-specific language](https://en.wikipedia.org/wiki/Domain-specific_language) (DSL) for creating CRN models. This tutorial will give a basic introduction on how to create Catalyst models using this macro (from now onwards called "*the Catalyst DSL*"). A [follow-up tutorial](@ref dsl_advanced_options) will describe some of the DSL's more advanced features. -The Catalyst DSL generates a [`ReactionSystem`](@ref) (the [julia structure](https://docs.julialang.org/en/v1/manual/types/#Composite-Types) Catalyst uses to represent CRN models). These can be created through alternative methods (e.g. [programmatically](@ref programmatic_CRN_construction) or [compositionally](@ref compositional_modeling)). A summary of the various ways to create `ReactionSystems`s can be found [here](@ref ref). [Previous](@ref introduction_to_catalyst) and [following](@ref simulation_intro) tutorials describe how to simulate models once they have been created using the DSL. This tutorial will solely focus on model creation. +The Catalyst DSL generates a [`ReactionSystem`](@ref) (the [julia structure](https://docs.julialang.org/en/v1/manual/types/#Composite-Types) Catalyst uses to represent CRN models). These can be created through alternative methods (e.g. [programmatically](@ref programmatic_CRN_construction) or [compositionally](@ref compositional_modeling)). [Previous](@ref introduction_to_catalyst) and [following](@ref simulation_intro) tutorials describe how to simulate models once they have been created using the DSL. This tutorial will solely focus on model creation. Before we begin, we will first load the Catalyst package (which is required to run the code). ```@example dsl_basics_intro @@ -217,7 +217,7 @@ latexify(rn_13_alt; form=:ode) ``` Here, while these models will generate identical ODE, SDE, and jump simulations, the chemical reaction network models themselves are not equivalent. Generally, as pointed out in the two notes below, using the second form is preferable. !!! warn - While `rn_13` and `rn_13_alt` will generate equivalent simulations, for jump simulations, the first model will have [reduced performance](@ref ref) (which generally are more performant when rates are constant). + While `rn_13` and `rn_13_alt` will generate equivalent simulations, for jump simulations, the first model will have reduced performance (which generally are more performant when rates are constant). !!! danger Catalyst automatically infers whether quantities appearing in the DSL are species or parameters (as described [here](@ref dsl_advanced_options_declaring_species_and_parameters)). Generally, anything that does not appear as a reactant is inferred to be a parameter. This means that if you want to model a reaction activated by a species (e.g. `kp*A, 0 --> P`), but that species does not occur as a reactant, it will be interpreted as a parameter. This can be handled by [manually declaring the system species](@ref dsl_advanced_options_declaring_species_and_parameters). @@ -282,7 +282,7 @@ end ``` !!! warn - Jump simulations cannot be performed for models with time-dependent rates without additional considerations, which are discussed [here](@ref ref). + Jump simulations cannot be performed for models with time-dependent rates without additional considerations. ## [Non-standard stoichiometries](@id dsl_description_stoichiometries) @@ -294,7 +294,7 @@ rn_16 = @reaction_network begin d, X --> 0 end ``` -It is also possible to have non-integer stoichiometric coefficients for substrates. However, in this case the [`combinatoric_ratelaw = false`](@ref ref) option must be used. We note that non-integer stoichiometric coefficients do not make sense in most fields, however, this feature is available for use for models where it does make sense. +It is also possible to have non-integer stoichiometric coefficients for substrates. However, in this case the `combinatoric_ratelaw = false` option must be used. We note that non-integer stoichiometric coefficients do not make sense in most fields, however, this feature is available for use for models where it does make sense. ### [Parametric stoichiometries](@id dsl_description_stoichiometries_parameters) It is possible for stoichiometric coefficients to be parameters. E.g. here we create a generic polymerisation system where `n` copies of `X` bind to form `Xn`: @@ -368,4 +368,4 @@ It should be noted that the following symbols are *not permitted* to be used as - `∅` ([used for production/degradation reactions](@ref dsl_description_symbols_empty_set)). - `im` (used in Julia to represent [complex numbers](https://docs.julialang.org/en/v1/manual/complex-and-rational-numbers/#Complex-Numbers)). - `nothing` (used in Julia to denote [nothing](https://docs.julialang.org/en/v1/base/constants/#Core.nothing)). -- `Γ` (used by Catalyst to represent [conserved quantities](@ref ref)). +- `Γ` (used by Catalyst to represent conserved quantities). diff --git a/docs/src/model_creation/examples/basic_CRN_library.md b/docs/src/model_creation/examples/basic_CRN_library.md index 3ac3a8d5d5..be9407e22d 100644 --- a/docs/src/model_creation/examples/basic_CRN_library.md +++ b/docs/src/model_creation/examples/basic_CRN_library.md @@ -79,7 +79,7 @@ oplt = plot(osol; idxs = X₁ + X₂, title = "Reaction rate equation (ODE)") splt = plot(ssol; idxs = X₁ + X₂, title = "Chemical Langevin equation (SDE)") plot(oplt, splt; lw = 3, ylimit = (99,101), size = (800,450), layout = (2,1)) ``` -Catalyst has special methods for working with conserved quantities, which are described [here](@ref ref). +Catalyst has special methods for working with conserved quantities, which are described here. ## [Michaelis-Menten enzyme kinetics](@id basic_CRN_library_mm) [Michaelis-Menten enzyme kinetics](https://en.wikipedia.org/wiki/Michaelis%E2%80%93Menten_kinetics) is a simple description of an enzyme ($E$) transforming a substrate ($S$) into a product ($P$). Under certain assumptions, it can be simplified to a single function (a Michaelis-Menten function) and used as a reaction rate. Here we instead present the full system model: diff --git a/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md b/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md index ae2be87b48..1121942934 100644 --- a/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md +++ b/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md @@ -77,7 +77,7 @@ plot!(sol_n10; idxs = :X10, label = "n = 10") ## [Modelling linear pathways using programmatic, generative, modelling](@id programmatic_generative_linear_pathway_generative) Above, we investigated the impact of linear pathways' lengths on their behaviours. Since the models were implemented using the DSL, we had to implement a new model for each pathway (in each case writing out all reactions). Here, we will instead show how [programmatic modelling](@ref programmatic_CRN_construction) can be used to generate pathways of arbitrary lengths. -First, we create a function, `generate_lp`, which creates a linear pathway model of length `n`. It utilises [*vector variables*](@ref ref) to create an arbitrary number of species, and also creates an [observable](@ref ref) for the final species of the chain. +First, we create a function, `generate_lp`, which creates a linear pathway model of length `n`. It utilises *vector variables* to create an arbitrary number of species, and also creates an observable for the final species of the chain. ```@example programmatic_generative_linear_pathway_generative using Catalyst # hide t = default_t() diff --git a/docs/src/model_creation/model_visualisation.md b/docs/src/model_creation/model_visualisation.md index a13f348749..7bf2a735c2 100644 --- a/docs/src/model_creation/model_visualisation.md +++ b/docs/src/model_creation/model_visualisation.md @@ -24,7 +24,7 @@ Here, we note that the output of `latexify(brusselator)` is identical to how a m latexify(brusselator; form = :ode) ``` !!! note - Internally, `latexify(brusselator; form = :ode)` calls `latexify(convert(ODESystem, brusselator))`. Hence, if you have already [generated the `ODESystem` corresponding to your model](@ref ref), it can be used directly as input to `latexify`. + Internally, `latexify(brusselator; form = :ode)` calls `latexify(convert(ODESystem, brusselator))`. Hence, if you have already generated the `ODESystem` corresponding to your model, it can be used directly as input to `latexify`. !!! note It should be possible to also generate SDEs through the `form = :sde` input. This feature is, however, currently broken. diff --git a/docs/src/model_simulation/ode_simulation_performance.md b/docs/src/model_simulation/ode_simulation_performance.md index 83e5fc7061..4fabcb9310 100644 --- a/docs/src/model_simulation/ode_simulation_performance.md +++ b/docs/src/model_simulation/ode_simulation_performance.md @@ -1,5 +1,5 @@ # [Advice for performant ODE simulations](@id ode_simulation_performance) -We have previously described how to perform ODE simulations of *chemical reaction network* (CRN) models. These simulations are typically fast and require little additional consideration. However, when a model is simulated many times (e.g. as a part of solving an [inverse problem](@ref ref)), or is very large, simulation run +We have previously described how to perform ODE simulations of *chemical reaction network* (CRN) models. These simulations are typically fast and require little additional consideration. However, when a model is simulated many times (e.g. as a part of solving an inverse problem), or is very large, simulation run times may become noticeable. Here we will give some advice on how to improve performance for these cases. Generally, there are few good ways to, before a simulation, determine the best options. Hence, while we below provide several options, if you face an application for which reducing run time is critical (e.g. if you need to simulate the same ODE many times), it might be required to manually trial these various options to see which yields the best performance ([BenchmarkTools.jl's](https://github.com/JuliaCI/BenchmarkTools.jl) `@btime` macro is useful for this purpose). It should be noted that the default options typically perform well, and it is primarily for large models where investigating alternative options is worthwhile. All ODE simulations of Catalyst models are performed using the OrdinaryDiffEq.jl package, [which documentation](https://docs.sciml.ai/DiffEqDocs/stable/) provides additional advice on performance. @@ -304,7 +304,7 @@ nothing # hide ``` Here have we increased the number of simulations to 10,000, since this is a more appropriate number for GPU parallelisation (as compared to the 100 simulations we performed in our CPU example). !!! note - Currently, declaration of static vectors requires [symbolic, rather than symbol, form](@ref ref) for species and parameters. Hence, we here first [`@unpack` these](@ref ref) before constructing `u0` and `ps` using `@SVector`. + Currently, declaration of static vectors requires symbolic, rather than symbol, form for species and parameters. Hence, we here first `@unpack` these before constructing `u0` and `ps` using `@SVector`. We can now simulate our model using a GPU-based ensemble algorithm. Currently, two such algorithms are available, `EnsembleGPUArray` and `EnsembleGPUKernel`. Their differences are that - Only `EnsembleGPUKernel` requires arrays to be static arrays (although it is still advantageous for `EnsembleGPUArray`). diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index 81894ac74e..54c84479b2 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -1,7 +1,7 @@ # [Model Simulation Introduction](@id simulation_intro) Catalyst's core functionality is the creation of *chemical reaction network* (CRN) models that can be simulated using ODE, SDE, and jump simulations. How such simulations are carried out has already been described in [Catalyst's introduction](@ref introduction_to_catalyst). This page provides a deeper introduction, giving some additional background and introducing various simulation-related options. -Here we will focus on the basics, with other sections of the simulation documentation describing various specialised features, or giving advice on performance. Anyone who plans on using Catalyst's simulation functionality extensively is recommended to also read the documentation on [solution plotting](@ref simulation_plotting), and on how to [interact with simulation problems, integrators, and solutions](@ref simulation_structure_interfacing). Anyone with an application for which performance is critical should consider reading the corresponding page on performance advice for [ODEs](@ref ode_simulation_performance), [SDEs](@ref ref), or [jump simulations](@ref ref). +Here we will focus on the basics, with other sections of the simulation documentation describing various specialised features, or giving advice on performance. Anyone who plans on using Catalyst's simulation functionality extensively is recommended to also read the documentation on [solution plotting](@ref simulation_plotting), and on how to [interact with simulation problems, integrators, and solutions](@ref simulation_structure_interfacing). Anyone with an application for which performance is critical should consider reading the corresponding page on performance advice for [ODEs](@ref ode_simulation_performance). ### [Background to CRN simulations](@id simulation_intro_theory) This section provides some brief theory on CRN simulations. For details on how to carry out these simulations in actual code, please skip to the following sections. @@ -113,7 +113,6 @@ More information on how to interact with solution structures is provided [here]( Some additional considerations: - If a model without parameters has been declared, only the first three arguments must be provided to `ODEProblem`. - While the first value of `tspan` will almost always be `0.0`, other starting times (both negative and positive) are possible. -- A discussion of various ways to represent species and parameters when designating their values in the `u0` and `ps` vectors can be found [here](@ref ref). ### [Designating solvers and solver options](@id simulation_intro_solver_options) @@ -170,7 +169,7 @@ nothing # hide ``` ## [Performing SDE simulations](@id simulation_intro_SDEs) -Catalyst uses the [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) package to perform SDE simulations. This section provides a brief introduction, with [StochasticDiffEq's documentation](https://docs.sciml.ai/StochasticDiffEq/stable/) providing a more extensive description. A dedicated section giving advice on how to optimise SDE simulation performance can be found [here](@ref ref). By default, Catalyst generates SDEs from CRN models using the chemical Langevin equation. +Catalyst uses the [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) package to perform SDE simulations. This section provides a brief introduction, with [StochasticDiffEq's documentation](https://docs.sciml.ai/StochasticDiffEq/stable/) providing a more extensive description. sBy default, Catalyst generates SDEs from CRN models using the chemical Langevin equation. SDE simulations are performed in a similar manner to ODE simulations. The only exception is that an `SDEProblem` is created (rather than an `ODEProblem`). Furthermore, the [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) package (rather than the OrdinaryDiffEq package) is required for performing simulations. Here we simulate the two-state model for the same parameter set as previously used: ```@example simulation_intro_sde @@ -280,7 +279,7 @@ While the `@default_noise_scaling` option is unavailable for [programmatically c ## [Performing jump simulations using stochastic chemical kinetics](@id simulation_intro_jumps) -Catalyst uses the [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) package to perform jump simulations. This section provides a brief introduction, with [JumpProcesses's documentation](https://docs.sciml.ai/JumpProcesses/stable/) providing a more extensive description. A dedicated section giving advice on how to optimise jump simulation performance can be found [here](@ref ref). +Catalyst uses the [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) package to perform jump simulations. This section provides a brief introduction, with [JumpProcesses's documentation](https://docs.sciml.ai/JumpProcesses/stable/) providing a more extensive description. Jump simulations are performed using so-called `JumpProblem`s. Unlike ODEs and SDEs (for which the corresponding problem types can be created directly), jump simulations require first creating an intermediary `DiscreteProblem`. In this example, we first declare our two-state model and its initial conditions, time span, and parameter values. ```@example simulation_intro_jumps @@ -325,7 +324,7 @@ Several different aggregators are available (a full list is provided [here](http jprob = JumpProblem(two_state_model, dprob, SortingDirect()) nothing # hide ``` -Especially for large systems, the choice of aggregator is relevant to simulation performance. A guide for aggregator selection is provided [here](@ref ref). +Especially for large systems, the choice of aggregator is relevant to simulation performance. Next, a simulation method can be provided (like for ODEs and SDEs) as the second argument to `solve`. Currently, the only relevant solver is `SSAStepper()` (which is the one used throughout Catalyst's documentation). Other choices are primarily relevant to combined ODE/SDE + jump simulations, or inexact simulations. These situations are described in more detail [here](https://docs.sciml.ai/JumpProcesses/stable/jump_solve/). @@ -337,4 +336,4 @@ circadian_model = @reaction_network begin d, P --> 0 end ``` -This type of model will generate so called [*variable rate jumps*](@ref ref). Simulation of such model is non-trivial (and Catalyst currently lacks a good interface for this). A detailed description of how to carry out jump simulations for models with time-dependant rates can be found [here](https://docs.sciml.ai/JumpProcesses/stable/tutorials/simple_poisson_process/#VariableRateJumps-for-processes-that-are-not-constant-between-jumps). \ No newline at end of file +This type of model will generate so called *variable rate jumps*. Simulation of such model is non-trivial (and Catalyst currently lacks a good interface for this). A detailed description of how to carry out jump simulations for models with time-dependant rates can be found [here](https://docs.sciml.ai/JumpProcesses/stable/tutorials/simple_poisson_process/#VariableRateJumps-for-processes-that-are-not-constant-between-jumps). \ No newline at end of file diff --git a/docs/src/model_simulation/simulation_plotting.md b/docs/src/model_simulation/simulation_plotting.md index b1e471cd04..56471fafcc 100644 --- a/docs/src/model_simulation/simulation_plotting.md +++ b/docs/src/model_simulation/simulation_plotting.md @@ -53,7 +53,7 @@ A useful option unique to Catalyst (and other DifferentialEquations.jl-based) pl ```@example simulation_plotting plot(sol; idxs = [:X]) ``` -can be used to plot `X` only. When only a single argument is given, the vector form is unnecessary (e.g. `idxs = :X` could have been used instead). If [symbolic species representation is used](@ref ref), this can be used to designate any algebraic expression(s) that should be plotted. E.g. here we plot the total concentration of $X + Y$ throughout the simulation: +can be used to plot `X` only. When only a single argument is given, the vector form is unnecessary (e.g. `idxs = :X` could have been used instead). If symbolic species representation is used, this can be used to designate any algebraic expression(s) that should be plotted. E.g. here we plot the total concentration of $X + Y$ throughout the simulation: ```@example simulation_plotting plot(sol; idxs = brusselator.X + brusselator.Y) ``` diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index 7acc660eca..eae285f296 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -84,7 +84,7 @@ nothing # hide ## [Interfacing integrator objects](@id simulation_structure_interfacing_integrators) -During a simulation, the solution is stored in an integrator object. Here, we will describe how to interface with these. The almost exclusive circumstance when integrator-interfacing is relevant is when simulation events are [implemented through callbacks](@ref ref). However, to demonstrate integrator indexing in this tutorial, we will create one through the `init` function (while circumstances where one might [want to use `init` function exist](https://docs.sciml.ai/DiffEqDocs/stable/basics/integrator/#Initialization-and-Stepping), since integrators are automatically created during simulations, these are rare). +During a simulation, the solution is stored in an integrator object. Here, we will describe how to interface with these. The almost exclusive circumstance when integrator-interfacing is relevant is when simulation events are implemented through callbacks. However, to demonstrate integrator indexing in this tutorial, we will create one through the `init` function (while circumstances where one might [want to use `init` function exist](https://docs.sciml.ai/DiffEqDocs/stable/basics/integrator/#Initialization-and-Stepping), since integrators are automatically created during simulations, these are rare). ```@example structure_indexing integrator = init(oprob) nothing # hide @@ -101,7 +101,7 @@ integrator.ps[:k₂] ``` Note that here, species-interfacing yields (or changes) a simulation's current value for a species, not its initial condition. -If you are interfacing with jump simulation integrators, please read [this, highly relevant, section](@ref ref). +If you are interfacing with jump simulation integrators, please read this, highly relevant, section ## [Interfacing solution objects](@id simulation_structure_interfacing_solutions) @@ -162,7 +162,7 @@ get_S(oprob) ``` ## [Interfacing using symbolic representations](@id simulation_structure_interfacing_symbolic_representation) -As [previously described](@ref ref), when e.g. [programmatic modelling is used](@ref programmatic_CRN_construction), species and parameters can be represented as *symbolic variables*. These can be used to index a problem, just like symbol-based representations can. Here we create a simple [two-state model](@ref rbasic_CRN_library_two_statesef) programmatically, and use its symbolic variables to check, and update, an initial condition: +When e.g. [programmatic modelling is used](@ref programmatic_CRN_construction), species and parameters can be represented as *symbolic variables*. These can be used to index a problem, just like symbol-based representations can. Here we create a simple [two-state model](@ref rbasic_CRN_library_two_statesef) programmatically, and use its symbolic variables to check, and update, an initial condition: ```@example structure_indexing_symbolic_variables using Catalyst t = default_t() @@ -185,7 +185,7 @@ oprob[X1] ``` Symbolic variables can be used to access or update species or parameters for all the cases when `Symbol`s can (including when using `remake` or e.g. `getu`). -An advantage when quantities are represented as symbolic variables is that [symbolic expressions](@ref ref) can be formed and used to index a structure. E.g. here we check the combined initial concentration of $X$ ($X1 + X2$) in our two-state problem: +An advantage when quantities are represented as symbolic variables is that symbolic expressions can be formed and used to index a structure. E.g. here we check the combined initial concentration of $X$ ($X1 + X2$) in our two-state problem: ```@example structure_indexing_symbolic_variables oprob[X1 + X2] ``` @@ -194,8 +194,7 @@ Just like symbolic variables can be used to directly interface with a structure, ```@example structure_indexing_symbolic_variables oprob[two_state_model.X1 + two_state_model.X2] ``` -This can be used to form symbolic expressions using model quantities when a model has been created using the DSL (as an alternative to [@unpack] -(@ref ref)). Alternatively, [creating an observable](@ref dsl_advanced_options_observables), and then interface using its `Symbol` representation, is also possible. +This can be used to form symbolic expressions using model quantities when a model has been created using the DSL (as an alternative to @unpack). Alternatively, [creating an observable](@ref dsl_advanced_options_observables), and then interface using its `Symbol` representation, is also possible. !!! warn - With interfacing with a simulating structure using symbolic variables stored in a `ReactionSystem` model, ensure that the [model is complete](@ref ref). \ No newline at end of file + With interfacing with a simulating structure using symbolic variables stored in a `ReactionSystem` model, ensure that the model is complete. \ No newline at end of file diff --git a/docs/src/steady_state_functionality/homotopy_continuation.md b/docs/src/steady_state_functionality/homotopy_continuation.md index ee81f88121..ef87a1beda 100644 --- a/docs/src/steady_state_functionality/homotopy_continuation.md +++ b/docs/src/steady_state_functionality/homotopy_continuation.md @@ -47,7 +47,7 @@ two_state_model = @reaction_network begin (k1,k2), X1 <--> X2 end ``` -Catalyst allows the conservation laws of such systems to be computed [using the `conservationlaws` function](@ref ref). By using these to reduce the dimensionality of the system, as well as specifying the initial amount of each species, homotopy continuation can again be used to find steady states. Here we do this by designating such an initial condition (which is used to compute the system's conserved quantities, in this case $X1 + X2$): +Catalyst allows the conservation laws of such systems to be computed using the `conservationlaws` function. By using these to reduce the dimensionality of the system, as well as specifying the initial amount of each species, homotopy continuation can again be used to find steady states. Here we do this by designating such an initial condition (which is used to compute the system's conserved quantities, in this case $X1 + X2$): ```@example hc_claws import HomotopyContinuation # hide ps = [:k1 => 2.0, :k2 => 1.0] diff --git a/docs/src/steady_state_functionality/nonlinear_solve.md b/docs/src/steady_state_functionality/nonlinear_solve.md index c3df99d484..f6b09cb22f 100644 --- a/docs/src/steady_state_functionality/nonlinear_solve.md +++ b/docs/src/steady_state_functionality/nonlinear_solve.md @@ -65,7 +65,7 @@ two_state_model = @reaction_network begin (k1,k2), X1 <--> X2 end ``` -It has an infinite number of steady states. To make steady state finding possible, information of the system's conserved quantities (here $C = X1 + X2$) must be provided. Since these can be computed from system initial conditions (`u0`, i.e. those provided when performing ODE simulations), designating an `u0` is often the best way. There are two ways to do this. First, one can perform [forward ODE simulation-based steady state finding](@ref steady_state_solving_simulation), using the initial condition as the initial `u` guess. Alternatively, any conserved quantities can be eliminated when the `NonlinearProblem` is created. This feature is supported by Catalyst's [conservation law finding and elimination feature](@ref ref). +It has an infinite number of steady states. To make steady state finding possible, information of the system's conserved quantities (here $C = X1 + X2$) must be provided. Since these can be computed from system initial conditions (`u0`, i.e. those provided when performing ODE simulations), designating an `u0` is often the best way. There are two ways to do this. First, one can perform [forward ODE simulation-based steady state finding](@ref steady_state_solving_simulation), using the initial condition as the initial `u` guess. Alternatively, any conserved quantities can be eliminated when the `NonlinearProblem` is created. This feature is supported by Catalyst's conservation law finding and elimination feature. To eliminate conservation laws we simply provide the `remove_conserved = true` argument to `NonlinearProblem`: ```@example steady_state_solving_claws From 9a9096e5fe0da0229718ce7d92f2f7f8c1f5614f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 13:05:22 -0400 Subject: [PATCH 227/446] remove nonsensical text --- docs/src/model_creation/examples/basic_CRN_library.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/model_creation/examples/basic_CRN_library.md b/docs/src/model_creation/examples/basic_CRN_library.md index be9407e22d..76287686b5 100644 --- a/docs/src/model_creation/examples/basic_CRN_library.md +++ b/docs/src/model_creation/examples/basic_CRN_library.md @@ -79,7 +79,6 @@ oplt = plot(osol; idxs = X₁ + X₂, title = "Reaction rate equation (ODE)") splt = plot(ssol; idxs = X₁ + X₂, title = "Chemical Langevin equation (SDE)") plot(oplt, splt; lw = 3, ylimit = (99,101), size = (800,450), layout = (2,1)) ``` -Catalyst has special methods for working with conserved quantities, which are described here. ## [Michaelis-Menten enzyme kinetics](@id basic_CRN_library_mm) [Michaelis-Menten enzyme kinetics](https://en.wikipedia.org/wiki/Michaelis%E2%80%93Menten_kinetics) is a simple description of an enzyme ($E$) transforming a substrate ($S$) into a product ($P$). Under certain assumptions, it can be simplified to a single function (a Michaelis-Menten function) and used as a reaction rate. Here we instead present the full system model: From efa4b4ef24f3cf20dcfc7e4e3efa9cbbb12c833c Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 13:06:38 -0400 Subject: [PATCH 228/446] update doc compat --- docs/Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 60c13c5e50..b92da6ef5d 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -51,7 +51,7 @@ IncompleteLU = "0.2" JumpProcesses = "9.11" Latexify = "0.16" LinearSolve = "2.30" -ModelingToolkit = "9.15" +ModelingToolkit = "9.16.0" NonlinearSolve = "3.12" Optim = "1.9" Optimization = "3.25" @@ -69,4 +69,4 @@ StaticArrays = "1.9" SteadyStateDiffEq = "2.2" StochasticDiffEq = "6.65" StructuralIdentifiability = "0.5.8" -Symbolics = "5.28" +Symbolics = "5.30.1" From 00be5037bb9b88d4f132d9650966ffec5e541e75 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 13:15:17 -0400 Subject: [PATCH 229/446] init --- .../catalyst_for_new_julia_users.md | 6 +++--- .../inverse_problems/optimization_ode_param_fitting.md | 2 +- .../examples/programmatic_generative_linear_pathway.md | 10 +++++----- .../src/model_simulation/ode_simulation_performance.md | 3 +-- .../steady_state_functionality/dynamical_systems.md | 8 ++++---- .../homotopy_continuation.md | 8 ++++---- docs/src/steady_state_functionality/nonlinear_solve.md | 2 +- 7 files changed, 19 insertions(+), 20 deletions(-) diff --git a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md index bfeee426cf..4dc4c5d44b 100644 --- a/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md +++ b/docs/src/introduction_to_catalyst/catalyst_for_new_julia_users.md @@ -1,5 +1,5 @@ # [Introduction to Catalyst and Julia for New Julia users](@id catalyst_for_new_julia_users) -The Catalyst tool for the modelling of chemical reaction networks is based in the Julia programming language. While experience in Julia programming is advantageous for using Catalyst, it is not necessary for accessing most of its basic features. This tutorial serves as an introduction to Catalyst for those unfamiliar with Julia, while also introducing some basic Julia concepts. Anyone who plans on using Catalyst extensively is recommended to familiarise oneself more thoroughly with the Julia programming language. A collection of resources for learning Julia can be found [here](https://julialang.org/learning/), and a full documentation is available [here](https://docs.julialang.org/en/v1/). A more practical (but also extensive) guide to Julia programming can be found [here](https://modernjuliaworkflows.github.io/writing/). +The Catalyst tool for the modelling of chemical reaction networks is based in the Julia programming language[^1][^2]. While experience in Julia programming is advantageous for using Catalyst, it is not necessary for accessing most of its basic features. This tutorial serves as an introduction to Catalyst for those unfamiliar with Julia, while also introducing some basic Julia concepts. Anyone who plans on using Catalyst extensively is recommended to familiarise oneself more thoroughly with the Julia programming language. A collection of resources for learning Julia can be found [here](https://julialang.org/learning/), and a full documentation is available [here](https://docs.julialang.org/en/v1/). A more practical (but also extensive) guide to Julia programming can be found [here](https://modernjuliaworkflows.github.io/writing/). Julia can be downloaded [here](https://julialang.org/downloads/). Generally, it is recommended to use the [*juliaup*](https://github.com/JuliaLang/juliaup) tool to install and update Julia. Furthermore, *Visual Studio Code* is a good IDE with [extensive Julia support](https://code.visualstudio.com/docs/languages/julia), and a good default choice. @@ -254,5 +254,5 @@ If you are a new Julia user who has used this tutorial, and there was something --- ## References -[^1]: [Jeff Bezanson, Alan Edelman, Stefan Karpinski, Viral B. Shah, *Julia: A Fresh Approach to Numerical Computing*, SIAM Review (2017).](https://epubs.siam.org/doi/abs/10.1137/141000671) -[^2]: [Torkel E. Loman, Yingbo Ma, Vasily Ilin, Shashi Gowda, Niklas Korsbo, Nikhil Yewale, Chris Rackauckas, Samuel A. Isaacson, *Catalyst: Fast and flexible modeling of reaction networks*, PLOS Computational Biology (2023).](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530) \ No newline at end of file +[^1]: [Torkel E. Loman, Yingbo Ma, Vasily Ilin, Shashi Gowda, Niklas Korsbo, Nikhil Yewale, Chris Rackauckas, Samuel A. Isaacson, *Catalyst: Fast and flexible modeling of reaction networks*, PLOS Computational Biology (2023).](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530) +[^2]: [Jeff Bezanson, Alan Edelman, Stefan Karpinski, Viral B. Shah, *Julia: A Fresh Approach to Numerical Computing*, SIAM Review (2017).](https://epubs.siam.org/doi/abs/10.1137/141000671) \ No newline at end of file diff --git a/docs/src/inverse_problems/optimization_ode_param_fitting.md b/docs/src/inverse_problems/optimization_ode_param_fitting.md index 6dd50c3e4a..4b82c0da70 100644 --- a/docs/src/inverse_problems/optimization_ode_param_fitting.md +++ b/docs/src/inverse_problems/optimization_ode_param_fitting.md @@ -1,5 +1,5 @@ # [Parameter Fitting for ODEs using Optimization.jl and DiffEqParamEstim.jl](@id optimization_parameter_fitting) -Fitting parameters to data involves solving an optimisation problem (that is, finding the parameter set that optimally fits your model to your data, typically by minimising a cost function). The SciML ecosystem's primary package for solving optimisation problems is [Optimization.jl](https://github.com/SciML/Optimization.jl). It provides access to a variety of solvers via a single common interface by wrapping a large number of optimisation libraries that have been implemented in Julia. +Fitting parameters to data involves solving an optimisation problem (that is, finding the parameter set that optimally fits your model to your data, typically by minimising a cost function)[^1]. The SciML ecosystem's primary package for solving optimisation problems is [Optimization.jl](https://github.com/SciML/Optimization.jl). It provides access to a variety of solvers via a single common interface by wrapping a large number of optimisation libraries that have been implemented in Julia. This tutorial demonstrates both how to create parameter fitting cost functions using the [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl) package, and how to use Optimization.jl to minimise these. Optimization.jl can also be used in other contexts, such as [finding parameter sets that maximise the magnitude of some system behaviour](@ref behaviour_optimisation). More details on how to use these packages can be found in their [respective](https://docs.sciml.ai/Optimization/stable/) [documentations](https://docs.sciml.ai/DiffEqParamEstim/stable/). diff --git a/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md b/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md index ae2be87b48..3f16e0b778 100644 --- a/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md +++ b/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md @@ -2,7 +2,7 @@ This example will show how to use programmatic, generative, modelling to model a system implicitly. I.e. rather than listing all system reactions explicitly, the reactions are implicitly generated from a simple set of rules. This example is specifically designed to show how [programmatic modelling](@ref programmatic_CRN_construction) enables *generative workflows* (demonstrating one of its advantages as compared to [DSL-based modelling](@ref dsl_description)). In our example, we will model linear pathways, so we will first introduce these. Next, we will model them first using the DSL, and then using a generative programmatic workflow. ## [Linear pathways](@id programmatic_generative_linear_pathway_intro) -Linear pathways consists of a series of species ($X_0$, $X_1$, $X_2$, ..., $X_n$) where each activates the subsequent one. These are often modelled through the following reaction system: +Linear pathways consists of a series of species ($X_0$, $X_1$, $X_2$, ..., $X_n$) where each activates the subsequent one[^1]. These are often modelled through the following reaction system: ```math X_{i-1}/\tau,\hspace{0.33cm} ∅ \to X_{i}\\ 1/\tau,\hspace{0.33cm} X_{i} \to ∅ @@ -21,7 +21,7 @@ for some kernel $g(\tau)$. Here, a common kernel is a [gamma distribution](https ```math g(\tau; \alpha, \beta) = \frac{\beta^{\alpha}\tau^{\alpha-1}}{\Gamma(\alpha)}e^{-\beta\tau} ``` -When this is converted to an ODE, this generates an integro-differential equation. These (as well as the simpler delay differential equations) can be difficult to solve and analyse (especially when SDE or jump simulations are desired). Here, *the linear chain trick* can be used to instead model the delay as a linear pathway of the form described above[^1]. A result by Fargue shows that this is equivalent to a gamma-distributed delay, where $\alpha$ is equivalent to $n$ (the number of species in our linear pathway) and $\beta$ to %\tau$ (the delay length term)[^2]. While modelling time delays using the linear chain trick introduces additional system species, it is often advantageous as it enables simulations using standard ODE, SDE, and Jump methods. +When this is converted to an ODE, this generates an integro-differential equation. These (as well as the simpler delay differential equations) can be difficult to solve and analyse (especially when SDE or jump simulations are desired). Here, *the linear chain trick* can be used to instead model the delay as a linear pathway of the form described above[^2]. A result by Fargue shows that this is equivalent to a gamma-distributed delay, where $\alpha$ is equivalent to $n$ (the number of species in our linear pathway) and $\beta$ to %\tau$ (the delay length term)[^3]. While modelling time delays using the linear chain trick introduces additional system species, it is often advantageous as it enables simulations using standard ODE, SDE, and Jump methods. ## [Modelling linear pathways using the DSL](@id programmatic_generative_linear_pathway_dsl) It is known that two linear pathways have similar delays if the following equality holds: @@ -132,6 +132,6 @@ plot!(sol_n20; idxs = :Xend, label = "n = 20") --- ## References -[^1]: [J. Metz, O. Diekmann *The Abstract Foundations of Linear Chain Trickery* (1991).](https://ir.cwi.nl/pub/1559/1559D.pdf) -[^2]: D. Fargue *Reductibilite des systemes hereditaires a des systemes dynamiques (regis par des equations differentielles aux derivees partielles)*, Comptes rendus de l'Académie des Sciences (1973). -[^3]: [N. Korsbo, H. Jönsson *It’s about time: Analysing simplifying assumptions for modelling multi-step pathways in systems biology*, PLoS Computational Biology (2020).](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1007982) \ No newline at end of file +[^1]: [N. Korsbo, H. Jönsson *It’s about time: Analysing simplifying assumptions for modelling multi-step pathways in systems biology*, PLoS Computational Biology (2020).](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1007982) +[^2]: [J. Metz, O. Diekmann *The Abstract Foundations of Linear Chain Trickery* (1991).](https://ir.cwi.nl/pub/1559/1559D.pdf) +[^3]: D. Fargue *Reductibilite des systemes hereditaires a des systemes dynamiques (regis par des equations differentielles aux derivees partielles)*, Comptes rendus de l'Académie des Sciences (1973). \ No newline at end of file diff --git a/docs/src/model_simulation/ode_simulation_performance.md b/docs/src/model_simulation/ode_simulation_performance.md index 83e5fc7061..ea05196622 100644 --- a/docs/src/model_simulation/ode_simulation_performance.md +++ b/docs/src/model_simulation/ode_simulation_performance.md @@ -1,6 +1,5 @@ # [Advice for performant ODE simulations](@id ode_simulation_performance) -We have previously described how to perform ODE simulations of *chemical reaction network* (CRN) models. These simulations are typically fast and require little additional consideration. However, when a model is simulated many times (e.g. as a part of solving an [inverse problem](@ref ref)), or is very large, simulation run -times may become noticeable. Here we will give some advice on how to improve performance for these cases. +We have previously described how to perform ODE simulations of *chemical reaction network* (CRN) models. These simulations are typically fast and require little additional consideration. However, when a model is simulated many times (e.g. as a part of solving an [inverse problem](@ref ref)), or is very large, simulation run times may become noticeable. Here we will give some advice on how to improve performance for these cases [^1]. Generally, there are few good ways to, before a simulation, determine the best options. Hence, while we below provide several options, if you face an application for which reducing run time is critical (e.g. if you need to simulate the same ODE many times), it might be required to manually trial these various options to see which yields the best performance ([BenchmarkTools.jl's](https://github.com/JuliaCI/BenchmarkTools.jl) `@btime` macro is useful for this purpose). It should be noted that the default options typically perform well, and it is primarily for large models where investigating alternative options is worthwhile. All ODE simulations of Catalyst models are performed using the OrdinaryDiffEq.jl package, [which documentation](https://docs.sciml.ai/DiffEqDocs/stable/) provides additional advice on performance. diff --git a/docs/src/steady_state_functionality/dynamical_systems.md b/docs/src/steady_state_functionality/dynamical_systems.md index 45847a12f8..2f3b1cd4c2 100644 --- a/docs/src/steady_state_functionality/dynamical_systems.md +++ b/docs/src/steady_state_functionality/dynamical_systems.md @@ -1,5 +1,5 @@ # [Analysing model steady state properties with DynamicalSystems.jl](@id dynamical_systems) -The [DynamicalSystems.jl package](https://github.com/JuliaDynamics/DynamicalSystems.jl) implements a wide range of methods for analysing dynamical systems. This includes both continuous-time systems (i.e. ODEs) and discrete-times ones (difference equations, however, these are not relevant to chemical reaction network modelling). Here we give two examples of how DynamicalSystems.jl can be used, with the package's [documentation describing many more features](https://juliadynamics.github.io/DynamicalSystemsDocs.jl/dynamicalsystems/dev/tutorial/). Finally, it should also be noted that DynamicalSystems.jl contain several tools for [analysing data measured from dynamical systems](https://juliadynamics.github.io/DynamicalSystemsDocs.jl/dynamicalsystems/dev/contents/#Exported-submodules). +The [DynamicalSystems.jl package](https://github.com/JuliaDynamics/DynamicalSystems.jl) implements a wide range of methods for analysing dynamical systems[^1][^2]. This includes both continuous-time systems (i.e. ODEs) and discrete-times ones (difference equations, however, these are not relevant to chemical reaction network modelling). Here we give two examples of how DynamicalSystems.jl can be used, with the package's [documentation describing many more features](https://juliadynamics.github.io/DynamicalSystemsDocs.jl/dynamicalsystems/dev/tutorial/). Finally, it should also be noted that DynamicalSystems.jl contain several tools for [analysing data measured from dynamical systems](https://juliadynamics.github.io/DynamicalSystemsDocs.jl/dynamicalsystems/dev/contents/#Exported-submodules). ## [Finding basins of attraction](@id dynamical_systems_basins_of_attraction) Given enough time, an ODE will eventually reach a so-called [*attractor*](https://en.wikipedia.org/wiki/Attractor). For chemical reaction networks (CRNs), this will typically be either a *steady state* or a *limit cycle*. Since ODEs are deterministic, which attractor a simulation will reach is uniquely determined by the initial condition (assuming parameter values are fixed). Conversely, each attractor is associated with a set of initial conditions such that model simulations originating in these will tend to that attractor. These sets are called *basins of attraction*. Here, phase space (the space of all possible states of the system) can be divided into a number of basins of attraction equal to the number of attractors. @@ -149,6 +149,6 @@ If you want to learn more about analysing dynamical systems, including chaotic b --- ## References -[^1]: [G. Datseris, U. Parlitz, *Nonlinear dynamics: A concise introduction interlaced with code*, Springer (2022).](https://link.springer.com/book/10.1007/978-3-030-91032-7) -[^2]: [S. H. Strogatz, *Nonlinear Dynamics and Chaos*, Westview Press (1994).](http://users.uoa.gr/~pjioannou/nonlin/Strogatz,%20S.%20H.%20-%20Nonlinear%20Dynamics%20And%20Chaos.pdf) -[^3]: [A. M. Lyapunov, *The general problem of the stability of motion*, International Journal of Control (1992).](https://www.tandfonline.com/doi/abs/10.1080/00207179208934253) \ No newline at end of file +[^1]: [S. H. Strogatz, *Nonlinear Dynamics and Chaos*, Westview Press (1994).](http://users.uoa.gr/~pjioannou/nonlin/Strogatz,%20S.%20H.%20-%20Nonlinear%20Dynamics%20And%20Chaos.pdf) +[^2]: [A. M. Lyapunov, *The general problem of the stability of motion*, International Journal of Control (1992).](https://www.tandfonline.com/doi/abs/10.1080/00207179208934253) +[^3]: [G. Datseris, U. Parlitz, *Nonlinear dynamics: A concise introduction interlaced with code*, Springer (2022).](https://link.springer.com/book/10.1007/978-3-030-91032-7) \ No newline at end of file diff --git a/docs/src/steady_state_functionality/homotopy_continuation.md b/docs/src/steady_state_functionality/homotopy_continuation.md index ee81f88121..82a53d7c69 100644 --- a/docs/src/steady_state_functionality/homotopy_continuation.md +++ b/docs/src/steady_state_functionality/homotopy_continuation.md @@ -6,8 +6,8 @@ method guaranteed to find all steady states for a system that has multiple ones. However, many chemical reaction networks generate polynomial systems (for example those which are purely mass action or have only have [Hill functions](https://en.wikipedia.org/wiki/Hill_equation_(biochemistry)) with integer Hill exponents). The roots of these can reliably be found through a -*homotopy continuation* algorithm [^1]. This is implemented in Julia through the -[HomotopyContinuation.jl](https://www.juliahomotopycontinuation.org/) package. +*homotopy continuation* algorithm[^1][^2]. This is implemented in Julia through the +[HomotopyContinuation.jl](https://www.juliahomotopycontinuation.org/) package[^3]. Catalyst contains a special homotopy continuation extension that is loaded whenever HomotopyContinuation.jl is. This exports a single function, `hc_steady_states`, that can be used to find the steady states of any `ReactionSystem` structure. @@ -79,5 +79,5 @@ If you use this functionality in your research, please cite the following paper --- ## References [^1]: [Andrew J Sommese, Charles W Wampler *The Numerical Solution of Systems of Polynomials Arising in Engineering and Science*, World Scientific (2005).](https://www.worldscientific.com/worldscibooks/10.1142/5763#t=aboutBook) -[^2]: [Paul Breiding, Sascha Timme, *HomotopyContinuation.jl: A Package for Homotopy Continuation in Julia*, International Congress on Mathematical Software (2018).](https://link.springer.com/chapter/10.1007/978-3-319-96418-8_54) -[^43]: [Daniel J. Bates, Paul Breiding, Tianran Chen, Jonathan D. Hauenstein, Anton Leykin, Frank Sottile, *Numerical Nonlinear Algebra*, arXiv (2023).](https://arxiv.org/abs/2302.08585) \ No newline at end of file +[^2]: [Daniel J. Bates, Paul Breiding, Tianran Chen, Jonathan D. Hauenstein, Anton Leykin, Frank Sottile, *Numerical Nonlinear Algebra*, arXiv (2023).](https://arxiv.org/abs/2302.08585) +[^3]: [Paul Breiding, Sascha Timme, *HomotopyContinuation.jl: A Package for Homotopy Continuation in Julia*, International Congress on Mathematical Software (2018).](https://link.springer.com/chapter/10.1007/978-3-319-96418-8_54) \ No newline at end of file diff --git a/docs/src/steady_state_functionality/nonlinear_solve.md b/docs/src/steady_state_functionality/nonlinear_solve.md index c3df99d484..170f4c83e8 100644 --- a/docs/src/steady_state_functionality/nonlinear_solve.md +++ b/docs/src/steady_state_functionality/nonlinear_solve.md @@ -1,7 +1,7 @@ # [Finding Steady States using NonlinearSolve.jl and SteadyStateDiffEq.jl](@id steady_state_solving) Catalyst `ReactionSystem` models can be converted to ODEs (through [the reaction rate equation](@ref introduction_to_catalyst_ratelaws)). We have previously described how these ODEs' steady states can be found through [homotopy continuation](@ref homotopy_continuation). Generally, homotopy continuation (due to its ability to find *all* of a system's steady states) is the preferred approach. However, Catalyst supports two additional approaches for finding steady states: -- Through solving the nonlinear system produced by setting all ODE differentials to 0. +- Through solving the nonlinear system produced by setting all ODE differentials to 0[^1]. - Through forward ODE simulation from an initial condition until a steady state has been reached. While these approaches only find a single steady state, they offer two advantages as compared to homotopy continuation: From 300d5e09961b8c7be06730208325f77463a0689b Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 14:02:10 -0400 Subject: [PATCH 230/446] up --- .../homotopy_continuation_extension.jl | 2 +- src/reactionsystem.jl | 2 +- src/reactionsystem_serialisation/serialisation_support.jl | 2 +- test/dsl/dsl_options.jl | 4 ++-- test/reactionsystem_core/coupled_equation_crn_systems.jl | 8 ++++---- test/reactionsystem_core/reactionsystem.jl | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl index 6b70922e45..db7a8ff0c4 100644 --- a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl +++ b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl @@ -67,7 +67,7 @@ function make_int_exps(expr) wrap(Rewriters.Postwalk(Rewriters.PassThrough(___make_int_exps))(unwrap(expr))).val end function ___make_int_exps(expr) - !istree(expr) && return expr + !iscall(expr) && return expr if (operation(expr) == ^) if isinteger(arguments(expr)[2]) return arguments(expr)[1]^Int64(arguments(expr)[2]) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 8373bf4990..bf20c94113 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -1479,4 +1479,4 @@ function validate(rs::ReactionSystem, info::String = "") end # Checks if a unit consist of exponents with base 1 (and is this unitless). -unitless_exp(u) = istree(u) && (operation(u) == ^) && (arguments(u)[1] == 1) +unitless_exp(u) = iscall(u) && (operation(u) == ^) && (arguments(u)[1] == 1) diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 6d8b2152df..18a13c0d80 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -240,7 +240,7 @@ const SKIPPED_METADATA = [ModelingToolkit.MTKVariableTypeCtx, Symbolics.Variable # Potentially strips the call for a symbolics. E.g. X(t) becomes X (but p remains p). This is used # when variables are written to files, as in code they are used without the call part. function strip_call(sym) - return istree(sym) ? Sym{Real}(Symbolics.getname(sym)) : sym + return iscall(sym) ? Sym{Real}(Symbolics.getname(sym)) : sym end # For an vector of symbolics, creates a dictionary taking each symbolics to each call-stripped form. diff --git a/test/dsl/dsl_options.jl b/test/dsl/dsl_options.jl index d12cbd4a45..85dd068aec 100644 --- a/test/dsl/dsl_options.jl +++ b/test/dsl/dsl_options.jl @@ -574,8 +574,8 @@ let end end V,W = getfield.(observed(rn), :lhs) - @test isequal(arguments(ModelingToolkit.unwrap(V)), Any[Catalyst.get_iv(rs), Catalyst.get_sivs(rn)[1], Catalyst.get_sivs(rn)[2]]) - @test isequal(arguments(ModelingToolkit.unwrap(W)), Any[Catalyst.get_iv(rs), Catalyst.get_sivs(rn)[2]]) + @test isequal(arguments(ModelingToolkit.unwrap(V)), Any[Catalyst.get_iv(rn), Catalyst.get_sivs(rn)[1], Catalyst.get_sivs(rn)[2]]) + @test isequal(arguments(ModelingToolkit.unwrap(W)), Any[Catalyst.get_iv(rn), Catalyst.get_sivs(rn)[2]]) end # Checks that metadata is written properly. diff --git a/test/reactionsystem_core/coupled_equation_crn_systems.jl b/test/reactionsystem_core/coupled_equation_crn_systems.jl index 33db205dd9..27a77af97d 100644 --- a/test/reactionsystem_core/coupled_equation_crn_systems.jl +++ b/test/reactionsystem_core/coupled_equation_crn_systems.jl @@ -47,7 +47,7 @@ let # Checks that the correct steady state is found through ODEProblem. oprob = ODEProblem(coupled_rs, u0, tspan, ps) osol = solve(oprob, Vern7(); abstol = 1e-8, reltol = 1e-8) - @test osol[end] ≈ [2.0, 1.0] + @test osol.u[end] ≈ [2.0, 1.0] # Checks that the correct steady state is found through NonlinearProblem. nlprob = NonlinearProblem(coupled_rs, u0, ps) @@ -116,7 +116,7 @@ let for coupled_rs in [coupled_rs_prog, coupled_rs_extended, coupled_rs_dsl] oprob = ODEProblem(coupled_rs, u0, tspan, ps) osol = solve(oprob, Vern7(); abstol = 1e-8, reltol = 1e-8) - osol[end] ≈ [10.0, 10.0, 11.0, 11.0] + osol.u[end] ≈ [10.0, 10.0, 11.0, 11.0] end end @@ -160,7 +160,7 @@ let # Checks that the correct steady state is found through ODEProblem. oprob = ODEProblem(coupled_rs, u0, tspan, ps; structural_simplify = true) osol = solve(oprob, Rosenbrock23(); abstol = 1e-8, reltol = 1e-8) - @test osol[end] ≈ [2.0, 3.0] + @test osol.u[end] ≈ [2.0, 3.0] # Checks that the correct steady state is found through NonlinearProblem. nlprob = NonlinearProblem(coupled_rs, u0, ps) @@ -207,7 +207,7 @@ let osol = solve(oprob, Rosenbrock23(); abstol = 1e-8, reltol = 1e-8) sssol = solve(ssprob, DynamicSS(Rosenbrock23()); abstol = 1e-8, reltol = 1e-8) nlsol = solve(nlprob; abstol = 1e-8, reltol = 1e-8) - @test osol[end] ≈ sssol ≈ nlsol + @test osol.u[end] ≈ sssol ≈ nlsol end diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index 6876026e0b..65e42180cc 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -356,7 +356,7 @@ let oprob1 = ODEProblem(osys, u0map, tspan, pmap) sts = [B, D, E, C] syms = [:B, :D, :E, :C] - ofun = ODEFunction(f!; syms) + ofun = ODEFunction(f!; sys = ModelingToolkit.SymbolCache(syms)) oprob2 = ODEProblem(ofun, u0, tspan, p) saveat = tspan[2] / 50 abstol = 1e-10 From 1f8d805fd01e2cccf1fdc168d72f98cfa431b229 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 16:17:11 -0400 Subject: [PATCH 231/446] fix unnecessary logging --- Project.toml | 3 ++- test/extensions/structural_identifiability.jl | 14 +++++++------- test/miscellaneous_tests/stability_computation.jl | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Project.toml b/Project.toml index 225d305c09..b9371b3656 100644 --- a/Project.toml +++ b/Project.toml @@ -67,6 +67,7 @@ DomainSets = "5b8099bc-c8ec-5219-889f-1d9e522a28bf" Graphviz_jll = "3c863552-8265-54e4-a6dc-903eb78fde85" HomotopyContinuation = "f213a82b-91d6-5c5d-acf7-10f1c761b327" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" @@ -83,4 +84,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [targets] -test = ["BifurcationKit", "DiffEqCallbacks", "DomainSets", "Graphviz_jll", "HomotopyContinuation", "NonlinearSolve", "OrdinaryDiffEq", "Plots", "Random", "SafeTestsets", "SciMLBase", "SciMLNLSolve", "StableRNGs", "Statistics", "SteadyStateDiffEq", "StochasticDiffEq", "StructuralIdentifiability", "Test", "Unitful"] +test = ["BifurcationKit", "DiffEqCallbacks", "DomainSets", "Graphviz_jll", "HomotopyContinuation", "Logging", "NonlinearSolve", "OrdinaryDiffEq", "Plots", "Random", "SafeTestsets", "SciMLBase", "SciMLNLSolve", "StableRNGs", "Statistics", "SteadyStateDiffEq", "StochasticDiffEq", "StructuralIdentifiability", "Test", "Unitful"] diff --git a/test/extensions/structural_identifiability.jl b/test/extensions/structural_identifiability.jl index f3617c3409..6b411cc9db 100644 --- a/test/extensions/structural_identifiability.jl +++ b/test/extensions/structural_identifiability.jl @@ -37,9 +37,9 @@ let # Identifiability analysis for Catalyst converted to StructuralIdentifiability.jl model. si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M]) - gi_2 = assess_identifiability(si_catalyst_ode) - li_2 = assess_local_identifiability(si_catalyst_ode) - ifs_2 = find_identifiable_functions(si_catalyst_ode) + gi_2 = assess_identifiability(si_catalyst_ode; loglevel) + li_2 = assess_local_identifiability(si_catalyst_ode; loglevel) + ifs_2 = find_identifiable_functions(si_catalyst_ode; loglevel) # Identifiability analysis for StructuralIdentifiability.jl model (declare this overwrites e.g. X2 variable etc.). goodwind_oscillator_si = @ODEmodel( @@ -264,14 +264,14 @@ let k1, x1 --> x2 end # Measure the source - id_report = assess_identifiability(rs, measured_quantities = [:x1]) + id_report = assess_identifiability(rs; measured_quantities = [:x1], loglevel) @test sym_dict(id_report) == Dict( :x1 => :globally, :x2 => :nonidentifiable, :k1 => :globally ) # Measure the target instead - id_report = assess_identifiability(rs, measured_quantities = [:x2]) + id_report = assess_identifiability(rs; measured_quantities = [:x2], loglevel) @test sym_dict(id_report) == Dict( :x1 => :globally, :x2 => :globally, @@ -303,13 +303,13 @@ let 1, x1 --> x2 1, x2 --> x3 end - id_report = assess_identifiability(rs, measured_quantities = [:x3]) + id_report = assess_identifiability(rs; measured_quantities = [:x3], loglevel) @test sym_dict(id_report) == Dict( :x1 => :globally, :x2 => :globally, :x3 => :globally, ) - @test length(find_identifiable_functions(rs, measured_quantities = [:x3])) == 1 + @test length(find_identifiable_functions(rs; measured_quantities = [:x3], loglevel)) == 1 end diff --git a/test/miscellaneous_tests/stability_computation.jl b/test/miscellaneous_tests/stability_computation.jl index c786ec68cf..542fcd4e51 100644 --- a/test/miscellaneous_tests/stability_computation.jl +++ b/test/miscellaneous_tests/stability_computation.jl @@ -29,7 +29,7 @@ let :d => 0.5 + rand(rng)) # Computes stability using various jacobian options. - sss = hc_steady_states(rn, ps; show_progress=false) + sss = hc_steady_states(rn, ps; show_progress = false) stabs_1 = [steady_state_stability(ss, rn, ps) for ss in sss] stabs_2 = [steady_state_stability(ss, rn, ps; ss_jac = ss_jac) for ss in sss] @@ -71,7 +71,7 @@ let ps_3 = [rn.k1 => 8.0, rn.k2 => 2.0, rn.k3 => 1.0, rn.k4 => 1.5, rn.kD1 => 0.5, rn.kD2 => 4.0] # Computes stability using various input forms, and checks that the output is correct. - sss = hc_steady_states(rn, ps_1; u0 = u0_1, show_progress=false) + sss = hc_steady_states(rn, ps_1; u0 = u0_1, show_progress = false) for u0 in [u0_1, u0_2, u0_3, u0_4], ps in [ps_1, ps_2, ps_3] stab_1 = [steady_state_stability(ss, rn, ps) for ss in sss] ss_jac = steady_state_jac(rn; u0 = u0) From 0f6cab61efdb029e70c128fcb0e446ff2e48f123 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 17:41:35 -0400 Subject: [PATCH 232/446] SI updates --- ext/CatalystStructuralIdentifiabilityExtension.jl | 1 + .../structural_identifiability_extension.jl | 8 ++++---- test/extensions/structural_identifiability.jl | 10 +++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ext/CatalystStructuralIdentifiabilityExtension.jl b/ext/CatalystStructuralIdentifiabilityExtension.jl index 0a442affa8..ed80952bd1 100644 --- a/ext/CatalystStructuralIdentifiabilityExtension.jl +++ b/ext/CatalystStructuralIdentifiabilityExtension.jl @@ -2,6 +2,7 @@ module CatalystStructuralIdentifiabilityExtension # Fetch packages. using Catalyst +import DataStructures.OrderedDict import StructuralIdentifiability as SI # Creates and exports hc_steady_states function. diff --git a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl index d4cc1ead23..c5906eaf6e 100644 --- a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl +++ b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl @@ -189,8 +189,8 @@ function make_measured_quantities( rs::ReactionSystem, measured_quantities::Vector{T}, known_p::Vector{S}, conseqs; ignore_no_measured_warn = false) where {T, S} # Warning if the user didn't give any measured quantities. - if ignore_no_measured_warn || isempty(measured_quantities) - @warn "No measured quantity provided to the `measured_quantities` argument, any further identifiability analysis will likely fail. You can disable this warning by setting `ignore_no_measured_warn=true`." + if !ignore_no_measured_warn && isempty(measured_quantities) + @warn "No measured quantity provided to the `measured_quantities` argument, any further identifiability analysis will likely fail. You can disable this warning by setting `ignore_no_measured_warn = true`." end # Appends the known parameters to the measured_quantities vector. Converts any Symbols to symbolics. @@ -220,9 +220,9 @@ end # Sorts the output according to their input order (defaults to the `[unknowns; parameters]` order). function make_output(out, funcs_to_check, conseqs) funcs_to_check = vector_subs(funcs_to_check, conseqs) - out = Dict(zip(vector_subs(keys(out), conseqs), values(out))) + out = OrderedDict(zip(vector_subs(keys(out), conseqs), values(out))) sortdict = Dict(ftc => i for (i, ftc) in enumerate(funcs_to_check)) - return sort(out; by = x -> sortdict[x]) + return sort!(out; by = x -> sortdict[x]) end # For a vector of expressions and a conservation law, substitutes the law into every equation. diff --git a/test/extensions/structural_identifiability.jl b/test/extensions/structural_identifiability.jl index 6b411cc9db..105d990d48 100644 --- a/test/extensions/structural_identifiability.jl +++ b/test/extensions/structural_identifiability.jl @@ -178,13 +178,13 @@ let end @unpack M, E, P, pₑ, pₚ, pₘ = goodwind_oscillator_catalyst si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; known_p = [:pₑ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; known_p = [:pₑ], ignore_no_measured_warn = true) si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M], known_p = [:pₑ]) si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M, :E], known_p = [:pₑ]) si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M], known_p = [:pₑ, :pₚ]) si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [:M, :E], known_p = [:pₑ, :pₚ]) si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [M]) - si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; known_p = [pₑ]) + si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; known_p = [pₑ], ignore_no_measured_warn = true) si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [M], known_p = [pₑ]) si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [M, E], known_p = [pₑ]) si_catalyst_ode = make_si_ode(goodwind_oscillator_catalyst; measured_quantities = [M], known_p = [pₑ, pₚ]) @@ -196,7 +196,7 @@ let # Tests using model.component style (have to make system complete first). gw_osc_complt = complete(goodwind_oscillator_catalyst) @test make_si_ode(gw_osc_complt; measured_quantities = [gw_osc_complt.M]) isa ODE - @test make_si_ode(gw_osc_complt; known_p = [gw_osc_complt.pₑ]) isa ODE + @test make_si_ode(gw_osc_complt; known_p = [gw_osc_complt.pₑ], ignore_no_measured_warn = true) isa ODE @test make_si_ode(gw_osc_complt; measured_quantities = [gw_osc_complt.M], known_p = [gw_osc_complt.pₑ]) isa ODE @test make_si_ode(gw_osc_complt; measured_quantities = [gw_osc_complt.M, gw_osc_complt.E], known_p = [gw_osc_complt.pₑ]) isa ODE @test make_si_ode(gw_osc_complt; measured_quantities = [gw_osc_complt.M], known_p = [gw_osc_complt.pₑ, gw_osc_complt.pₚ]) isa ODE @@ -288,7 +288,7 @@ let b, A0 --> 2A2 c, A0 --> A1 + A2 end - id_report = assess_identifiability(rs, measured_quantities = [:A0, :A1, :A2]) + id_report = assess_identifiability(rs; measured_quantities = [:A0, :A1, :A2], loglevel) @test sym_dict(id_report) == Dict( :A0 => :globally, :A1 => :globally, @@ -358,6 +358,6 @@ let # Computes bifurcation diagram. @test_throws Exception assess_identifiability(incomplete_network; measured_quantities, loglevel) - @test_throws Exception assess_local_identifiability(incomplete_network; measured_quantities, loglevels) + @test_throws Exception assess_local_identifiability(incomplete_network; measured_quantities, loglevel) @test_throws Exception find_identifiable_functions(incomplete_network; measured_quantities, loglevel) end \ No newline at end of file From 93c94c9418d3d7dd78a9a24d7503a12686049a0c Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 18:33:40 -0400 Subject: [PATCH 233/446] init --- src/registered_functions.jl | 6 +++--- test/reactionsystem_core/custom_crn_functions.jl | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/registered_functions.jl b/src/registered_functions.jl index 4a0c826618..07b8ee6299 100644 --- a/src/registered_functions.jl +++ b/src/registered_functions.jl @@ -116,7 +116,7 @@ expand_registered_functions(expr) Takes an expression, and expands registered function expressions. E.g. `mm(X,v,K)` is replaced with v*X/(X+K). Currently supported functions: `mm`, `mmr`, `hill`, `hillr`, and `hill`. """ -function expand_registered_functions(expr) +function expand_registered_functions!(expr) iscall(expr) || return expr args = arguments(expr) if operation(expr) == Catalyst.mm @@ -132,13 +132,13 @@ function expand_registered_functions(expr) ((args[1])^args[5] + (args[2])^args[5] + (args[4])^args[5]) end for i in 1:length(args) - args[i] = expand_registered_functions(args[i]) + args[i] = expand_registered_functions!(args[i]) end return expr end # If applied to a Reaction, return a reaction with its rate modified. function expand_registered_functions(rx::Reaction) - Reaction(expand_registered_functions(rx.rate), rx.substrates, rx.products, + Reaction(expand_registered_functions!(deepcopy(rx.rate)), rx.substrates, rx.products, rx.substoich, rx.prodstoich, rx.netstoich, rx.only_use_rate, rx.metadata) end # If applied to a Equation, returns it with it applied to lhs and rhs diff --git a/test/reactionsystem_core/custom_crn_functions.jl b/test/reactionsystem_core/custom_crn_functions.jl index 5d1070d65a..0df89e9ca4 100644 --- a/test/reactionsystem_core/custom_crn_functions.jl +++ b/test/reactionsystem_core/custom_crn_functions.jl @@ -154,4 +154,18 @@ let @test isequal(Catalyst.expand_registered_functions(eq3), 0 ~ V * (X^N) / (X^N + K^N)) @test isequal(Catalyst.expand_registered_functions(eq4), 0 ~ V * (K^N) / (X^N + K^N)) @test isequal(Catalyst.expand_registered_functions(eq5), 0 ~ V * (X^N) / (X^N + Y^N + K^N)) +end + +# Ensures that original system is not modified. +let + # Create model with a registered function. + @species X(t) + @parameters v K + rxs = [Reaction(mm(X,v,K), [], [X])] + @named rs = ReactionSystem(rxs, t) + + # Check that `expand_registered_functions` does not mutate original model. + rs_expanded_funcs = Catalyst.expand_registered_functions(rs) + @test isequal(only(Catalyst.get_rxs(rs)).rate, Catalyst.mm(X,v,K)) + @test isequal(only(Catalyst.get_rxs(rs_expanded_funcs)).rate, v*X/(X + K)) end \ No newline at end of file From 367ac4e5d3b3ef8b5e29d6b15dbc344e0f485780 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 18:41:58 -0400 Subject: [PATCH 234/446] handle equations in reaction system as well --- src/registered_functions.jl | 2 +- test/reactionsystem_core/custom_crn_functions.jl | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/registered_functions.jl b/src/registered_functions.jl index 07b8ee6299..af7b227432 100644 --- a/src/registered_functions.jl +++ b/src/registered_functions.jl @@ -143,7 +143,7 @@ function expand_registered_functions(rx::Reaction) end # If applied to a Equation, returns it with it applied to lhs and rhs function expand_registered_functions(eq::Equation) - return expand_registered_functions(eq.lhs) ~ expand_registered_functions(eq.rhs) + return expand_registered_functions!(deepcopy(eq.lhs)) ~ expand_registered_functions!(deepcopy(eq.rhs)) end # If applied to a ReactionSystem, applied function to all Reactions and other Equations, and return updated system. function expand_registered_functions(rs::ReactionSystem) diff --git a/test/reactionsystem_core/custom_crn_functions.jl b/test/reactionsystem_core/custom_crn_functions.jl index 0df89e9ca4..6a62e6bb99 100644 --- a/test/reactionsystem_core/custom_crn_functions.jl +++ b/test/reactionsystem_core/custom_crn_functions.jl @@ -160,12 +160,18 @@ end let # Create model with a registered function. @species X(t) + @variables V(t) @parameters v K - rxs = [Reaction(mm(X,v,K), [], [X])] - @named rs = ReactionSystem(rxs, t) + eqs = [ + Reaction(mm(X,v,K), [], [X]), + mm(V,v,K) ~ V + 1 + ] + @named rs = ReactionSystem(eqs, t) # Check that `expand_registered_functions` does not mutate original model. rs_expanded_funcs = Catalyst.expand_registered_functions(rs) @test isequal(only(Catalyst.get_rxs(rs)).rate, Catalyst.mm(X,v,K)) @test isequal(only(Catalyst.get_rxs(rs_expanded_funcs)).rate, v*X/(X + K)) + @test isequal(last(Catalyst.get_eqs(rs)).lhs, Catalyst.mm(V,v,K)) + @test isequal(last(Catalyst.get_eqs(rs_expanded_funcs)).lhs, v*V/(V + K)) end \ No newline at end of file From 59294a599a66e721d8a1fc92c18b1bb4380dc3da Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sat, 8 Jun 2024 18:43:01 -0400 Subject: [PATCH 235/446] Fix merge error --- test/runtests.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 94ee22826b..ca6c8a918f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -64,9 +64,9 @@ using SafeTestsets, Test @time @safetestset "Graphs Visualisations" begin include("visualisation/graphs.jl") end end - Tests extensions. + # Tests extensions. @time @safetestset "BifurcationKit Extension" begin include("extensions/bifurcation_kit.jl") end @time @safetestset "HomotopyContinuation Extension" begin include("extensions/homotopy_continuation.jl") end @time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end -end # @time \ No newline at end of file +end # @time From deaf660997eaf01b14c1ffc75dda0474e32aef77 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 20:59:21 -0400 Subject: [PATCH 236/446] init --- test/dsl/dsl_options.jl | 4 +- .../coupled_equation_crn_systems.jl | 16 +++-- test/reactionsystem_core/events.jl | 11 ++-- .../parameter_type_designation.jl | 12 +--- test/runtests.jl | 1 + test/simulation_and_solving/simulate_SDEs.jl | 7 ++- .../lattice_reaction_systems.jl | 1 + test/upstream/mtk_problem_inputs.jl | 18 ++++-- test/upstream/mtk_structure_indexing.jl | 61 +++++++++++++------ 9 files changed, 78 insertions(+), 53 deletions(-) diff --git a/test/dsl/dsl_options.jl b/test/dsl/dsl_options.jl index 93ce90b541..84acb94f0b 100644 --- a/test/dsl/dsl_options.jl +++ b/test/dsl/dsl_options.jl @@ -488,12 +488,12 @@ let @test sol[:Y][end] ≈ 3.0 # Tests that observables can be used for plot indexing. - @test_broken false # plot(sol; idxs=X).series_list[1].plotattributes[:y][end] ≈ 10.0 + plot(sol; idxs=X).series_list[1].plotattributes[:y][end] ≈ 10.0 @test plot(sol; idxs=rn.X).series_list[1].plotattributes[:y][end] ≈ 10.0 @test plot(sol; idxs=:X).series_list[1].plotattributes[:y][end] ≈ 10.0 @test plot(sol; idxs=[X, Y]).series_list[2].plotattributes[:y][end] ≈ 3.0 @test plot(sol; idxs=[rn.X, rn.Y]).series_list[2].plotattributes[:y][end] ≈ 3.0 - @test_broken false # plot(sol; idxs=[:X, :Y]).series_list[2].plotattributes[:y][end] ≈ 3.0 + @test_broken plot(sol; idxs=[:X, :Y]).series_list[2].plotattributes[:y][end] ≈ 3.0 # (https://github.com/SciML/ModelingToolkit.jl/issues/2778) end # Compares programmatic and DSL system with observables. diff --git a/test/reactionsystem_core/coupled_equation_crn_systems.jl b/test/reactionsystem_core/coupled_equation_crn_systems.jl index 33db205dd9..fc662d9c59 100644 --- a/test/reactionsystem_core/coupled_equation_crn_systems.jl +++ b/test/reactionsystem_core/coupled_equation_crn_systems.jl @@ -293,7 +293,7 @@ end # Checks for both differential and algebraic equations. # Checks for problems, integrators, and solutions yielded by coupled systems. # Checks that metadata, types, and default values are carried through correctly. -@test_broken let # SDEs are currently broken with structural simplify. +@test_broken let # SDEs are currently broken with structural simplify (https://github.com/SciML/ModelingToolkit.jl/issues/2614). # Creates the model @parameters a1 [description="Parameter a1"] a2::Rational{Int64} a3=0.3 a4::Rational{Int64}=4//10 [description="Parameter a4"] @parameters b1 [description="Parameter b1"] b2::Int64 b3 = 3 b4::Int64=4 [description="Parameter b4"] @@ -558,14 +558,12 @@ let # Checks that SteadyState simulation of the system achieves the correct steady state. # Currently broken due to MTK. - @test_broken begin - ssprob = SteadyStateProblem(coupled_rs, u0, ps; structural_simplify = true) - sssol = solve(oprob, DynamicSS(Vern7()); abstol = 1e-8, reltol = 1e-8) - @test osol[X][end] ≈ 2.0 - @test osol[A][end] ≈ 0.0 atol = 1e-8 - @test osol[D(A)][end] ≈ 0.0 atol = 1e-8 - @test osol[B][end] ≈ 1.0 - end + ssprob = SteadyStateProblem(coupled_rs, u0, ps; structural_simplify = true) + sssol = solve(ssprob, DynamicSS(Vern7()); abstol = 1e-8, reltol = 1e-8) + @test sssol[X][end] ≈ 2.0 + @test sssol[A][end] ≈ 0.0 atol = 1e-8 + @test sssol[D(A)][end] ≈ 0.0 atol = 1e-8 + @test sssol[B][end] ≈ 1.0 # Checks that the steady state can be found by solving a nonlinear problem. # Here `B => 0.1` has to be provided as well (and it shouldn't for the 2nd order ODE), hence the diff --git a/test/reactionsystem_core/events.jl b/test/reactionsystem_core/events.jl index ab8488eaf7..4b12c5b39d 100644 --- a/test/reactionsystem_core/events.jl +++ b/test/reactionsystem_core/events.jl @@ -362,9 +362,9 @@ let sol = solve(jprob, SSAStepper(); seed) # Checks that all `e` parameters have been updated properly. - # Note that periodic discrete events are currently broken for jump processes. + # Note that periodic discrete events are currently broken for jump processes (and unlikely to be fixed soon due to have events are implemented). @test sol.ps[:e1] == 1 - @test_broken sol.ps[:e2] == 1 + @test_broken sol.ps[:e2] == 1 # (https://github.com/SciML/JumpProcesses.jl/issues/417) @test sol.ps[:e3] == 1 end @@ -424,21 +424,22 @@ let osol_events = solve(oprob_events, Tsit5()) @test osol == osol_events - # Checks for SDE simulations. + # Checks for SDE simulations (note, non-seed dependant test should be created instead). sprob = SDEProblem(rn, u0, tspan, ps) sprob_events = SDEProblem(rn_events, u0, tspan, ps) ssol = solve(sprob, ImplicitEM(); seed, callback) ssol_events = solve(sprob_events, ImplicitEM(); seed) @test ssol == ssol_events - # Checks for Jump simulations. + # Checks for Jump simulations. (note, non-seed dependant test should be created instead) + # Note that periodic discrete events are currently broken for jump processes (and unlikely to be fixed soon due to have events are implemented). callback = CallbackSet(cb_disc_1, cb_disc_2, cb_disc_3) dprob = DiscreteProblem(rn, u0, tspan, ps) dprob_events = DiscreteProblem(rn_dics_events, u0, tspan, ps) jprob = JumpProblem(rn, dprob, Direct(); rng) jprob_events = JumpProblem(rn_dics_events, dprob_events, Direct(); rng) sol = solve(jprob, SSAStepper(); seed, callback) - @test_broken let # Broken due to. Even if fixed, seeding might not work due to events. + @test_broken let # (https://github.com/SciML/JumpProcesses.jl/issues/417) sol_events = solve(jprob_events, SSAStepper(); seed) @test sol == sol_events end diff --git a/test/reactionsystem_core/parameter_type_designation.jl b/test/reactionsystem_core/parameter_type_designation.jl index 204d0f5095..8405c3e384 100644 --- a/test/reactionsystem_core/parameter_type_designation.jl +++ b/test/reactionsystem_core/parameter_type_designation.jl @@ -88,7 +88,7 @@ let nsol = solve(nprob, NewtonRaphson()) # Checks all stored parameters. - for mtk_struct in [oprob, sprob, dprob, jprob, nprob, oinit, sinit, jinit, osol, ssol, jsol, nsol] + for mtk_struct in [oprob, sprob, dprob, jprob, nprob, oinit, sinit, jinit, ninit, osol, ssol, jsol, nsol] # Checks that all parameters have the correct type. @test unwrap(mtk_struct.ps[p1]) isa Float64 @test unwrap(mtk_struct.ps[d1]) isa Float64 @@ -114,8 +114,8 @@ let @test unwrap(mtk_struct.ps[d5]) == Float32(1.5) end - # Checks all stored variables. - for mtk_struct in [oprob, sprob, dprob, jprob, nprob, oinit, sinit, jinit] + # Checks all stored variables (these should always be `Float64`). + for mtk_struct in [oprob, sprob, dprob, jprob, nprob, oinit, sinit, jinit, ninit] # Checks that all variables have the correct type. @test unwrap(mtk_struct[X1]) isa Float64 @test unwrap(mtk_struct[X2]) isa Float64 @@ -130,10 +130,4 @@ let @test unwrap(mtk_struct[X4]) == 0.4 @test unwrap(mtk_struct[X5]) == 0.5 end - - # This test started working now, probably due to a MTK fix. Need to look at where to put it - # back into the test properly though. - @test_broken false - # Indexing currently broken for NonlinearSystem integrators (MTK intend to support this though). - @test unwrap(ninit.ps[p1]) isa Float64 end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index ca6c8a918f..82ed62060f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,6 +9,7 @@ using SafeTestsets, Test ### Run Tests ### @time begin + @time @safetestset "MTK Structure Indexing" begin include("upstream/mtk_structure_indexing.jl") end # Tests the `ReactionSystem` structure and its properties. @time @safetestset "Reaction Structure" begin include("reactionsystem_core/reaction.jl") end diff --git a/test/simulation_and_solving/simulate_SDEs.jl b/test/simulation_and_solving/simulate_SDEs.jl index d3504a3cd6..485cad85f8 100644 --- a/test/simulation_and_solving/simulate_SDEs.jl +++ b/test/simulation_and_solving/simulate_SDEs.jl @@ -224,13 +224,14 @@ let @species X1(t) X2(t) p_syms = @parameters $(η_stored) k1 k2 - r1 = Reaction(k1,[X1],[X2],[1],[1]; metadata = [:noise_scaling => η_stored]) - r2 = Reaction(k2,[X2],[X1],[1],[1]; metadata = [:noise_scaling => η_stored]) + r1 = Reaction(k1, [X1], [X2], [1], [1]; metadata = [:noise_scaling => p_syms[1]]) + r2 = Reaction(k2, [X2], [X1], [1], [1]; metadata = [:noise_scaling => p_syms[1]]) @named noise_scaling_network = ReactionSystem([r1, r2], t, [X1, X2], [k1, k2, p_syms[1]]) + noise_scaling_network = complete(noise_scaling_network) u0 = [:X1 => 1100.0, :X2 => 3900.0] p = [:k1 => 2.0, :k2 => 0.5, :η => 0.0] - @test_broken SDEProblem(noise_scaling_network, u0, (0.0, 1000.0), p).ps[:η] == 0.0 # Broken due to SII/MTK stuff. + @test SDEProblem(noise_scaling_network, u0, (0.0, 1000.0), p).ps[:η] == 0.0 end # Complicated test with many combinations of options. diff --git a/test/spatial_modelling/lattice_reaction_systems.jl b/test/spatial_modelling/lattice_reaction_systems.jl index a467baae61..d25c72694c 100644 --- a/test/spatial_modelling/lattice_reaction_systems.jl +++ b/test/spatial_modelling/lattice_reaction_systems.jl @@ -280,6 +280,7 @@ end # Currently not supported. Won't be until the LatticeReactionSystem internal update is merged. # Checks that parameter types designated in the non-spatial `ReactionSystem` is handled correctly. +# Broken lattice tests have local branches that fixes them. @test_broken let # Declares LatticeReactionSystem with designated parameter types. rs = @reaction_network begin diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index c1f86ca900..277b9cf52b 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -172,6 +172,11 @@ let @species X3(t) @parameters k3 + # Creates systems (so these are not recreated in each problem call). + osys = convert(ODESystem, rn) + ssys = convert(SDESystem, rn) + nsys = convert(NonlinearSystem, rn) + # Declares valid initial conditions and parameter values u0_valid = [X1 => 1, X2 => 2] ps_valid = [k1 => 0.5, k2 => 0.1] @@ -218,25 +223,26 @@ let ] # Loops through all potential parameter sets, checking their inputs yield errors. + # Broken tests are due to this issue: https://github.com/SciML/ModelingToolkit.jl/issues/2779 for ps in [ps_valid; ps_invalid], u0 in [u0_valid; u0s_invalid] # Handles problems with/without tspan separately. Special check ensuring that valid inputs passes. - for XProblem in [ODEProblem, SDEProblem, DiscreteProblem] + for (xsys, XProblem) in zip([osys, ssys, rn], [ODEProblem, SDEProblem, DiscreteProblem]) if (ps == ps_valid) && (u0 == u0_valid) - XProblem(rn, u0, (0.0, 1.0), ps); @test true; + XProblem(xsys, u0, (0.0, 1.0), ps); @test true; else # Several of these cases do not throw errors (https://github.com/SciML/ModelingToolkit.jl/issues/2624). @test_broken false continue - @test_throws Exception XProblem(rn, u0, (0.0, 1.0), ps) + @test_throws Exception XProblem(xsys, u0, (0.0, 1.0), ps) end end - for XProblem in [NonlinearProblem, SteadyStateProblem] + for (xsys, XProblem) in zip([nsys, osys], [NonlinearProblem, SteadyStateProblem]) if (ps == ps_valid) && (u0 == u0_valid) - XProblem(rn, u0, ps); @test true; + XProblem(xsys, u0, ps); @test true; else @test_broken false continue - @test_throws Exception XProblem(rn, u0, ps) + @test_throws Exception XProblem(xsys, u0, ps) end end end diff --git a/test/upstream/mtk_structure_indexing.jl b/test/upstream/mtk_structure_indexing.jl index 78b70eb279..46fd95e0b0 100644 --- a/test/upstream/mtk_structure_indexing.jl +++ b/test/upstream/mtk_structure_indexing.jl @@ -50,7 +50,7 @@ begin sint = init(sprob, ImplicitEM(); save_everystep=false) jint = init(jprob, SSAStepper()) nint = init(nprob, NewtonRaphson(); save_everystep=false) - @test_broken ssint = init(ssprob, DynamicSS(Tsit5()); save_everystep=false) # https://github.com/SciML/SciMLBase.jl/issues/660 + @test_broken ssint = init(ssprob, DynamicSS(Tsit5()); save_everystep=false) # https://github.com/SciML/SteadyStateDiffEq.jl/issues/79 integrators = [oint, sint, jint, nint] # Creates solutions. @@ -64,14 +64,12 @@ end # Tests problem indexing and updating. let - @test_broken false # A few cases fails for SteadyStateProblem: https://github.com/SciML/SciMLBase.jl/issues/660 - @test_broken false # Most cases broken for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 - for prob in deepcopy(problems[1:end-1]) + for prob in [deepcopy(problems); deepcopy(eproblems)] # Get u values (including observables). @test prob[X] == prob[model.X] == prob[:X] == 4 @test prob[XY] == prob[model.XY] == prob[:XY] == 9 @test prob[[XY,Y]] == prob[[model.XY,model.Y]] == prob[[:XY,:Y]] == [9, 5] - @test_broken prob[(XY,Y)] == prob[(model.XY,model.Y)] == prob[(:XY,:Y)] == (9, 5) + @test_broken prob[(XY,Y)] == prob[(model.XY,model.Y)] == prob[(:XY,:Y)] == (9, 5) # https://github.com/SciML/SciMLBase.jl/issues/709 @test getu(prob, X)(prob) == getu(prob, model.X)(prob) == getu(prob, :X)(prob) == 4 @test getu(prob, XY)(prob) == getu(prob, model.XY)(prob) == getu(prob, :XY)(prob) == 9 @test getu(prob, [XY,Y])(prob) == getu(prob, [model.XY,model.Y])(prob) == getu(prob, [:XY,:Y])(prob) == [9, 5] @@ -117,8 +115,7 @@ end # Test remake function. let - @test_broken false # Currently cannot be run for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 (as indexing cannot be used to check values). - for prob in deepcopy(problems) + for prob in [deepcopy(problems); deepcopy(eproblems)] # Remake for all u0s. rp = remake(prob; u0 = [X => 1, Y => 2]) @test rp[[X, Y]] == [1, 2] @@ -156,9 +153,7 @@ end # Test integrator indexing. let @test_broken false # NOTE: Multiple problems for `nint` (https://github.com/SciML/SciMLBase.jl/issues/662). - @test_broken false # NOTE: Multiple problems for `jint` (https://github.com/SciML/SciMLBase.jl/issues/654). - @test_broken false # NOTE: Cannot even create a `ssint` (https://github.com/SciML/SciMLBase.jl/issues/660). - for int in deepcopy([oint, sint]) + for int in deepcopy([oint, sint, jint]) # Get u values. @test int[X] == int[model.X] == int[:X] == 4 @test int[XY] == int[model.XY] == int[:XY] == 9 @@ -208,6 +203,7 @@ let end # Test solve's save_idxs argument. +# Currently, `save_idxs` is broken with symbolic stuff (https://github.com/SciML/ModelingToolkit.jl/issues/1761). let for (prob, solver) in zip(deepcopy([oprob, sprob, jprob]), [Tsit5(), ImplicitEM(), SSAStepper()]) # Save single variable @@ -241,10 +237,10 @@ let @test getu(sol, (XY,Y))(sol)[1] == getu(sol, (model.XY,model.Y))(sol)[1] == getu(sol, (:XY,:Y))(sol)[1] == (9, 5) # Get u values via idxs and functional call. - @test osol(0.0; idxs=X) == osol(0.0; idxs=X) == osol(0.0; idxs=X) == 4 - @test osol(0.0; idxs=XY) == osol(0.0; idxs=XY) == osol(0.0; idxs=XY) == 9 - @test_broken osol(0.0; idxs=[model.Y,model.XY]) == osol(0.0; idxs=[model.Y,model.XY]) == osol(0.0; idxs=[model.XY,model.X]) == [9, 5] - @test_broken osol(0.0; idxs=(:Y,:XY)) == osol(0.0; idxs=(:Y,:XY)) == osol(0.0; idxs=(:XY,:Y)) == (9, 5) + @test osol(0.0; idxs=X) == osol(0.0; idxs=model.X) == osol(0.0; idxs=:X) == 4 + @test osol(0.0; idxs=XY) == osol(0.0; idxs=model.XY) == osol(0.0; idxs=:XY) == 9 + @test osol(0.0; idxs = [XY,Y]) == osol(0.0; idxs = [model.XY,model.Y]) == osol(0.0; idxs = [:XY,:Y]) == [9, 5] + @test_broken osol(0.0; idxs = (XY,Y)) == osol(0.0; idxs = (model.XY,model.Y)) == osol(0.0; idxs = (:XY,:Y)) == (9, 5) # https://github.com/SciML/SciMLBase.jl/issues/711 # Get p values. @test sol.ps[kp] == sol.ps[model.kp] == sol.ps[:kp] == 1.0 @@ -262,11 +258,11 @@ let @test sol[X] == sol[model.X] == sol[:X] @test sol[XY] == sol[model.XY][1] == sol[:XY] @test sol[[XY,Y]] == sol[[model.XY,model.Y]] == sol[[:XY,:Y]] - @test_broken sol[(XY,Y)] == sol[(model.XY,model.Y)] == sol[(:XY,:Y)] + @test_broken sol[(XY,Y)] == sol[(model.XY,model.Y)] == sol[(:XY,:Y)] # https://github.com/SciML/SciMLBase.jl/issues/710 @test getu(sol, X)(sol) == getu(sol, model.X)(sol)[1] == getu(sol, :X)(sol) @test getu(sol, XY)(sol) == getu(sol, model.XY)(sol)[1] == getu(sol, :XY)(sol) @test getu(sol, [XY,Y])(sol) == getu(sol, [model.XY,model.Y])(sol) == getu(sol, [:XY,:Y])(sol) - @test_broken getu(sol, (XY,Y))(sol) == getu(sol, (model.XY,model.Y))(sol) == getu(sol, (:XY,:Y))(sol)[1] + @test_broken getu(sol, (XY,Y))(sol) == getu(sol, (model.XY,model.Y))(sol) == getu(sol, (:XY,:Y))(sol)[1] # https://github.com/SciML/SciMLBase.jl/issues/710 # Get p values. @test sol.ps[kp] == sol.ps[model.kp] == sol.ps[:kp] @@ -281,8 +277,7 @@ end # Tests plotting. let - @test_broken false # Currently broken for `ssol` (https://github.com/SciML/SciMLBase.jl/issues/580) - for sol in deepcopy([osol, jsol]) + for sol in deepcopy([osol, ssol, jsol]) # Single variable. @test length(plot(sol; idxs = X).series_list) == 1 @test length(plot(sol; idxs = XY).series_list) == 1 @@ -386,4 +381,32 @@ let @test jint.cb.condition.ma_jumps.scaled_rates[1] == 16.0 reset_aggregated_jumps!(jint) @test jint.cb.condition.ma_jumps.scaled_rates[1] == 6.0 -end \ No newline at end of file +end + + + + +using ModelingToolkit, OrdinaryDiffEq +using ModelingToolkit: t_nounits as t, D_nounits as D + +@parameters p d +@variables X(t) +@variables Y(t) +eqs = [ + D(X) ~ p - d*X, + D(Y) ~ p - d*Y +] +@mtkbuild sys = ODESystem(eqs, t) + +u0 = [X => 1.0, Y => 1.0] +tspan = (0.0, 100.0) +ps = [p => 1.0, d => 0.1] + +prob = ODEProblem(sys, u0, tspan, ps) +sol = solve(prob) + +sol(0.0; idxs=[sys.Y,sys.XY]) +sol(0.0; idxs=[sys.Y,sys.XY]) + +@test_broken osol(0.0; idxs=[model.Y,model.XY]) == osol(0.0; idxs=[model.Y,model.XY]) == osol(0.0; idxs=[model.XY,model.X]) == [9, 5] +@test_broken osol(0.0; idxs=(:Y,:XY)) == osol(0.0; idxs=(:Y,:XY)) == osol(0.0; idxs=(:XY,:Y)) == (9, 5) \ No newline at end of file From 20be659dd0091555c804abff457675a553b66c86 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 21:03:14 -0400 Subject: [PATCH 237/446] up --- test/runtests.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 82ed62060f..ca6c8a918f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,7 +9,6 @@ using SafeTestsets, Test ### Run Tests ### @time begin - @time @safetestset "MTK Structure Indexing" begin include("upstream/mtk_structure_indexing.jl") end # Tests the `ReactionSystem` structure and its properties. @time @safetestset "Reaction Structure" begin include("reactionsystem_core/reaction.jl") end From d54844f0dabf1e965f5b812ac5a04368af5c7c2b Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 9 Jun 2024 03:29:46 +0200 Subject: [PATCH 238/446] Update mtk_structure_indexing.jl --- test/upstream/mtk_structure_indexing.jl | 27 +------------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/test/upstream/mtk_structure_indexing.jl b/test/upstream/mtk_structure_indexing.jl index 46fd95e0b0..d305a2098a 100644 --- a/test/upstream/mtk_structure_indexing.jl +++ b/test/upstream/mtk_structure_indexing.jl @@ -383,30 +383,5 @@ let @test jint.cb.condition.ma_jumps.scaled_rates[1] == 6.0 end - - - -using ModelingToolkit, OrdinaryDiffEq -using ModelingToolkit: t_nounits as t, D_nounits as D - -@parameters p d -@variables X(t) -@variables Y(t) -eqs = [ - D(X) ~ p - d*X, - D(Y) ~ p - d*Y -] -@mtkbuild sys = ODESystem(eqs, t) - -u0 = [X => 1.0, Y => 1.0] -tspan = (0.0, 100.0) -ps = [p => 1.0, d => 0.1] - -prob = ODEProblem(sys, u0, tspan, ps) -sol = solve(prob) - -sol(0.0; idxs=[sys.Y,sys.XY]) -sol(0.0; idxs=[sys.Y,sys.XY]) - @test_broken osol(0.0; idxs=[model.Y,model.XY]) == osol(0.0; idxs=[model.Y,model.XY]) == osol(0.0; idxs=[model.XY,model.X]) == [9, 5] -@test_broken osol(0.0; idxs=(:Y,:XY)) == osol(0.0; idxs=(:Y,:XY)) == osol(0.0; idxs=(:XY,:Y)) == (9, 5) \ No newline at end of file +@test_broken osol(0.0; idxs=(:Y,:XY)) == osol(0.0; idxs=(:Y,:XY)) == osol(0.0; idxs=(:XY,:Y)) == (9, 5) From e1b3a00f3eefce4ced86e2e4e86d3aa40a7b989f Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 9 Jun 2024 03:30:54 +0200 Subject: [PATCH 239/446] test fix --- test/upstream/mtk_structure_indexing.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/upstream/mtk_structure_indexing.jl b/test/upstream/mtk_structure_indexing.jl index d305a2098a..29e7a01755 100644 --- a/test/upstream/mtk_structure_indexing.jl +++ b/test/upstream/mtk_structure_indexing.jl @@ -383,5 +383,3 @@ let @test jint.cb.condition.ma_jumps.scaled_rates[1] == 6.0 end -@test_broken osol(0.0; idxs=[model.Y,model.XY]) == osol(0.0; idxs=[model.Y,model.XY]) == osol(0.0; idxs=[model.XY,model.X]) == [9, 5] -@test_broken osol(0.0; idxs=(:Y,:XY)) == osol(0.0; idxs=(:Y,:XY)) == osol(0.0; idxs=(:XY,:Y)) == (9, 5) From 36d2e178ea87c0fa8807e68da5e23e7783dd7d22 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 9 Jun 2024 11:00:19 -0400 Subject: [PATCH 240/446] up --- test/reactionsystem_core/events.jl | 2 +- test/reactionsystem_core/reactionsystem.jl | 4 ++-- test/upstream/mtk_problem_inputs.jl | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/reactionsystem_core/events.jl b/test/reactionsystem_core/events.jl index 4b12c5b39d..f9c0438463 100644 --- a/test/reactionsystem_core/events.jl +++ b/test/reactionsystem_core/events.jl @@ -158,7 +158,7 @@ let ] # Declares various misformatted events . - # Relevant MTK issue regarding misformatted events not throwing an early error https://github.com/SciML/ModelingToolkit.jl/issues/2612. + @test_broken false # Some missformatted tests should throw error at this stage, but does not (https://github.com/SciML/ModelingToolkit.jl/issues/2612). continuous_events_bad = [ X ~ 1.0 => [X ~ 0.5], # Scalar condition. [X ~ 1.0] => X ~ 0.5, # Scalar affect. diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index 6876026e0b..173c060d3c 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -235,7 +235,7 @@ let maj = MassActionJump(symmaj.param_mapper(pars), symmaj.reactant_stoch, symmaj.net_stoch, symmaj.param_mapper, scale_rates = false) for i in midxs - @test_broken abs(jumps[i].scaled_rates - maj.scaled_rates[i]) < 100 * eps() + @test_broken abs(jumps[i].scaled_rates - maj.scaled_rates[i]) < 100 * eps() # This (and next two) tests are broken due to the order in `maj` not being preserved. @test jumps[i].reactant_stoch == maj.reactant_stoch[i] @test jumps[i].net_stoch == maj.net_stoch[i] end @@ -747,5 +747,5 @@ end # there are several places in the code where the `reactionsystem_uptodate` function is called, here # the code might need adaptation to take the updated reaction system into account. let - @test_nowarn Catalyst.reactionsystem_uptodate_check() + @test_nowarn Catalyst.reactionsystem_uptodate_check() end \ No newline at end of file diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index 277b9cf52b..0e30f05631 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -230,7 +230,6 @@ let if (ps == ps_valid) && (u0 == u0_valid) XProblem(xsys, u0, (0.0, 1.0), ps); @test true; else - # Several of these cases do not throw errors (https://github.com/SciML/ModelingToolkit.jl/issues/2624). @test_broken false continue @test_throws Exception XProblem(xsys, u0, (0.0, 1.0), ps) From 23763d6b95257269050639fab44d692ed40e95c8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 9 Jun 2024 11:57:00 -0400 Subject: [PATCH 241/446] init --- .../serialisation_support.jl | 2 ++ .../serialise_fields.jl | 23 ++++++++++++++++- .../serialise_reactionsystem.jl | 12 +++++++-- test/failed_serialisation.jl | 0 .../reactionsystem_serialisation.jl | 25 +++++++++++++++++++ 5 files changed, 59 insertions(+), 3 deletions(-) delete mode 100644 test/failed_serialisation.jl diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 6d8b2152df..60b096bdfc 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -247,6 +247,8 @@ end function make_strip_call_dict(syms) return Dict([sym => strip_call(Symbolics.unwrap(sym)) for sym in syms]) end + +# If the input is a `ReactionSystem`, extracts the unknowns (i.e. syms depending on another variable). function make_strip_call_dict(rn::ReactionSystem) return make_strip_call_dict(get_unknowns(rn)) end diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index 939059fb5e..e4e64aff28 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -308,7 +308,7 @@ EQUATIONS_FS = (seri_has_equations, get_equations_string, get_equations_annotati # Checks if the reaction system has any observables. function seri_has_observed(rn::ReactionSystem) - return !isempty(observed(rn)) + return !isempty(get_observed(rn)) end # Extract a string which declares the system's observables. @@ -354,6 +354,27 @@ end # Combines the 3 -related functions in a constant tuple. OBSERVED_FS = (seri_has_observed, get_observed_string, get_observed_annotation) +### Handles Observables ### + +# Checks if the reaction system has any defaults. +function seri_has_defaults(rn::ReactionSystem) + return !isempty(get_defaults(rn)) +end + +# Extract a string which declares the system's defaults. +function get_defaults_string(rn::ReactionSystem) + defaults_string = "defaults = " * x_2_string(get_defaults(rn)) + return defaults_string +end + +# Creates an annotation for the system's defaults. +function get_defaults_annotation(rn::ReactionSystem) + return "Defaults:" +end + +# Combines the 3 defaults-related functions in a constant tuple. +DEFAULTS_FS = (seri_has_defaults, get_defaults_string, get_defaults_annotation) + ### Handles Continuous Events ### # Checks if the reaction system have has continuous events. diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index 7c647ef2a2..113667699b 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -34,7 +34,13 @@ Notes: """ function save_reactionsystem(filename::String, rn::ReactionSystem; annotate = true, safety_check = true) + # Error and warning checks. reactionsystem_uptodate_check() + if !isempty(get_networkproperties(rn)) + @warn "The serialised network has cached network properties (e.g. computed conservation laws). This will not be saved as part of the network, and must be recomputed when it is loaded." + end + + # Write model to file and performs a safety check. open(filename, "w") do file write(file, get_full_system_string(rn, annotate, true)) end @@ -65,6 +71,7 @@ function get_full_system_string(rn::ReactionSystem, annotate::Bool, top_level::B file_text, has_reactions = push_field(file_text, rn, annotate, top_level, REACTIONS_FS) file_text, has_equations = push_field(file_text, rn, annotate, top_level, EQUATIONS_FS) file_text, has_observed = push_field(file_text, rn, annotate, top_level, OBSERVED_FS) + file_text, has_defaults = push_field(file_text, rn, annotate, top_level, DEFAULTS_FS) file_text, has_continuous_events = push_field(file_text, rn, annotate, top_level, CONTINUOUS_EVENTS_FS) file_text, has_discrete_events = push_field(file_text, rn, annotate, @@ -78,7 +85,7 @@ function get_full_system_string(rn::ReactionSystem, annotate::Bool, top_level::B rs_creation_code = make_reaction_system_call( rn, annotate, top_level, has_sivs, has_species, has_variables, has_parameters, has_reactions, - has_equations, has_observed, has_continuous_events, + has_equations, has_observed, has_defaults, has_continuous_events, has_discrete_events, has_systems, has_connection_type) annotate || (@string_prepend! "\n" file_text) @string_prepend! "let" file_text @@ -92,7 +99,7 @@ end function make_reaction_system_call( rs::ReactionSystem, annotate, top_level, has_sivs, has_species, has_variables, has_parameters, has_reactions, has_equations, - has_observed, has_continuous_events, has_discrete_events, + has_observed, has_defaults, has_continuous_events, has_discrete_events, has_systems, has_connection_type) # Gets the independent variable input. @@ -141,6 +148,7 @@ function make_reaction_system_call( # Goes through various fields that might exists, and if so, adds them to the string. has_sivs && (@string_append! reaction_system_string ", spatial_ivs") has_observed && (@string_append! reaction_system_string ", observed") + has_defaults && (@string_append! reaction_system_string ", defaults") has_continuous_events && (@string_append! reaction_system_string ", continuous_events") has_discrete_events && (@string_append! reaction_system_string ", discrete_events") has_systems && (@string_append! reaction_system_string ", systems") diff --git a/test/failed_serialisation.jl b/test/failed_serialisation.jl deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index 46ec69863c..06684fdcf2 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -400,6 +400,30 @@ end ### Other Tests ### +# Checks that systems with cached network properties yields a warning. +# Checks that default values as saved properly (even if they have different types). +let + # Prepares model inputs. + @species X1(t) X2(t) + @parameters k1 k2::Int64 + rxs = [ + Reaction(k1, [X1], [X2]), + Reaction(k2, [X2], [X1]) + ] + defaults = Dict((X1 => 1.0, k2 => 2)) + + # Creates model and computes conservation laws. + @named rs = ReactionSystem(rxs, t; defaults) + conservationlaws(rs) + + # Serialises model and then loads and checks it. + @test_logs (:warn, ) match_mode=:any save_reactionsystem("serialised_rs.jl", rs) + rs_loaded = include("../serialised_rs.jl") + @test rs == rs_loaded + @test ModelingToolkit.get_defaults(rs) == ModelingToolkit.get_defaults(rs_loaded) + rm("serialised_rs.jl") +end + # Tests that an error is generated when non-`ReactionSystem` subs-systems are used. let @variables V(t) @@ -415,6 +439,7 @@ let @named osys = ODESystem([eq], t) @named rs = ReactionSystem(rxs, t; systems = [osys]) @test_throws Exception save_reactionsystem("failed_serialisation.jl", rs) + rm("failed_serialisation.jl") end # Checks that completeness is recorded correctly. From e575fb564b2a7e5ef22c6fdd25d401ac39cbff5e Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 9 Jun 2024 12:06:11 -0400 Subject: [PATCH 242/446] formating --- .../serialise_reactionsystem.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/reactionsystem_serialisation/serialise_reactionsystem.jl b/src/reactionsystem_serialisation/serialise_reactionsystem.jl index 113667699b..ced6d131e2 100644 --- a/src/reactionsystem_serialisation/serialise_reactionsystem.jl +++ b/src/reactionsystem_serialisation/serialise_reactionsystem.jl @@ -39,7 +39,7 @@ function save_reactionsystem(filename::String, rn::ReactionSystem; if !isempty(get_networkproperties(rn)) @warn "The serialised network has cached network properties (e.g. computed conservation laws). This will not be saved as part of the network, and must be recomputed when it is loaded." end - + # Write model to file and performs a safety check. open(filename, "w") do file write(file, get_full_system_string(rn, annotate, true)) @@ -96,11 +96,10 @@ end # Creates a ReactionSystem call for creating the model. Adds all the correct inputs to it. The input # `has_` `Bool`s described which inputs are used. If the model is `complete`, this is handled here. -function make_reaction_system_call( - rs::ReactionSystem, annotate, top_level, has_sivs, has_species, - has_variables, has_parameters, has_reactions, has_equations, - has_observed, has_defaults, has_continuous_events, has_discrete_events, - has_systems, has_connection_type) +function make_reaction_system_call(rs::ReactionSystem, annotate, top_level, has_sivs, + has_species, has_variables, has_parameters, has_reactions, has_equations, + has_observed, has_defaults, has_continuous_events, has_discrete_events, has_systems, + has_connection_type) # Gets the independent variable input. iv = x_2_string(get_iv(rs)) From 0068d75ba6bb23c50fbeea28bee34137c5d7577e Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 9 Jun 2024 13:09:08 -0400 Subject: [PATCH 243/446] up --- docs/src/steady_state_functionality/dynamical_systems.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/steady_state_functionality/dynamical_systems.md b/docs/src/steady_state_functionality/dynamical_systems.md index 2f3b1cd4c2..f6f1fb8ee6 100644 --- a/docs/src/steady_state_functionality/dynamical_systems.md +++ b/docs/src/steady_state_functionality/dynamical_systems.md @@ -144,7 +144,7 @@ If you use this functionality in your research, [in addition to Catalyst](@ref c --- ## Learning more -If you want to learn more about analysing dynamical systems, including chaotic behaviour, you can have a look at the textbook [Nonlinear Dynamics](https://link.springer.com/book/10.1007/978-3-030-91032-7). It utilizes DynamicalSystems.jl and provides a concise, hands-on approach to learning nonlinear dynamics and analysing dynamical systems [^1]. +If you want to learn more about analysing dynamical systems, including chaotic behaviour, you can have a look at the textbook [Nonlinear Dynamics](https://link.springer.com/book/10.1007/978-3-030-91032-7). It utilizes DynamicalSystems.jl and provides a concise, hands-on approach to learning nonlinear dynamics and analysing dynamical systems [^3]. --- From 5ded26168d9f342de623a1b994e2899233bd1c8e Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 9 Jun 2024 13:56:06 -0400 Subject: [PATCH 244/446] init --- test/visualisation/latexify.jl | 233 +++++++++++++++++---------------- 1 file changed, 117 insertions(+), 116 deletions(-) diff --git a/test/visualisation/latexify.jl b/test/visualisation/latexify.jl index e5f162e176..f83890a9dd 100644 --- a/test/visualisation/latexify.jl +++ b/test/visualisation/latexify.jl @@ -29,6 +29,8 @@ include("../test_networks.jl") ### Just be sure to remove all such macros before you commit a change since it ### will cause issues with Travis. +# Generally, for all latexify tests, the lines after `@test latexify(rn) == replace(` must +# start without any tabs, hence the somewhat weird formatting. ### Basic Tests ### @@ -48,68 +50,68 @@ let (d1,d2,d3,d4,d5,d6), (X1,X2,X3,X4,X5,X6) ⟶ ∅ end - # Latexify.@generate_test latexify(rn) - @test_broken latexify(rn; expand_functions = false) == replace( - raw"\begin{align*} - \varnothing &\xrightarrow{\frac{X4^{n1} v1^{2} K1^{n1}}{\left( K1^{n1} + X4^{n1} \right) \left( K1^{n1} + X2^{n1} \right)}} \mathrm{X1} \\ - \varnothing &\xrightarrow{\mathrm{hill}\left( X5, v2, K2, n2 \right)} \mathrm{X2} \\ - \varnothing &\xrightarrow{\mathrm{hill}\left( X3, v3, K3, n3 \right)} \mathrm{X3} \\ - \varnothing &\xrightarrow{\mathrm{hillr}\left( X1, v4, K4, n4 \right)} \mathrm{X4} \\ - \varnothing &\xrightarrow{\mathrm{hill}\left( X2, v5, K5, n5 \right)} \mathrm{X5} \\ - \varnothing &\xrightarrow{\mathrm{hillar}\left( X1, X6, v6, K6, n6 \right)} \mathrm{X6} \\ - \mathrm{X2} &\xrightleftharpoons[k2]{k1} \mathrm{X1} + 2 \mathrm{X4} \\ - \mathrm{X4} &\xrightleftharpoons[k4]{k3} \mathrm{X3} \\ - 3 \mathrm{X5} + \mathrm{X1} &\xrightleftharpoons[k6]{k5} \mathrm{X2} \\ - \mathrm{X1} &\xrightarrow{d1} \varnothing \\ - \mathrm{X2} &\xrightarrow{d2} \varnothing \\ - \mathrm{X3} &\xrightarrow{d3} \varnothing \\ - \mathrm{X4} &\xrightarrow{d4} \varnothing \\ - \mathrm{X5} &\xrightarrow{d5} \varnothing \\ - \mathrm{X6} &\xrightarrow{d6} \varnothing - \end{align*} - ", "\r\n"=>"\n") - - #Latexify.@generate_test latexify(rn; expand_functions=false) - @test_broken latexify(rn; expand_functions = false) == replace( - raw"\begin{align*} - \varnothing &\xrightarrow{\frac{X4^{n1} v1^{2} K1^{n1}}{\left( K1^{n1} + X4^{n1} \right) \left( K1^{n1} + X2^{n1} \right)}} \mathrm{X1} \\ - \varnothing &\xrightarrow{\mathrm{mm}\left( X5, v2, K2 \right)} \mathrm{X2} \\ - \varnothing &\xrightarrow{\mathrm{mmr}\left( X3, v3, K3 \right)} \mathrm{X3} \\ - \varnothing &\xrightarrow{\mathrm{hillr}\left( X1, v4, K4, n4 \right)} \mathrm{X4} \\ - \varnothing &\xrightarrow{\mathrm{hill}\left( X2, v5, K5, n5 \right)} \mathrm{X5} \\ - \varnothing &\xrightarrow{\mathrm{hillar}\left( X1, X6, v6, K6, n6 \right)} \mathrm{X6} \\ - \mathrm{X2} &\xrightleftharpoons[k2]{k1} \mathrm{X1} + 2 \mathrm{X4} \\ - \mathrm{X4} &\xrightleftharpoons[k4]{k3} \mathrm{X3} \\ - 3 \mathrm{X5} + \mathrm{X1} &\xrightleftharpoons[k6]{k5} \mathrm{X2} \\ - \mathrm{X1} &\xrightarrow{d1} \varnothing \\ - \mathrm{X2} &\xrightarrow{d2} \varnothing \\ - \mathrm{X3} &\xrightarrow{d3} \varnothing \\ - \mathrm{X4} &\xrightarrow{d4} \varnothing \\ - \mathrm{X5} &\xrightarrow{d5} \varnothing \\ - \mathrm{X6} &\xrightarrow{d6} \varnothing - \end{align*} - ", "\r\n"=>"\n") - - # Latexify.@generate_test latexify(rn, mathjax=false) - @test_broken latexify(rn, mathjax = false) == replace( - raw"\begin{align*} - \varnothing &\xrightarrow{\frac{X4^{n1} v1^{2} K1^{n1}}{\left( K1^{n1} + X4^{n1} \right) \left( K1^{n1} + X2^{n1} \right)}} \mathrm{X1} \\ - \varnothing &\xrightarrow{\frac{X5 v2}{K2 + X5}} \mathrm{X2} \\ - \varnothing &\xrightarrow{\frac{K3 v3}{K3 + X3}} \mathrm{X3} \\ - \varnothing &\xrightarrow{\frac{v4 K4^{n4}}{K4^{n4} + X1^{n4}}} \mathrm{X4} \\ - \varnothing &\xrightarrow{\frac{v5 X2^{n5}}{X2^{n5} + K5^{n5}}} \mathrm{X5} \\ - \varnothing &\xrightarrow{\frac{v6 X1^{n6}}{X6^{n6} + K6^{n6} + X1^{n6}}} \mathrm{X6} \\ - \mathrm{X2} &\xrightleftharpoons[k2]{k1} \mathrm{X1} + 2 \mathrm{X4} \\ - \mathrm{X4} &\xrightleftharpoons[k4]{k3} \mathrm{X3} \\ - 3 \mathrm{X5} + \mathrm{X1} &\xrightleftharpoons[k6]{k5} \mathrm{X2} \\ - \mathrm{X1} &\xrightarrow{d1} \varnothing \\ - \mathrm{X2} &\xrightarrow{d2} \varnothing \\ - \mathrm{X3} &\xrightarrow{d3} \varnothing \\ - \mathrm{X4} &\xrightarrow{d4} \varnothing \\ - \mathrm{X5} &\xrightarrow{d5} \varnothing \\ - \mathrm{X6} &\xrightarrow{d6} \varnothing - \end{align*} - ", "\r\n"=>"\n") + # Latexify.@generate_test latexify(rn; expand_functions = false) + @test latexify(rn; expand_functions = false) == replace( +raw"\begin{align*} +\varnothing &\xrightarrow{\mathrm{hillr}\left( X2, v1, K1, n1 \right) \mathrm{hill}\left( X4, v1, K1, n1 \right)} \mathrm{X1} \\ +\varnothing &\xrightarrow{\mathrm{hill}\left( X5, v2, K2, n2 \right)} \mathrm{X2} \\ +\varnothing &\xrightarrow{\mathrm{hill}\left( X3, v3, K3, n3 \right)} \mathrm{X3} \\ +\varnothing &\xrightarrow{\mathrm{hillr}\left( X1, v4, K4, n4 \right)} \mathrm{X4} \\ +\varnothing &\xrightarrow{\mathrm{hill}\left( X2, v5, K5, n5 \right)} \mathrm{X5} \\ +\varnothing &\xrightarrow{\mathrm{hillar}\left( X1, X6, v6, K6, n6 \right)} \mathrm{X6} \\ +\mathrm{X2} &\xrightleftharpoons[k2]{k1} \mathrm{X1} + 2 \mathrm{X4} \\ +\mathrm{X4} &\xrightleftharpoons[k4]{k3} \mathrm{X3} \\ +3 \mathrm{X5} + \mathrm{X1} &\xrightleftharpoons[k6]{k5} \mathrm{X2} \\ +\mathrm{X1} &\xrightarrow{d1} \varnothing \\ +\mathrm{X2} &\xrightarrow{d2} \varnothing \\ +\mathrm{X3} &\xrightarrow{d3} \varnothing \\ +\mathrm{X4} &\xrightarrow{d4} \varnothing \\ +\mathrm{X5} &\xrightarrow{d5} \varnothing \\ +\mathrm{X6} &\xrightarrow{d6} \varnothing + \end{align*} +", "\r\n"=>"\n") + + # Latexify.@generate_test latexify(rn; expand_functions = true) + @test latexify(rn; expand_functions = true) == replace( +raw"\begin{align*} +\varnothing &\xrightarrow{\frac{X4^{n1} v1^{2} K1^{n1}}{\left( K1^{n1} + X4^{n1} \right) \left( K1^{n1} + X2^{n1} \right)}} \mathrm{X1} \\ +\varnothing &\xrightarrow{\frac{v2 X5^{n2}}{X5^{n2} + K2^{n2}}} \mathrm{X2} \\ +\varnothing &\xrightarrow{\frac{v3 X3^{n3}}{X3^{n3} + K3^{n3}}} \mathrm{X3} \\ +\varnothing &\xrightarrow{\frac{v4 K4^{n4}}{K4^{n4} + X1^{n4}}} \mathrm{X4} \\ +\varnothing &\xrightarrow{\frac{v5 X2^{n5}}{X2^{n5} + K5^{n5}}} \mathrm{X5} \\ +\varnothing &\xrightarrow{\frac{v6 X1^{n6}}{X6^{n6} + K6^{n6} + X1^{n6}}} \mathrm{X6} \\ +\mathrm{X2} &\xrightleftharpoons[k2]{k1} \mathrm{X1} + 2 \mathrm{X4} \\ +\mathrm{X4} &\xrightleftharpoons[k4]{k3} \mathrm{X3} \\ +3 \mathrm{X5} + \mathrm{X1} &\xrightleftharpoons[k6]{k5} \mathrm{X2} \\ +\mathrm{X1} &\xrightarrow{d1} \varnothing \\ +\mathrm{X2} &\xrightarrow{d2} \varnothing \\ +\mathrm{X3} &\xrightarrow{d3} \varnothing \\ +\mathrm{X4} &\xrightarrow{d4} \varnothing \\ +\mathrm{X5} &\xrightarrow{d5} \varnothing \\ +\mathrm{X6} &\xrightarrow{d6} \varnothing + \end{align*} +", "\r\n"=>"\n") + + # Latexify.@generate_test latexify(rn, mathjax = false) + @test latexify(rn, mathjax = false) == replace( +raw"\begin{align*} +\varnothing &\xrightarrow{\frac{X4^{n1} v1^{2} K1^{n1}}{\left( K1^{n1} + X4^{n1} \right) \left( K1^{n1} + X2^{n1} \right)}} \mathrm{X1} \\ +\varnothing &\xrightarrow{\frac{v2 X5^{n2}}{X5^{n2} + K2^{n2}}} \mathrm{X2} \\ +\varnothing &\xrightarrow{\frac{v3 X3^{n3}}{X3^{n3} + K3^{n3}}} \mathrm{X3} \\ +\varnothing &\xrightarrow{\frac{v4 K4^{n4}}{K4^{n4} + X1^{n4}}} \mathrm{X4} \\ +\varnothing &\xrightarrow{\frac{v5 X2^{n5}}{X2^{n5} + K5^{n5}}} \mathrm{X5} \\ +\varnothing &\xrightarrow{\frac{v6 X1^{n6}}{X6^{n6} + K6^{n6} + X1^{n6}}} \mathrm{X6} \\ +\mathrm{X2} &\xrightleftharpoons[k2]{k1} \mathrm{X1} + 2 \mathrm{X4} \\ +\mathrm{X4} &\xrightleftharpoons[k4]{k3} \mathrm{X3} \\ +3 \mathrm{X5} + \mathrm{X1} &\xrightleftharpoons[k6]{k5} \mathrm{X2} \\ +\mathrm{X1} &\xrightarrow{d1} \varnothing \\ +\mathrm{X2} &\xrightarrow{d2} \varnothing \\ +\mathrm{X3} &\xrightarrow{d3} \varnothing \\ +\mathrm{X4} &\xrightarrow{d4} \varnothing \\ +\mathrm{X5} &\xrightarrow{d5} \varnothing \\ +\mathrm{X6} &\xrightarrow{d6} \varnothing + \end{align*} +", "\r\n"=>"\n") end # Tests basic functions on simple network (2). @@ -121,22 +123,22 @@ let end # Latexify.@generate_test latexify(rn) - @test_broken latexify(rn) == replace( - raw"\begin{align*} - \varnothing &\xrightleftharpoons[d_{a}]{\frac{p_{a} B^{n}}{k^{n} + B^{n}}} \mathrm{A} \\ - \varnothing &\xrightleftharpoons[d_{b}]{p_{b}} \mathrm{B} \\ - 3 \mathrm{B} &\xrightleftharpoons[r_{b}]{r_{a}} \mathrm{A} - \end{align*} - ", "\r\n"=>"\n") - - # Latexify.@generate_test latexify(rn, mathjax=false) - @test_broken latexify(rn, mathjax = false) == replace( - raw"\begin{align*} - \varnothing &\xrightleftharpoons[d_{a}]{\frac{p_{a} B^{n}}{k^{n} + B^{n}}} \mathrm{A} \\ - \varnothing &\xrightleftharpoons[d_{b}]{p_{b}} \mathrm{B} \\ - 3 \mathrm{B} &\xrightleftharpoons[r_{b}]{r_{a}} \mathrm{A} - \end{align*} - ", "\r\n"=>"\n") + @test latexify(rn) == replace( +raw"\begin{align*} +\varnothing &\xrightleftharpoons[d_{a}]{\frac{p_{a} B^{n}}{k^{n} + B^{n}}} \mathrm{A} \\ +\varnothing &\xrightleftharpoons[d_{b}]{p_{b}} \mathrm{B} \\ +3 \mathrm{B} &\xrightleftharpoons[r_{b}]{r_{a}} \mathrm{A} + \end{align*} +", "\r\n"=>"\n") + + # Latexify.@generate_test latexify(rn, mathjax = false) + @test latexify(rn, mathjax = false) == replace( +raw"\begin{align*} +\varnothing &\xrightleftharpoons[d_{a}]{\frac{p_{a} B^{n}}{k^{n} + B^{n}}} \mathrm{A} \\ +\varnothing &\xrightleftharpoons[d_{b}]{p_{b}} \mathrm{B} \\ +3 \mathrm{B} &\xrightleftharpoons[r_{b}]{r_{a}} \mathrm{A} + \end{align*} +", "\r\n"=>"\n") end # Tests for system with parametric stoichiometry. @@ -144,12 +146,26 @@ let rn = @reaction_network begin p, 0 --> (m + n)*X end - - @test_broken latexify(rn) == replace( - raw"\begin{align*} - \varnothing &\xrightarrow{p} (m + n)\mathrm{X} - \end{align*} - ", "\r\n"=>"\n") + + # Latexify.@generate_test latexify(rn) + @test latexify(rn) == replace( +raw"\begin{align*} +\varnothing &\xrightarrow{p} (m + n)\mathrm{X} + \end{align*} +", "\r\n"=>"\n") +end + +# Checks for systems with vector species/parameters. +# Technically tests would work, however, the display is non-ideal (https://github.com/SciML/Catalyst.jl/issues/932, https://github.com/JuliaSymbolics/Symbolics.jl/issues/1167). +let + rn = @reaction_network begin + @parameters k[1:2] x[1:2] [isconstantspecies=true] + @species (X(t))[1:2] (K(t))[1:2] + (k[1]*K[1],k[2]*K[2]), X[1] + x[1] <--> X[2] + x[2] + end + + # Latexify.@generate_test latexify(rn) + @test_broken false end ### Tests the `form` Option ### @@ -170,21 +186,15 @@ let end # Latexify.@generate_test latexify(rn; form=:ode) - @test_broken latexify(rn; form = :ode) == replace( - raw"$\begin{align} - \frac{\mathrm{d} X\left( t \right)}{\mathrm{d}t} =& p - \left( X\left( t \right) \right)^{2} kB - d X\left( t \right) + 2 kD \mathrm{X2}\left( t \right) \\ - \frac{\mathrm{d} \mathrm{X2}\left( t \right)}{\mathrm{d}t} =& \frac{1}{2} \left( X\left( t \right) \right)^{2} kB - kD \mathrm{X2}\left( t \right) - \end{align} - $", "\r\n"=>"\n") - - # Currently latexify doesn't handle SDE systems properly, and they look identical to ode systems. - # The "==" shoudl be a "!=", but due to latexify tests not working, for the broken test to work, I changed it. - @test_broken latexify(rn; form=:sde) == replace( - raw"$\begin{align} - \frac{\mathrm{d} X\left( t \right)}{\mathrm{d}t} =& p - \left( X\left( t \right) \right)^{2} kB - d X\left( t \right) + 2 kD \mathrm{X2}\left( t \right) \\ - \frac{\mathrm{d} \mathrm{X2}\left( t \right)}{\mathrm{d}t} =& \frac{1}{2} \left( X\left( t \right) \right)^{2} kB - kD \mathrm{X2}\left( t \right) - \end{align} - $", "\r\n"=>"\n") + @test latexify(rn; form = :ode) == replace( +raw"$\begin{align} +\frac{\mathrm{d} X\left( t \right)}{\mathrm{d}t} =& p - d X\left( t \right) + 2 kD \mathrm{X2}\left( t \right) - \left( X\left( t \right) \right)^{2} kB \\ +\frac{\mathrm{d} \mathrm{X2}\left( t \right)}{\mathrm{d}t} =& - kD \mathrm{X2}\left( t \right) + \frac{1}{2} \left( X\left( t \right) \right)^{2} kB +\end{align} +$", "\r\n"=>"\n") + + # Currently latexify doesn't handle SDE systems properly, and they look identical to ode systems (https://github.com/SciML/ModelingToolkit.jl/issues/2782). + @test_broken false # Tests that erroneous form gives error. @test_throws ErrorException latexify(rn; form=:xxx) @@ -229,14 +239,15 @@ let end # Latexify.@generate_test latexify(rn) - @test_broken latexify(rn) == replace( - raw"\begin{align*} - \varnothing &\xrightarrow{p} (m + n)\mathrm{X} - \end{align*} - ", "\r\n"=>"\n") + @test latexify(rn) == replace( +raw"\begin{align*} +\mathrm{Y} &\xrightarrow{Y k} \varnothing + \end{align*} +", "\r\n"=>"\n") end # Checks when combined with equations (nonlinear system). +# Technically tests would work, however, the display is non-ideal (https://github.com/SciML/Catalyst.jl/issues/927). let t = default_t() base_network = @reaction_network begin @@ -247,18 +258,8 @@ let extended = extend(decaying_rate, base_network) # Latexify.@generate_test latexify(extended) - @test_broken latexify(extended) == replace( - raw"\begin{align*} - \mathrm{X} &\xrightarrow{k r} \varnothing - 0 &= -1 - x\left( t \right) - \end{align*} - ", "\r\n"=>"\n") + @test_broken false # Latexify.@generate_test latexify(extended, mathjax=false) - @test_broken latexify(extended, mathjax = false) == replace( - raw"\begin{align*} - \mathrm{X} &\xrightarrow{k r} \varnothing - 0 &= -1 - x\left( t \right) - \end{align*} - ", "\r\n"=>"\n") + @test_broken false end From 4529be4d981f2e054c3cb7c2dd13079a223c7d77 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 9 Jun 2024 15:45:14 -0400 Subject: [PATCH 245/446] init --- README.md | 3 +- docs/make.jl | 1 - docs/src/v14_migration_guide.md | 188 ++++++++++++++++++ .../petab_ode_param_fitting.md | 0 4 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 docs/src/v14_migration_guide.md rename docs/{src/inverse_problems => unpublished}/petab_ode_param_fitting.md (100%) diff --git a/README.md b/README.md index 0f8d03e474..24f172a91c 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ etc). **NOTE:** version 14 is a breaking release, prompted by the release of ModelingToolkit.jl version 9. This caused several breaking changes in how Catalyst models are represented and interfaced with. Breaking changes and new functionality are summarized in the -[HISTORY.md](HISTORY.md) file. +[HISTORY.md](HISTORY.md) file. Furthermore, a migration guide on how to adapt your workflows to the +the new v14 update can be found [here](https://docs.sciml.ai/Catalyst/stable/v14_migration_guide/). ## Tutorials and documentation diff --git a/docs/make.jl b/docs/make.jl index 8595cd6ffa..af7e2ffd4a 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -40,7 +40,6 @@ makedocs(sitename = "Catalyst.jl", doctest = false, clean = true, pages = pages, - pagesonly = true, warnonly = [:missing_docs]) deploydocs(repo = "github.com/SciML/Catalyst.jl.git"; diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md new file mode 100644 index 0000000000..20f1c18be6 --- /dev/null +++ b/docs/src/v14_migration_guide.md @@ -0,0 +1,188 @@ +# Version 14 Migration Guide + +Catalyst is built on the [ModelingToolkit.jl](https://github.com/SciML/ModelingToolkit.jl) modelling language. A recent update of ModelingToolkit from version 8 to version 9 have required a corresponding update to Catalyst (from version 13 to 14). This update have introduced a couple of breaking changes, all of which will be detailed below. + +!!! note + Catalyst version 14 also introduces a large number of new features. These will not be discussed here, however, they are described in Catalyst's [history file]([@ref ](https://github.com/SciML/Catalyst.jl/blob/master/HISTORY.md)). + +## System completeness +In ModelingToolkit v19 (and thus also Catalyst v14) all systems (e.g. `ReactionSystem`s and `ODESystem`s) are either *complete* or *incomplete* (completeness was a thing previously, however, recent updates mean that the user now have to be aware of this). Complete and incomplete systems differ in that +- Only complete models can be used as inputs to simulations or certain tools for model analysis. +- Only incomplete models can be [composed with other models to form hierarchical models](@ref compositional_modeling). + +A model's completeness depends on how it was created: +- Models created programmatically (using the `ReactionSystem` constructor) are *incomplete*. +- Models created using the `@reaction_network` DSL are *complete*. +- To create *incomplete models using the DSL*, use the `@network_component` macro. +- Models generated through the `compose` and `extend` functions are *incomplete*. + +Furthermore, any systems generated through e.g. `convert(ODESystem, rs)` are also complete. + +Complete models can be generated from incomplete models through the `complete` function. Here is a workflow where we take completeness into account in the simulation of a simple birth-death process. +```@example v14_migration_1 +using Catalyst +t = default_t() +@species X(t) +@parameters p d +rxs = [ + Reaction(p, [], [X]), + Reaction(d, [X], []) +] +@named rs = ReactionSystem(rxs, t) +``` +Here we have created an incomplete model. If we think our model is ready (i.e. we do not wish to compose it with additional models) we mark it as complete: +```@example v14_migration_1 +rs = complete(rs) +``` +Here, `complete` does not change the input model, but simply creates a new complete model. We hence overwrite our model variable (`rs`) with `complete`'s output. We can confirm that our model is complete using the `iscomplete` function +```@example v14_migration_1 +Catalyst.iscomplete(rs) +``` +We can now go on and use our model for e.g. simulations: +```@example v14_migration_1 +using OrdinaryDiffEq, Plots +u0 = [X => 0.1] +tspan = (0.0, 10.0) +ps = [d => 1.0, d => 0.2] +oprob = ODEProblem(rs, u0, tspan, ps) +sol = solve(oprob) +plot(sol) +``` + +if we wish to first create a `ODESystem` from our `ReactionSystem`, the created `ODESystem` will be incomplete: +```@example v14_migration_1 +osys = convert(ODESystem, rs) +Catalyst.iscomplete(osys) +``` +(note that `rs` must be complete before it can be converted to a `ODESystem`) + +If we now wish to create an `ODEProblem` from our `ODESystem`, we must first mark it as complete (using similar syntax as for our `ReactionSystem`): +```@example v14_migration_1 +osys = complete(osys) +oprob = ODEProblem(osys, u0, tspan, ps) +sol = solve(oprob) +plot(sol) +``` + +## Unknowns instead of states +Previously, "states" was used as a term for a systems variables (both species and non-species variables). Now, the term "unknowns" is preferred instead. This means that there have been a number of changes to function names (e.g. `states` => `unknowns`, `get_states` => `get_unknowns`). + +E.g. here we declare a `ReactionSystem` model containing both species and non-species unknowns: +```@example v14_migration_2 +using Catalyst +t = default_t() +D = default_time_deriv() +@species X(t) +@variables V(t) +@parameters p d Vmax + +eqs = [ + Reaction(p, [], [X]), + Reaction(d, [X], []), + D(V) ~ Vmax - V*X*d/p +] +@named rs = ReactionSystem(eqs, t) +``` +We can now use `unknowns` to retrieve all unknowns +```@example v14_migration_2 +unknowns(rs) +``` +Meanwhile, `species` and `nonspecies` (like previously) returns all species or non-species unknowns, respectively: +```@example v14_migration_2 +species(rs) +``` +```@example v14_migration_2 +nonspecies(rs) +``` + +## Lost support for most units +As part of its v9 update, ModelingToolkit changed how units was handled. This includes using the package [DynamicQuantities.jl](https://github.com/SymbolicML/DynamicQuantities.jl) to manage units (instead of [Uniful.jl](https://github.com/PainterQubits/Unitful.jl), like previously). + +While this should lead to long-term improvements, unfortunately, as part of the process support for most units where removed. Currently, only the main SI units are supported (`s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`). Composite units (e.g. `N = kg/(m^2)`) are no longer supported. Furthermore, prefix units (e.g. `mm = m/1000`) are not supported either. This means that most units relevant to Catalyst (such as `µM`) cannot be used directly. While composite units can still be written out in full and used (e.f. `mol/(m^3)`) this is hardly user friendly. + +The maintainers of ModelingTOolkit have been notified of this issue. We are unsure when this will be fixed, however, we do not think it will be a permanent change. + +## Removed support for system-mutating functions +According to ModelingToolkit policy, created systems should not be modified. In accordance with this, the following functions have been deprecated: `addparam!`, `addreaction!`, `addspecies!`, `@add_reactions`, and `merge!`. Please use `ModelingToolkit.extend` and `ModelingToolkit.compose` to generate new merged and/or composed `ReactionSystems` from multiple component systems. + +It is still possible to add default values to a created `ReactionSystem`, i.e. the `setdefaults!` function is still supported. + +## New interface for creating time variable (`t`) and its differential (`D`) + +Previously, the time independent variable (typically called `t`) was declared using +```@example v14_migration_3 +using Catalyst +@variables t +nothing # hide +``` +A new, preferred, interface have now been introduced: +```@example v14_migration_3 +t = default_t() +nothing # hide +``` + +Similarly, the time differential (primarily relevant when creating combined reaction-equation models) used to be declared through +```@example v14_migration_3 +D = Differential(t) +nothing # hide +``` +where the preferred method now being +```@example v14_migration_3 +Dt = default_time_deriv() +nothing # hide +``` + +## New interface for accessing problem/integrator/solution parameter (and species) value + +Previously, it was possible to index problems to query them for their parameter values. e.g. +```@example v14_migration_4 +using Catalyst +rn = @reaction_network begin + (p,d), 0 <--> X +end +oprob = ODEProblem(rn, [:X => 1.0], (0.0, 1.0), [:p => 1.0, :d => 0.2]) +nothing # hide +```julia +oprob[:p] +``` +This *is no longer supported*. When you wish to query a problem (or integrator or solution) for a parameter value (or to update a parameter value), you must append `.ps` to the problem: +```@example v14_migration_4 +oprob.ps[:p] +``` + +Furthermore, a few new functions (`getp`, `getu`, `setp`, `setu`) have been introduced. These can improve performance when querying a structure for a value multiple times (especially for very large models). These are describe in more detail [here](@ref simulation_structure_interfacing_functions). + +For more details on how to query various structures for parameter and species values, please read [this documentation page](@ref simulation_structure_interfacing). + +## Other notes +Finally, here are some additional, minor, notes regarding the new update. + +### Modification of problems with conservation laws broken +While it is possible to update e.g. `ODEProblem`s using the [`remake`](@ref simulation_structure_interfacing_problems_remake) function, this is not possible if the `remove_conserved = true` option was used. E.g. while +```@example v14_migration_5 +using Catalyst, OrdinaryDiffEq +rn = @reaction_network begin + (k1,k2), X1 <--> X2 +end +u0 = [:X1 => 1.0, :X2 => 2.0] +ps = [:k1 => 0.5, :k2 => 3.0] +oprob = ODEProblem(rn, u0, (0.0, 10.0), ps; remove_conserved = true) +sol(oprob) +# hide +``` +is perfectly fine, attempting to then modify `oprob` (in any manner!) is not possible (without introducing a bug): +```@example v14_migration_5 +oprob_remade = remake(oprob; u0 = [:X1 => 5.0]) # NEVER do this. +sol(oprob) +# hide +``` + +This bug was likely present on earlier versions as well, but was only recently discovered. While we hope it will be fixed soon, the bug is in ModelingToolkit, and will not be fixed until its maintainers find the time to do so. + +### Depending on parameter order even more dangerous than before +In early versions of Catalyst, parameters and species were provided as vectors (e.g. `[1.0, 2.0]`) rather than maps (e.g. `[p => 1.0, d => 2.0]`). While it has been *strongly* recommended to use the map form for a while, the vector form is still (technically) possible (however, we would not guarantee that this would produce expected behaviours). However, due to recent internal ModelingToolkit updates, the risk of unexpected behaviour when providing parameter values as vector is even larger than before. + +*Users should never use vector-forms to represent parameter and species values* + +### Additional deprecated functions +The `reactionparams`, `numreactionparams`, and `reactionparamsmap` functions have been deprecated. \ No newline at end of file diff --git a/docs/src/inverse_problems/petab_ode_param_fitting.md b/docs/unpublished/petab_ode_param_fitting.md similarity index 100% rename from docs/src/inverse_problems/petab_ode_param_fitting.md rename to docs/unpublished/petab_ode_param_fitting.md From 332fb318cccd1ea4cb4aff32e85ab243bb27daeb Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 9 Jun 2024 16:40:21 -0400 Subject: [PATCH 246/446] update history file --- HISTORY.md | 132 +++++++++++++++++++------------- docs/make.jl | 1 + docs/src/v14_migration_guide.md | 67 ++++++++-------- 3 files changed, 114 insertions(+), 86 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 270d64b177..d1558c122d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,8 +3,82 @@ ## Catalyst unreleased (master branch) ## Catalyst 14.0 -- The `reactionparams`, `numreactionparams`, and `reactionparamsmap` functions have been removed. + +#### Breaking changes +Catalyst v14 was prompted by the release of ModelingToolkit v9. This introduced several breaking changes to the package. A summary of these (and how to handle them) can be found [here](https://docs.sciml.ai/Catalyst/stable/v14_migration_guide/). These are briefly summarised in the following bullet points: +- `ReactionSystem`s must now be *complete* before they are exposed to most forms of simulation and analysis. With the exception of `ReactionSystem`s created through the `@reaction_network` macro, all `ReactionSystem`s are created incomplete. The `complete` function can be used to generate complete `ReactionSystem`s from incomplete ones. +- The `states` function has been replaced with `unknowns`. The `get_states` function has been replaced with `get_unknowns`. +- Support for most units (with the exception of `s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`) has been dropped until further notice. +- Problem parameter values are now accessed through `prob.ps[p]` (rather than `prob[p]`). +- A significant bug prevents the safe application of the `remake` function on problems for which `remove_conserved = true` was used. +- The `reactionparams`, `numreactionparams`, and `reactionparamsmap` functions have been removed. - To be more consistent with ModelingToolkit's immutability requirement for systems, we have removed API functions that mutate `ReactionSystem`s such as `addparam!`, `addreaction!`, `addspecies`, `@add_reactions`, and `merge!`. Please use `ModelingToolkit.extend` and `ModelingToolkit.compose` to generate new merged and/or composed `ReactionSystem`s from multiple component systems. + +#### General changes +- The `default_t()` and `default_time_deriv()` functions are now the preferred approaches for creating the default time independent variable and its differential. +- It is now possible to add metadata to individual reactions, e.g. using: +```julia +rn = @reaction_network begin + @parameters η + k, 2X --> X2, [description="Dimerisation"] +end +getdescription(rn) +``` +a more detailed description can be found [here](https://docs.sciml.ai/Catalyst/dev/model_creation/dsl_advanced/#dsl_advanced_options_reaction_metadata). +- `SDEProblem` no longer takes the `noise_scaling` argument. Noise scaling is now handled through the `noise_scaling` metadata (described in more detail [here](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_introduction/#simulation_intro_SDEs_noise_saling)) +- Fields of the internal `Reaction` structure have been changed. `ReactionSystems`s saved using `serialize` on previous Catalyst versions cannot be loaded using this (or later) versions. +- A new function, `save_reactionsystem`, which permits the writing of `ReactionSystem` models to files, has been created. A thorough description of this function can be found [here](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#Saving-Catalyst-models-to,-and-loading-them-from,-Julia-files) +- Update how compounds are created. E.g. use +```julia +@variables t C(t) O(t) +@compound CO2 ~ C + 2O +``` +to create a compound species `CO2` that consists of `C` and 2 `O`. +- Added documentation for chemistry-related functionality (compound creation and reaction balancing). +- Added function `isautonomous` to check if a `ReactionSystem` is autonomous. +- Added function `steady_state_stability` to compute stability for steady states. Example: +```julia +# Creates model. +rn = @reaction_network begin + (p,d), 0 <--> X +end +p = [:p => 1.0, :d => 0.5] + +# Finds (the trivial) steady state, and computes stability. +steady_state = [2.0] +steady_state_stability(steady_state, rn, p) +``` +Here, `steady_state_stability` takes an optional argument `tol = 10*sqrt(eps())`, which is used to determine whether a eigenvalue real part is reliably less than 0. +- Added a DSL option, `@combinatoric_ratelaws`, which can be used to toggle whether to use combinatorial rate laws within the DSL. Example: +```julia +# Creates model. +rn = @reaction_network begin + @combinatoric_ratelaws false + (kB,kD), 2X <--> X2 +end +``` +- Added a DSL option, `@observables` for [creating observables](https://docs.sciml.ai/Catalyst/stable/model_creation/dsl_advanced/#dsl_advanced_options_observables) (this feature was already supported for programmatic modelling). +- Added DSL options `@continuous_events` and `@discrete_events` to add events to a model as part of its creation (this feature was already supported for programmatic modelling). Example: +```julia +rn = @reaction_network begin + @continuous_events begin + [X ~ 1.0] => [X ~ X + 1.0] + end + d, X --> 0 +end +``` +- Added DSL option `@equations` to add (algebraic or differential) equations to a model as part of its creation (this feature was already supported for programmatic modelling). Example: +```julia +rn = @reaction_network begin + @equations begin + D(V) ~ 1 - V + end + (p/V,d/V), 0 <--> X +end +``` +- Coupled reaction network + differential equation (or algebraic differential equation) systems can now be converted to `SDESystem`s and `NonlinearSystem`s. + +#### Structural identifiability extension - Added CatalystStructuralIdentifiabilityExtension, which permits StructuralIdentifiability.jl function to be applied directly to Catalyst systems. E.g. use ```julia using Catalyst, StructuralIdentifiability @@ -17,47 +91,9 @@ assess_identifiability(goodwind_oscillator; measured_quantities=[:M]) ``` to assess (global) structural identifiability for all parameters and variables of the `goodwind_oscillator` model (under the presumption that we can measure `M` only). - Automatically handles conservation laws for structural identifiability problems (eliminates these internally to speed up computations). -- Adds a tutorial to illustrate the use of the extension. -- Enable adding metadata to individual reactions, e.g: -```julia -rn = @reaction_network begin - @parameters η - k, 2X --> X2, [noise_scaling=η] -end -getnoisescaling(rn) -``` -- `SDEProblem` no longer takes the `noise_scaling` argument (see above for new approach to handle noise scaling). -- Changed fields of internal `Reaction` structure. `ReactionSystems`s saved using `serialize` on previous Catalyst versions cannot be loaded using this (or later) versions. -- 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. -- LatticeReactionSystem structure represents a spatial reaction network: - ```julia - rn = @reaction_network begin - (p,d), 0 <--> X - end - tr = @transport_reaction D X - lattice = Graphs.grid([5, 5]) - lrs = LatticeReactionSystem(rn, [tr], lattice) -``` -- Here, if a `u0` or `p` vector is given with scalar values: - ```julia - u0 = [:X => 1.0] - p = [:p => 1.0, :d => 0.5, :D => 0.1] - ``` - this value will be used across the entire system. If their values are instead vectors, different values are used across the spatial system. Here - ```julia - X0 = zeros(25) - X0[1] = 1.0 - u0 = [:X => X0] - ``` - X's value will be `1.0` in the first vertex, but `0.0` in the remaining one (the system have 25 vertexes in total). SInce th parameters `p` and `d` are part of the non-spatial reaction network, their values are tied to vertexes. However, if the `D` parameter (which governs diffusion between vertexes) is given several values, these will instead correspond to the specific edges (and transportation along those edges.) +- A more detailed of how this extension works can be found [here](https://docs.sciml.ai/Catalyst/stable/inverse_problems/structural_identifiability/). -- Update how compounds are created. E.g. use -```julia -@variables t C(t) O(t) -@compound CO2 ~ C + 2O -``` -to create a compound species `CO2` that consists of `C` and 2 `O`. -- Added documentation for chemistry related functionality (compound creation and reaction balancing). +#### Bifurcation identifiability extension - Add a CatalystBifurcationKitExtension, permitting BifurcationKit's `BifurcationProblem`s to be created from Catalyst reaction networks. Example usage: ```julia using Catalyst @@ -86,20 +122,6 @@ plot(bif_dia; xguide="k1", yguide="X") ``` - Automatically handles elimination of conservation laws for computing bifurcation diagrams. - Updated Bifurcation documentation with respect to this new feature. -- Added function `isautonomous` to check if a `ReactionSystem` is autonomous. -- Added function `steady_state_stability` to compute stability for steady states. Example: -```julia -# Creates model. -rn = @reaction_network begin - (p,d), 0 <--> X -end -p = [:p => 1.0, :d => 0.5] - -# Finds (the trivial) steady state, and computes stability. -steady_state = [2.0] -steady_state_stability(steady_state, rn, p) -``` -Here, `steady_state_stability` take an optional argument `tol = 10*sqrt(eps())`, which is used to determine whether a eigenvalue real part is reliably less that 0. ## Catalyst 13.5 - Added a CatalystHomotopyContinuationExtension extension, which exports the `hc_steady_state` function if HomotopyContinuation is exported. `hc_steady_state` finds the steady states of a reaction system using the homotopy continuation method. This feature is only available for julia versions 1.9+. Example: diff --git a/docs/make.jl b/docs/make.jl index af7e2ffd4a..0d354c641a 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -40,6 +40,7 @@ makedocs(sitename = "Catalyst.jl", doctest = false, clean = true, pages = pages, + pagesonly = false, warnonly = [:missing_docs]) deploydocs(repo = "github.com/SciML/Catalyst.jl.git"; diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 20f1c18be6..88d1ff6d00 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -1,19 +1,19 @@ # Version 14 Migration Guide -Catalyst is built on the [ModelingToolkit.jl](https://github.com/SciML/ModelingToolkit.jl) modelling language. A recent update of ModelingToolkit from version 8 to version 9 have required a corresponding update to Catalyst (from version 13 to 14). This update have introduced a couple of breaking changes, all of which will be detailed below. +Catalyst is built on the [ModelingToolkit.jl](https://github.com/SciML/ModelingToolkit.jl) modelling language. A recent update of ModelingToolkit from version 8 to version 9 has required a corresponding update to Catalyst (from version 13 to 14). This update has introduced a couple of breaking changes, all of which will be detailed below. !!! note - Catalyst version 14 also introduces a large number of new features. These will not be discussed here, however, they are described in Catalyst's [history file]([@ref ](https://github.com/SciML/Catalyst.jl/blob/master/HISTORY.md)). + Catalyst version 14 also introduces several new features. These will not be discussed here, however, they are described in Catalyst's [history file](https://github.com/SciML/Catalyst.jl/blob/master/HISTORY.md). ## System completeness -In ModelingToolkit v19 (and thus also Catalyst v14) all systems (e.g. `ReactionSystem`s and `ODESystem`s) are either *complete* or *incomplete* (completeness was a thing previously, however, recent updates mean that the user now have to be aware of this). Complete and incomplete systems differ in that -- Only complete models can be used as inputs to simulations or certain tools for model analysis. -- Only incomplete models can be [composed with other models to form hierarchical models](@ref compositional_modeling). +In ModelingToolkit v9 (and thus also Catalyst v14) all systems (e.g. `ReactionSystem`s and `ODESystem`s) are either *complete* or *incomplete* (completeness was already a thing, however, recent updates mean that the user now has to be aware of this). Complete and incomplete systems differ in that +- Only complete systems can be used as inputs to simulations or most tools for model analysis. +- Only incomplete systems can be [composed with other systems to form hierarchical models](@ref compositional_modeling). A model's completeness depends on how it was created: - Models created programmatically (using the `ReactionSystem` constructor) are *incomplete*. - Models created using the `@reaction_network` DSL are *complete*. -- To create *incomplete models using the DSL*, use the `@network_component` macro. +- To create *incomplete models using the DSL*, use the `@network_component` macro (in all other aspects identical to `@reaction_network`). - Models generated through the `compose` and `extend` functions are *incomplete*. Furthermore, any systems generated through e.g. `convert(ODESystem, rs)` are also complete. @@ -30,11 +30,11 @@ rxs = [ ] @named rs = ReactionSystem(rxs, t) ``` -Here we have created an incomplete model. If we think our model is ready (i.e. we do not wish to compose it with additional models) we mark it as complete: +Here we have created an incomplete model. If our model is ready (i.e. we do not wish to compose it with additional models) we mark it as complete: ```@example v14_migration_1 rs = complete(rs) ``` -Here, `complete` does not change the input model, but simply creates a new complete model. We hence overwrite our model variable (`rs`) with `complete`'s output. We can confirm that our model is complete using the `iscomplete` function +Here, `complete` does not change the input model, but simply creates a new (complete) model. We hence overwrite our model variable (`rs`) with `complete`'s output. We can confirm that our model is complete using the `Catalyst.iscomplete` function: ```@example v14_migration_1 Catalyst.iscomplete(rs) ``` @@ -49,12 +49,12 @@ sol = solve(oprob) plot(sol) ``` -if we wish to first create a `ODESystem` from our `ReactionSystem`, the created `ODESystem` will be incomplete: +If we wish to first convert out `ReactionSystem` to an `ODESystem`, the `ODESystem` will be incomplete: ```@example v14_migration_1 osys = convert(ODESystem, rs) Catalyst.iscomplete(osys) ``` -(note that `rs` must be complete before it can be converted to a `ODESystem`) +(note that `rs` must be complete before it can be converted to an `ODESystem` or any other system type) If we now wish to create an `ODEProblem` from our `ODESystem`, we must first mark it as complete (using similar syntax as for our `ReactionSystem`): ```@example v14_migration_1 @@ -65,7 +65,7 @@ plot(sol) ``` ## Unknowns instead of states -Previously, "states" was used as a term for a systems variables (both species and non-species variables). Now, the term "unknowns" is preferred instead. This means that there have been a number of changes to function names (e.g. `states` => `unknowns`, `get_states` => `get_unknowns`). +Previously, "states" was used as a term for system variables (both species and non-species variables). Now, the term "unknowns" will be used instead. This means that there have been a number of changes to function names (e.g. `states` => `unknowns` and `get_states` => `get_unknowns`). E.g. here we declare a `ReactionSystem` model containing both species and non-species unknowns: ```@example v14_migration_2 @@ -96,9 +96,9 @@ nonspecies(rs) ``` ## Lost support for most units -As part of its v9 update, ModelingToolkit changed how units was handled. This includes using the package [DynamicQuantities.jl](https://github.com/SymbolicML/DynamicQuantities.jl) to manage units (instead of [Uniful.jl](https://github.com/PainterQubits/Unitful.jl), like previously). +As part of its v9 update, ModelingToolkit changed how units were handled. This includes using the package [DynamicQuantities.jl](https://github.com/SymbolicML/DynamicQuantities.jl) to manage units (instead of [Uniful.jl](https://github.com/PainterQubits/Unitful.jl), like previously). -While this should lead to long-term improvements, unfortunately, as part of the process support for most units where removed. Currently, only the main SI units are supported (`s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`). Composite units (e.g. `N = kg/(m^2)`) are no longer supported. Furthermore, prefix units (e.g. `mm = m/1000`) are not supported either. This means that most units relevant to Catalyst (such as `µM`) cannot be used directly. While composite units can still be written out in full and used (e.f. `mol/(m^3)`) this is hardly user friendly. +While this should lead to long-term improvements, unfortunately, as part of the process support for most units was removed. Currently, only the main SI units are supported (`s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`). Composite units (e.g. `N = kg/(m^2)`) are no longer supported. Furthermore, prefix units (e.g. `mm = m/1000`) are not supported either. This means that most units relevant to Catalyst (such as `µM`) cannot be used directly. While composite units can still be written out in full and used (e.g. `kg/(m^2)`) this is hardly user-friendly. The maintainers of ModelingTOolkit have been notified of this issue. We are unsure when this will be fixed, however, we do not think it will be a permanent change. @@ -108,14 +108,13 @@ According to ModelingToolkit policy, created systems should not be modified. In It is still possible to add default values to a created `ReactionSystem`, i.e. the `setdefaults!` function is still supported. ## New interface for creating time variable (`t`) and its differential (`D`) - -Previously, the time independent variable (typically called `t`) was declared using +Previously, the time-independent variable (typically called `t`) was declared using ```@example v14_migration_3 using Catalyst @variables t nothing # hide ``` -A new, preferred, interface have now been introduced: +A new, preferred, interface has now been introduced: ```@example v14_migration_3 t = default_t() nothing # hide @@ -126,43 +125,48 @@ Similarly, the time differential (primarily relevant when creating combined reac D = Differential(t) nothing # hide ``` -where the preferred method now being +where the preferred method is now ```@example v14_migration_3 -Dt = default_time_deriv() +D = default_time_deriv() nothing # hide ``` -## New interface for accessing problem/integrator/solution parameter (and species) value +!!! note + If you look at ModelingToolkit documentation, these defaults are instead retrieved using `using ModelingToolkit: t_nounits as t, D_nounits as D`. This will also work, however, in Catalyst we have opted to instead use `default_t` and `default_time_deriv` as our main approach. -Previously, it was possible to index problems to query them for their parameter values. e.g. +## New interface for accessing problem/integrator/solution parameter (and species) value +Previously, it was possible to directly index problems to query them for their parameter values. e.g. ```@example v14_migration_4 using Catalyst rn = @reaction_network begin - (p,d), 0 <--> X + (p,d), 0 <--> X end -oprob = ODEProblem(rn, [:X => 1.0], (0.0, 1.0), [:p => 1.0, :d => 0.2]) +u0 = [:X => 1.0] +ps = [:p => 1.0, :d => 0.2] +oprob = ODEProblem(rn, u0, (0.0, 1.0), ps) nothing # hide +``` ```julia oprob[:p] ``` -This *is no longer supported*. When you wish to query a problem (or integrator or solution) for a parameter value (or to update a parameter value), you must append `.ps` to the problem: +This is *no longer supported*. When you wish to query a problem (or integrator or solution) for a parameter value (or to update a parameter value), you must append `.ps` to the problem variable name: ```@example v14_migration_4 oprob.ps[:p] ``` -Furthermore, a few new functions (`getp`, `getu`, `setp`, `setu`) have been introduced. These can improve performance when querying a structure for a value multiple times (especially for very large models). These are describe in more detail [here](@ref simulation_structure_interfacing_functions). +Furthermore, a few new functions (`getp`, `getu`, `setp`, `setu`) have been introduced. These can improve performance when querying a structure for a value multiple times (especially for very large models). These are described in more detail [here](@ref simulation_structure_interfacing_functions). For more details on how to query various structures for parameter and species values, please read [this documentation page](@ref simulation_structure_interfacing). ## Other notes Finally, here are some additional, minor, notes regarding the new update. -### Modification of problems with conservation laws broken -While it is possible to update e.g. `ODEProblem`s using the [`remake`](@ref simulation_structure_interfacing_problems_remake) function, this is not possible if the `remove_conserved = true` option was used. E.g. while +#### Modification of problems with conservation laws broken +While it is possible to update e.g. `ODEProblem`s using the [`remake`](@ref simulation_structure_interfacing_problems_remake) function, this is currently not possible if the `remove_conserved = true` option was used. E.g. while ```@example v14_migration_5 using Catalyst, OrdinaryDiffEq rn = @reaction_network begin - (k1,k2), X1 <--> X2 + (k1,k2), X1 <--> X2 end u0 = [:X1 => 1.0, :X2 => 2.0] ps = [:k1 => 0.5, :k2 => 3.0] @@ -170,19 +174,20 @@ oprob = ODEProblem(rn, u0, (0.0, 10.0), ps; remove_conserved = true) sol(oprob) # hide ``` -is perfectly fine, attempting to then modify `oprob` (in any manner!) is not possible (without introducing a bug): +is perfectly fine, attempting to then modify `oprob` (in any manner!) is not possible: ```@example v14_migration_5 oprob_remade = remake(oprob; u0 = [:X1 => 5.0]) # NEVER do this. sol(oprob) # hide ``` +This might generate a silent error, where the remade problem is different from the intended one. This bug was likely present on earlier versions as well, but was only recently discovered. While we hope it will be fixed soon, the bug is in ModelingToolkit, and will not be fixed until its maintainers find the time to do so. -### Depending on parameter order even more dangerous than before -In early versions of Catalyst, parameters and species were provided as vectors (e.g. `[1.0, 2.0]`) rather than maps (e.g. `[p => 1.0, d => 2.0]`). While it has been *strongly* recommended to use the map form for a while, the vector form is still (technically) possible (however, we would not guarantee that this would produce expected behaviours). However, due to recent internal ModelingToolkit updates, the risk of unexpected behaviour when providing parameter values as vector is even larger than before. +#### Depending on parameter order is even more dangerous than before +In early versions of Catalyst, parameters and species were provided as vectors (e.g. `[1.0, 2.0]`) rather than maps (e.g. `[p => 1.0, d => 2.0]`). While we have already *strongly* recommended users to use the map form (or they might produce unintended results), the vector form is still (technically). Due to recent internal ModelingToolkit updates, the risk of unexpected behaviour when providing parameter values as vectors is even larger than before. *Users should never use vector-forms to represent parameter and species values* -### Additional deprecated functions +#### Additional deprecated functions The `reactionparams`, `numreactionparams`, and `reactionparamsmap` functions have been deprecated. \ No newline at end of file From 36682681b3f5b4154bad2a8745ce8b8c1858f320 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 9 Jun 2024 16:41:24 -0400 Subject: [PATCH 247/446] minor history file updates --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index d1558c122d..d3d88ff999 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -49,7 +49,7 @@ steady_state = [2.0] steady_state_stability(steady_state, rn, p) ``` Here, `steady_state_stability` takes an optional argument `tol = 10*sqrt(eps())`, which is used to determine whether a eigenvalue real part is reliably less than 0. -- Added a DSL option, `@combinatoric_ratelaws`, which can be used to toggle whether to use combinatorial rate laws within the DSL. Example: +- Added a DSL option, `@combinatoric_ratelaws`, which can be used to toggle whether to use combinatorial rate laws within the DSL (this feature was already supported for programmatic modelling). Example: ```julia # Creates model. rn = @reaction_network begin From 884cc7f1e930cdf30224212b6096fdce3119f4ac Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 9 Jun 2024 16:44:09 -0400 Subject: [PATCH 248/446] migration guide code fixes --- docs/src/v14_migration_guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 88d1ff6d00..5d426a5e60 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -3,7 +3,7 @@ Catalyst is built on the [ModelingToolkit.jl](https://github.com/SciML/ModelingToolkit.jl) modelling language. A recent update of ModelingToolkit from version 8 to version 9 has required a corresponding update to Catalyst (from version 13 to 14). This update has introduced a couple of breaking changes, all of which will be detailed below. !!! note - Catalyst version 14 also introduces several new features. These will not be discussed here, however, they are described in Catalyst's [history file](https://github.com/SciML/Catalyst.jl/blob/master/HISTORY.md). + Catalyst version 14 also introduces several new features. These will not be discussed here, however, they are described in Catalyst's [history file](https://github.com/SciML/Catalyst.jl/blob/master/HISTORY.md). ## System completeness In ModelingToolkit v9 (and thus also Catalyst v14) all systems (e.g. `ReactionSystem`s and `ODESystem`s) are either *complete* or *incomplete* (completeness was already a thing, however, recent updates mean that the user now has to be aware of this). Complete and incomplete systems differ in that @@ -43,7 +43,7 @@ We can now go on and use our model for e.g. simulations: using OrdinaryDiffEq, Plots u0 = [X => 0.1] tspan = (0.0, 10.0) -ps = [d => 1.0, d => 0.2] +ps = [p => 1.0, d => 0.2] oprob = ODEProblem(rs, u0, tspan, ps) sol = solve(oprob) plot(sol) From 34260b2bd2b73851722efd9b57b7908815b0a4bb Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 10 Jun 2024 05:35:14 +0200 Subject: [PATCH 249/446] Update HISTORY.md --- HISTORY.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index d3d88ff999..435dd22ff7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -93,7 +93,7 @@ to assess (global) structural identifiability for all parameters and variables o - Automatically handles conservation laws for structural identifiability problems (eliminates these internally to speed up computations). - A more detailed of how this extension works can be found [here](https://docs.sciml.ai/Catalyst/stable/inverse_problems/structural_identifiability/). -#### Bifurcation identifiability extension +#### Bifurcation analysis extension - Add a CatalystBifurcationKitExtension, permitting BifurcationKit's `BifurcationProblem`s to be created from Catalyst reaction networks. Example usage: ```julia using Catalyst @@ -110,15 +110,15 @@ bif_par = :k1 u_guess = [:X => 5.0, :Y => 2.0] p_start = [:k1 => 4.0, :k2 => 1.0, :k3 => 1.0, :k4 => 1.5, :k5 => 1.25] plot_var = :X -bprob = BifurcationProblem(wilhelm_2009_model, u_guess, p_start, bif_par; plot_var=plot_var) +bprob = BifurcationProblem(wilhelm_2009_model, u_guess, p_start, bif_par; plot_var = plot_var) p_span = (2.0, 20.0) -opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps=1000) +opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000) -bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside=true) +bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true) using Plots -plot(bif_dia; xguide="k1", yguide="X") +plot(bif_dia; xguide = "k1", guide = "X") ``` - Automatically handles elimination of conservation laws for computing bifurcation diagrams. - Updated Bifurcation documentation with respect to this new feature. From 97e2d3d0d707a366f0abf8526c229cf9f14988ac Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 10 Jun 2024 16:21:57 +0200 Subject: [PATCH 250/446] Update docs/src/steady_state_functionality/dynamical_systems.md Co-authored-by: Sam Isaacson --- docs/src/steady_state_functionality/dynamical_systems.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/steady_state_functionality/dynamical_systems.md b/docs/src/steady_state_functionality/dynamical_systems.md index f6f1fb8ee6..01b21fcc57 100644 --- a/docs/src/steady_state_functionality/dynamical_systems.md +++ b/docs/src/steady_state_functionality/dynamical_systems.md @@ -144,7 +144,7 @@ If you use this functionality in your research, [in addition to Catalyst](@ref c --- ## Learning more -If you want to learn more about analysing dynamical systems, including chaotic behaviour, you can have a look at the textbook [Nonlinear Dynamics](https://link.springer.com/book/10.1007/978-3-030-91032-7). It utilizes DynamicalSystems.jl and provides a concise, hands-on approach to learning nonlinear dynamics and analysing dynamical systems [^3]. +If you want to learn more about analysing dynamical systems, including chaotic behaviour, see the textbook [Nonlinear Dynamics](https://link.springer.com/book/10.1007/978-3-030-91032-7). It utilizes DynamicalSystems.jl and provides a concise, hands-on approach to learning nonlinear dynamics and analysing dynamical systems [^3]. --- From 8ea1ec504475fae1065121e1009d661241352545 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 10 Jun 2024 11:24:53 -0400 Subject: [PATCH 251/446] remove erroneous mention of broken test --- test/reactionsystem_core/coupled_equation_crn_systems.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/test/reactionsystem_core/coupled_equation_crn_systems.jl b/test/reactionsystem_core/coupled_equation_crn_systems.jl index fc662d9c59..b8404f6514 100644 --- a/test/reactionsystem_core/coupled_equation_crn_systems.jl +++ b/test/reactionsystem_core/coupled_equation_crn_systems.jl @@ -557,7 +557,6 @@ let @test osol[B][end] ≈ 1.0 # Checks that SteadyState simulation of the system achieves the correct steady state. - # Currently broken due to MTK. ssprob = SteadyStateProblem(coupled_rs, u0, ps; structural_simplify = true) sssol = solve(ssprob, DynamicSS(Vern7()); abstol = 1e-8, reltol = 1e-8) @test sssol[X][end] ≈ 2.0 From c898718e942e6a495c34d1afaa91d811195a1cbd Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 10 Jun 2024 11:30:37 -0400 Subject: [PATCH 252/446] init --- src/reactionsystem.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index bf20c94113..9962586cfd 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -204,6 +204,14 @@ end ### ReactionSystem Structure ### +""" +WARNING!!! + +The following variable is used to check that code that should be updated when the `ReactionSystem` +fields are updated have in fact been updated. Do not just blindly update this without first checking +all such code and updating it appropriately (e.g. serialization). Please use a search for +`reactionsystem_fields` throughout the package to ensure all places which should be updated, are updated. +""" # Constant storing all reaction system fields (in order). Used to check whether the `ReactionSystem` # structure have been updated (in the `reactionsystem_uptodate_check` function). const reactionsystem_fields = ( From 63b61d57d5cdf3ac33c7ece7e491d48e11c530b2 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 11:47:38 -0400 Subject: [PATCH 253/446] fix jump internals test --- test/reactionsystem_core/reactionsystem.jl | 59 ++++++++++++---------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index 6876026e0b..93fda454cc 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -159,7 +159,6 @@ end # Test with JumpSystem. let - p = rand(rng, length(k)) @species A(t) B(t) C(t) D(t) E(t) F(t) rxs = [Reaction(k[1], nothing, [A]), # 0 -> A Reaction(k[2], [B], nothing), # B -> 0 @@ -193,27 +192,29 @@ let @test all(map(i -> typeof(equations(js)[i]) <: JumpProcesses.ConstantRateJump, cidxs)) @test all(map(i -> typeof(equations(js)[i]) <: JumpProcesses.VariableRateJump, vidxs)) - pars = rand(rng, length(k)) + p = rand(rng, length(k)) + pmap = parameters(js) .=> p u0 = rand(rng, 2:10, 6) + u0map = unknowns(js) .=> u0 ttt = rand(rng) jumps = Vector{Union{ConstantRateJump, MassActionJump, VariableRateJump}}(undef, length(rxs)) - jumps[1] = MassActionJump(pars[1], Vector{Pair{Int, Int}}(), [1 => 1]) - jumps[2] = MassActionJump(pars[2], [2 => 1], [2 => -1]) - jumps[3] = MassActionJump(pars[3], [1 => 1], [1 => -1, 3 => 1]) - jumps[4] = MassActionJump(pars[4], [3 => 1], [1 => 1, 2 => 1, 3 => -1]) - jumps[5] = MassActionJump(pars[5], [3 => 1], [1 => 2, 3 => -1]) - jumps[6] = MassActionJump(pars[6], [1 => 1, 2 => 1], [1 => -1, 2 => -1, 3 => 1]) - jumps[7] = MassActionJump(pars[7], [2 => 2], [1 => 1, 2 => -2]) - jumps[8] = MassActionJump(pars[8], [1 => 1, 2 => 1], [2 => -1, 3 => 1]) - jumps[9] = MassActionJump(pars[9], [1 => 1, 2 => 1], [1 => -1, 2 => -1, 3 => 1, 4 => 1]) - jumps[10] = MassActionJump(pars[10], [1 => 2], [1 => -2, 3 => 1, 4 => 1]) - jumps[11] = MassActionJump(pars[11], [1 => 2], [1 => -1, 2 => 1]) - jumps[12] = MassActionJump(pars[12], [1 => 1, 2 => 3, 3 => 4], + jumps[1] = MassActionJump(p[1], Vector{Pair{Int, Int}}(), [1 => 1]) + jumps[2] = MassActionJump(p[2], [2 => 1], [2 => -1]) + jumps[3] = MassActionJump(p[3], [1 => 1], [1 => -1, 3 => 1]) + jumps[4] = MassActionJump(p[4], [3 => 1], [1 => 1, 2 => 1, 3 => -1]) + jumps[5] = MassActionJump(p[5], [3 => 1], [1 => 2, 3 => -1]) + jumps[6] = MassActionJump(p[6], [1 => 1, 2 => 1], [1 => -1, 2 => -1, 3 => 1]) + jumps[7] = MassActionJump(p[7], [2 => 2], [1 => 1, 2 => -2]) + jumps[8] = MassActionJump(p[8], [1 => 1, 2 => 1], [2 => -1, 3 => 1]) + jumps[9] = MassActionJump(p[9], [1 => 1, 2 => 1], [1 => -1, 2 => -1, 3 => 1, 4 => 1]) + jumps[10] = MassActionJump(p[10], [1 => 2], [1 => -2, 3 => 1, 4 => 1]) + jumps[11] = MassActionJump(p[11], [1 => 2], [1 => -1, 2 => 1]) + jumps[12] = MassActionJump(p[12], [1 => 1, 2 => 3, 3 => 4], [1 => -1, 2 => -3, 3 => -2, 4 => 3]) - jumps[13] = MassActionJump(pars[13], [1 => 3, 2 => 1], [1 => -3, 2 => -1]) - jumps[14] = MassActionJump(pars[14], Vector{Pair{Int, Int}}(), [1 => 2]) + jumps[13] = MassActionJump(p[13], [1 => 3, 2 => 1], [1 => -3, 2 => -1]) + jumps[14] = MassActionJump(p[14], Vector{Pair{Int, Int}}(), [1 => 2]) jumps[15] = ConstantRateJump((u, p, t) -> p[15] * u[1] / (2 + u[1]), integrator -> (integrator.u[1] -= 1)) @@ -230,32 +231,34 @@ let integrator -> (integrator.u[4] -= 2; integrator.u[5] -= 1; integrator.u[6] += 2)) unknownoid = Dict(unknown => i for (i, unknown) in enumerate(unknowns(js))) - jspmapper = ModelingToolkit.JumpSysMajParamMapper(js, pars) + dprob = DiscreteProblem(js, u0map, (0.0, 10.0), pmap) + mtkpars = dprob.p + jspmapper = ModelingToolkit.JumpSysMajParamMapper(js, mtkpars) symmaj = ModelingToolkit.assemble_maj(equations(js).x[1], unknownoid, jspmapper) - maj = MassActionJump(symmaj.param_mapper(pars), symmaj.reactant_stoch, symmaj.net_stoch, + maj = MassActionJump(symmaj.param_mapper(mtkpars), symmaj.reactant_stoch, symmaj.net_stoch, symmaj.param_mapper, scale_rates = false) for i in midxs - @test_broken abs(jumps[i].scaled_rates - maj.scaled_rates[i]) < 100 * eps() + @test abs(jumps[i].scaled_rates - maj.scaled_rates[i]) < 100 * eps() @test jumps[i].reactant_stoch == maj.reactant_stoch[i] @test jumps[i].net_stoch == maj.net_stoch[i] end for i in cidxs crj = ModelingToolkit.assemble_crj(js, equations(js)[i], unknownoid) - @test_broken isapprox(crj.rate(u0, p, ttt), jumps[i].rate(u0, p, ttt)) - fake_integrator1 = (u = zeros(6), p = p, t = 0.0) - fake_integrator2 = deepcopy(fake_integrator1) + @test isapprox(crj.rate(u0, mtkpars, ttt), jumps[i].rate(u0, p, ttt)) + fake_integrator1 = (u = zeros(6), p = mtkpars, t = 0.0) + fake_integrator2 = (u = zeros(6), p, t = 0.0) crj.affect!(fake_integrator1) jumps[i].affect!(fake_integrator2) - @test fake_integrator1 == fake_integrator2 + @test fake_integrator1.u == fake_integrator2.u end for i in vidxs crj = ModelingToolkit.assemble_vrj(js, equations(js)[i], unknownoid) - @test_broken isapprox(crj.rate(u0, p, ttt), jumps[i].rate(u0, p, ttt)) - fake_integrator1 = (u = zeros(6), p = p, t = 0.0) - fake_integrator2 = deepcopy(fake_integrator1) + @test isapprox(crj.rate(u0, mtkpars, ttt), jumps[i].rate(u0, p, ttt)) + fake_integrator1 = (u = zeros(6), p = mtkpars, t = 0.0) + fake_integrator2 = (u = zeros(6), p, t = 0.0) crj.affect!(fake_integrator1) jumps[i].affect!(fake_integrator2) - @test fake_integrator1 == fake_integrator2 + @test fake_integrator1.u == fake_integrator2.u end end @@ -439,7 +442,7 @@ let umean += sol(10.0, idxs = [B1, B2, B3, C]) end umean /= Nsims - @test isapprox(umean[1], umean[2]; rtol = 1e-2) + @test isapprox(umean[1], umean[2]; rtol = 1e-2) @test isapprox(umean[1], umean[3]; rtol = 1e-2) @test umean[4] == 10 end From 175aba5440cadcab3c6e37a6ab9f30fbd9019213 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 13:07:53 -0400 Subject: [PATCH 254/446] Update src/reactionsystem.jl --- src/reactionsystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 9962586cfd..a84d41c522 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -208,7 +208,7 @@ end WARNING!!! The following variable is used to check that code that should be updated when the `ReactionSystem` -fields are updated have in fact been updated. Do not just blindly update this without first checking +fields are updated has in fact been updated. Do not just blindly update this without first checking all such code and updating it appropriately (e.g. serialization). Please use a search for `reactionsystem_fields` throughout the package to ensure all places which should be updated, are updated. """ From 8c9b4be42618ea4ac6f97e796f75be9bd6ef18fc Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 13:35:04 -0400 Subject: [PATCH 255/446] update to master --- docs/src/model_creation/parametric_stoichiometry.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/model_creation/parametric_stoichiometry.md b/docs/src/model_creation/parametric_stoichiometry.md index a0d370ef0d..1f5c2fab6c 100644 --- a/docs/src/model_creation/parametric_stoichiometry.md +++ b/docs/src/model_creation/parametric_stoichiometry.md @@ -9,6 +9,7 @@ is a parameter, and the number of products is the product of two parameters. ```@example s1 using Catalyst, Latexify, OrdinaryDiffEq, ModelingToolkit, Plots revsys = @reaction_network revsys begin + @parameters m::Int64 n::Int64 k₊, m*A --> (m*n)*B k₋, B --> A end @@ -36,7 +37,7 @@ We could have equivalently specified our systems directly via the Catalyst API. For example, for `revsys` we would could use ```@example s1 t = default_t() -@parameters k₊, k₋, m, n +@parameters k₊ k₋ m::Int n @species A(t), B(t) rxs = [Reaction(k₊, [A], [B], [m], [m*n]), Reaction(k₋, [B], [A])] From 62b80042460630576b075f97f48ef536857f1474 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 13:57:56 -0400 Subject: [PATCH 256/446] fix parametric stoich tutorial --- .../parametric_stoichiometry.md | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/src/model_creation/parametric_stoichiometry.md b/docs/src/model_creation/parametric_stoichiometry.md index 1f5c2fab6c..547fefdcb2 100644 --- a/docs/src/model_creation/parametric_stoichiometry.md +++ b/docs/src/model_creation/parametric_stoichiometry.md @@ -15,7 +15,12 @@ revsys = @reaction_network revsys begin end reactions(revsys) ``` -Note, as always the `@reaction_network` macro defaults to setting all symbols +Notice, as described in the [Reaction rate laws used in simulations](@ref) +section, the default rate laws involve factorials in the stoichiometric +coefficients. For this reason we explicitly specify `m` and `n` as integers (as +otherwise ModelingToolkit will implicitly assume they are floating point). + +As always the `@reaction_network` macro defaults to setting all symbols neither used as a reaction substrate nor a product to be parameters. Hence, in this example we have two species (`A` and `B`) and four parameters (`k₊`, `k₋`, `m`, and `n`). In addition, the stoichiometry is applied to the rightmost symbol @@ -37,21 +42,21 @@ We could have equivalently specified our systems directly via the Catalyst API. For example, for `revsys` we would could use ```@example s1 t = default_t() -@parameters k₊ k₋ m::Int n +@parameters k₊ k₋ m::Int n::Int @species A(t), B(t) rxs = [Reaction(k₊, [A], [B], [m], [m*n]), Reaction(k₋, [B], [A])] revsys2 = ReactionSystem(rxs,t; name=:revsys) revsys2 == revsys ``` -which can be simplified using the `@reaction` macro to +or ```@example s1 -rxs2 = [(@reaction k₊, m*A --> (m*n)*B), +rxs2 = [(@reaction k₊, $m*A --> ($m*$n)*B), (@reaction k₋, B --> A)] revsys3 = ReactionSystem(rxs2,t; name=:revsys) revsys3 == revsys ``` -Note, the `@reaction` macro again assumes all symbols are parameters except the +Here we interpolate in the pre-declared `m` and `n` symbolic variables using `$m` and `$n` to ensure the parameter is known to be integer-valued. The `@reaction` macro again assumes all symbols are parameters except the substrates or reactants (i.e. `A` and `B`). For example, in `@reaction k, F*A + 2(H*G+B) --> D`, the substrates are `(A,G,B)` with stoichiometries `(F,2*H,2)`. @@ -63,10 +68,6 @@ osys = complete(osys) equations(osys) show(stdout, MIME"text/plain"(), equations(osys)) # hide ``` -Notice, as described in the [Reaction rate laws used in simulations](@ref) -section, the default rate laws involve factorials in the stoichiometric -coefficients. For this reason we must specify `m` and `n` as integers, and hence -*use a tuple for the parameter mapping* ```@example s1 p = (k₊ => 1.0, k₋ => 1.0, m => 2, n => 2) u₀ = [A => 1.0, B => 1.0] @@ -78,8 +79,6 @@ We can now solve and plot the system sol = solve(oprob, Tsit5()) plot(sol) ``` -*If we had used a vector to store parameters, `m` and `n` would be converted to -floating point giving an error when solving the system.* **Note, currently a [bug](https://github.com/SciML/ModelingToolkit.jl/issues/2296) in ModelingToolkit has broken this example by converting to floating point when using tuple parameters, see the alternative approach below for a workaround.** An alternative approach to avoid the issues of using mixed floating point and integer variables is to disable the rescaling of rate laws as described in @@ -89,6 +88,11 @@ directly building the problem from a `ReactionSystem` instead of first converting to an `ODESystem`). For the previous example this gives the following (different) system of ODEs ```@example s1 +revsys = @reaction_network revsys begin + @parameters m::Int64 n::Int64 + k₊, m*A --> (m*n)*B + k₋, B --> A +end osys = convert(ODESystem, revsys; combinatoric_ratelaws = false) osys = complete(osys) equations(osys) From 8de3c9e9108217faaef1437a51cba26f7179ca78 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 14:17:40 -0400 Subject: [PATCH 257/446] fixes --- docs/make.jl | 2 +- docs/src/assets/Project.toml | 6 ++--- .../smoluchowski_coagulation_equation.md | 6 ++--- .../parametric_stoichiometry.md | 22 ++++++++++--------- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 8595cd6ffa..32df32290d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -41,7 +41,7 @@ makedocs(sitename = "Catalyst.jl", clean = true, pages = pages, pagesonly = true, - warnonly = [:missing_docs]) + warnonly = true) deploydocs(repo = "github.com/SciML/Catalyst.jl.git"; push_preview = true) diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index 83ecae3cc6..b92da6ef5d 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -51,7 +51,7 @@ IncompleteLU = "0.2" JumpProcesses = "9.11" Latexify = "0.16" LinearSolve = "2.30" -ModelingToolkit = "9.15" +ModelingToolkit = "9.16.0" NonlinearSolve = "3.12" Optim = "1.9" Optimization = "3.25" @@ -68,5 +68,5 @@ SpecialFunctions = "2.4" StaticArrays = "1.9" SteadyStateDiffEq = "2.2" StochasticDiffEq = "6.65" -StructuralIdentifiability = "0.5.7" -Symbolics = "5.28" +StructuralIdentifiability = "0.5.8" +Symbolics = "5.30.1" diff --git a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md index dc9427c979..d8865651b4 100644 --- a/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md +++ b/docs/src/model_creation/examples/smoluchowski_coagulation_equation.md @@ -147,6 +147,6 @@ plot!(ϕ, sol[3,:] / Nₒ, line = (:dot, 4, :purple), label = "Analytical sol--X --- ## References -[^1]: [https://en.wikipedia.org/wiki/Smoluchowski\_coagulation\_equation](https://en.wikipedia.org/wiki/Smoluchowski_coagulation_equation) -[^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 -[^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. +1. [https://en.wikipedia.org/wiki/Smoluchowski\_coagulation\_equation](https://en.wikipedia.org/wiki/Smoluchowski_coagulation_equation) +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 +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. diff --git a/docs/src/model_creation/parametric_stoichiometry.md b/docs/src/model_creation/parametric_stoichiometry.md index 547fefdcb2..2704625b25 100644 --- a/docs/src/model_creation/parametric_stoichiometry.md +++ b/docs/src/model_creation/parametric_stoichiometry.md @@ -15,7 +15,7 @@ revsys = @reaction_network revsys begin end reactions(revsys) ``` -Notice, as described in the [Reaction rate laws used in simulations](@ref) +Notice, as described in the [Reaction rate laws used in simulations](@ref introduction_to_catalyst_ratelaws) section, the default rate laws involve factorials in the stoichiometric coefficients. For this reason we explicitly specify `m` and `n` as integers (as otherwise ModelingToolkit will implicitly assume they are floating point). @@ -68,28 +68,30 @@ osys = complete(osys) equations(osys) show(stdout, MIME"text/plain"(), equations(osys)) # hide ``` +Specifying the parameter and initial condition values, ```@example s1 p = (k₊ => 1.0, k₋ => 1.0, m => 2, n => 2) u₀ = [A => 1.0, B => 1.0] oprob = ODEProblem(osys, u₀, (0.0, 1.0), p) nothing # hide ``` -We can now solve and plot the system -```@julia +we can now solve and plot the system +```@example s1 sol = solve(oprob, Tsit5()) plot(sol) ``` An alternative approach to avoid the issues of using mixed floating point and integer variables is to disable the rescaling of rate laws as described in -[Reaction rate laws used in simulations](@ref) section. This requires passing -the `combinatoric_ratelaws=false` keyword to `convert` or to `ODEProblem` (if -directly building the problem from a `ReactionSystem` instead of first -converting to an `ODESystem`). For the previous example this gives the following -(different) system of ODEs +[Reaction rate laws used in simulations](@ref introduction_to_catalyst_ratelaws) +section. This requires passing the `combinatoric_ratelaws=false` keyword to +`convert` or to `ODEProblem` (if directly building the problem from a +`ReactionSystem` instead of first converting to an `ODESystem`). For the +previous example this gives the following (different) system of ODEs where we +now let `m` and `n` be floating point valued parameters (the default): ```@example s1 revsys = @reaction_network revsys begin - @parameters m::Int64 n::Int64 + @parameters m n k₊, m*A --> (m*n)*B k₋, B --> A end @@ -99,7 +101,7 @@ equations(osys) show(stdout, MIME"text/plain"(), equations(osys)) # hide ``` Since we no longer have factorial functions appearing, our example will now run -even with floating point values for `m` and `n`: +with `m` and `n` treated as floating point parameters: ```@example s1 p = (k₊ => 1.0, k₋ => 1.0, m => 2.0, n => 2.0) oprob = ODEProblem(osys, u₀, (0.0, 1.0), p) From 356c8b122bdee8612e23d70a42df5480f47c4a69 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 14:20:31 -0400 Subject: [PATCH 258/446] more fixes --- docs/src/model_creation/parametric_stoichiometry.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/model_creation/parametric_stoichiometry.md b/docs/src/model_creation/parametric_stoichiometry.md index 2704625b25..8fb0b53dd1 100644 --- a/docs/src/model_creation/parametric_stoichiometry.md +++ b/docs/src/model_creation/parametric_stoichiometry.md @@ -91,7 +91,6 @@ previous example this gives the following (different) system of ODEs where we now let `m` and `n` be floating point valued parameters (the default): ```@example s1 revsys = @reaction_network revsys begin - @parameters m n k₊, m*A --> (m*n)*B k₋, B --> A end From 0fd6f16c3a8b675692f3405e5e431076e97f1947 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 14:22:39 -0400 Subject: [PATCH 259/446] restore warning level --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index 32df32290d..8595cd6ffa 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -41,7 +41,7 @@ makedocs(sitename = "Catalyst.jl", clean = true, pages = pages, pagesonly = true, - warnonly = true) + warnonly = [:missing_docs]) deploydocs(repo = "github.com/SciML/Catalyst.jl.git"; push_preview = true) From 494d185c3ea4f7c7e19a19f2e499a42e91e4969f Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 14:25:53 -0400 Subject: [PATCH 260/446] namespace mappings --- docs/src/model_creation/parametric_stoichiometry.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/model_creation/parametric_stoichiometry.md b/docs/src/model_creation/parametric_stoichiometry.md index 8fb0b53dd1..a219abfce5 100644 --- a/docs/src/model_creation/parametric_stoichiometry.md +++ b/docs/src/model_creation/parametric_stoichiometry.md @@ -70,8 +70,8 @@ show(stdout, MIME"text/plain"(), equations(osys)) # hide ``` Specifying the parameter and initial condition values, ```@example s1 -p = (k₊ => 1.0, k₋ => 1.0, m => 2, n => 2) -u₀ = [A => 1.0, B => 1.0] +p = (revsys.k₊ => 1.0, revsys.k₋ => 1.0, revsys.m => 2, revsys.n => 2) +u₀ = [revsys.A => 1.0, revsys.B => 1.0] oprob = ODEProblem(osys, u₀, (0.0, 1.0), p) nothing # hide ``` @@ -102,7 +102,7 @@ show(stdout, MIME"text/plain"(), equations(osys)) # hide Since we no longer have factorial functions appearing, our example will now run with `m` and `n` treated as floating point parameters: ```@example s1 -p = (k₊ => 1.0, k₋ => 1.0, m => 2.0, n => 2.0) +p = (revsys.k₊ => 1.0, revsys.k₋ => 1.0, revsys.m => 2.0, revsys.n => 2.0) oprob = ODEProblem(osys, u₀, (0.0, 1.0), p) sol = solve(oprob, Tsit5()) plot(sol) From c4f18d62a7a15b64a7ddce40bff8a8f9ab213d50 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 14:56:13 -0400 Subject: [PATCH 261/446] final fix --- docs/make.jl | 2 +- docs/src/introduction_to_catalyst/introduction_to_catalyst.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 8595cd6ffa..32df32290d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -41,7 +41,7 @@ makedocs(sitename = "Catalyst.jl", clean = true, pages = pages, pagesonly = true, - warnonly = [:missing_docs]) + warnonly = true) deploydocs(repo = "github.com/SciML/Catalyst.jl.git"; push_preview = true) diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index 94b36738ca..452c9cd9c6 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -182,7 +182,7 @@ Gillespie's `Direct` method, and then solve it to generate one realization of the jump process: ```@example tut1 -# imports the JumpProcesses packages +# imports the JumpProcesses packages using JumpProcesses # redefine the initial condition to be integer valued @@ -361,4 +361,4 @@ and the ODE model --- ## References -[^1]: [Torkel E. Loman, Yingbo Ma, Vasily Ilin, Shashi Gowda, Niklas Korsbo, Nikhil Yewale, Chris Rackauckas, Samuel A. Isaacson, *Catalyst: Fast and flexible modeling of reaction networks*, PLOS Computational Biology (2023).](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530) \ No newline at end of file +1. [Torkel E. Loman, Yingbo Ma, Vasily Ilin, Shashi Gowda, Niklas Korsbo, Nikhil Yewale, Chris Rackauckas, Samuel A. Isaacson, *Catalyst: Fast and flexible modeling of reaction networks*, PLOS Computational Biology (2023).](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530) \ No newline at end of file From abb7242418ee7ee9cc02308e108413d8466ed31b Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 10 Jun 2024 16:13:36 -0400 Subject: [PATCH 262/446] Update test/reactionsystem_core/events.jl Co-authored-by: Sam Isaacson --- test/reactionsystem_core/events.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/reactionsystem_core/events.jl b/test/reactionsystem_core/events.jl index f9c0438463..d83622e370 100644 --- a/test/reactionsystem_core/events.jl +++ b/test/reactionsystem_core/events.jl @@ -158,7 +158,7 @@ let ] # Declares various misformatted events . - @test_broken false # Some missformatted tests should throw error at this stage, but does not (https://github.com/SciML/ModelingToolkit.jl/issues/2612). + @test_broken false # Some misformatted tests should throw error at this stage, but does not (https://github.com/SciML/ModelingToolkit.jl/issues/2612). continuous_events_bad = [ X ~ 1.0 => [X ~ 0.5], # Scalar condition. [X ~ 1.0] => X ~ 0.5, # Scalar affect. From a02a8fc18dc41657d7ab54919afb073aa06f29d1 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 10 Jun 2024 16:13:49 -0400 Subject: [PATCH 263/446] Update test/reactionsystem_core/events.jl Co-authored-by: Sam Isaacson --- test/reactionsystem_core/events.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/reactionsystem_core/events.jl b/test/reactionsystem_core/events.jl index d83622e370..e6de318837 100644 --- a/test/reactionsystem_core/events.jl +++ b/test/reactionsystem_core/events.jl @@ -362,7 +362,7 @@ let sol = solve(jprob, SSAStepper(); seed) # Checks that all `e` parameters have been updated properly. - # Note that periodic discrete events are currently broken for jump processes (and unlikely to be fixed soon due to have events are implemented). + # Note that periodic discrete events are currently broken for jump processes (and unlikely to be fixed soon due to periodic callbacks using the internals of ODE integrator and Datastructures heap implementations). @test sol.ps[:e1] == 1 @test_broken sol.ps[:e2] == 1 # (https://github.com/SciML/JumpProcesses.jl/issues/417) @test sol.ps[:e3] == 1 From f763f9137a0a6552cb9af8fc2680ade8357ad685 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 10 Jun 2024 16:17:16 -0400 Subject: [PATCH 264/446] init --- docs/src/assets/Project.toml | 6 +++--- docs/src/faqs.md | 4 ++-- .../introduction_to_catalyst/introduction_to_catalyst.md | 5 ++--- docs/src/inverse_problems/behaviour_optimisation.md | 2 +- docs/src/inverse_problems/global_sensitivity_analysis.md | 4 ++-- docs/src/inverse_problems/optimization_ode_param_fitting.md | 6 +++--- docs/src/model_simulation/ode_simulation_performance.md | 2 +- docs/src/model_simulation/simulation_introduction.md | 2 +- docs/src/model_simulation/simulation_plotting.md | 6 +++--- .../model_simulation/simulation_structure_interfacing.md | 2 +- docs/src/steady_state_functionality/dynamical_systems.md | 2 +- .../steady_state_stability_computation.md | 2 +- .../petab_ode_param_fitting.md | 0 13 files changed, 21 insertions(+), 22 deletions(-) rename docs/{src/inverse_problems => unpublished}/petab_ode_param_fitting.md (100%) diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index 83ecae3cc6..b92da6ef5d 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -51,7 +51,7 @@ IncompleteLU = "0.2" JumpProcesses = "9.11" Latexify = "0.16" LinearSolve = "2.30" -ModelingToolkit = "9.15" +ModelingToolkit = "9.16.0" NonlinearSolve = "3.12" Optim = "1.9" Optimization = "3.25" @@ -68,5 +68,5 @@ SpecialFunctions = "2.4" StaticArrays = "1.9" SteadyStateDiffEq = "2.2" StochasticDiffEq = "6.65" -StructuralIdentifiability = "0.5.7" -Symbolics = "5.28" +StructuralIdentifiability = "0.5.8" +Symbolics = "5.30.1" diff --git a/docs/src/faqs.md b/docs/src/faqs.md index d9faa38a73..45d69c9982 100644 --- a/docs/src/faqs.md +++ b/docs/src/faqs.md @@ -59,7 +59,7 @@ plot(sol; idxs = [A, B]) ``` ## How to disable rescaling of reaction rates in rate laws? -As explained in the [Reaction rate laws used in simulations](@ref) section, for +As explained in the [Reaction rate laws used in simulations](@ref introduction_to_catalyst_ratelaws) section, for a reaction such as `k, 2X --> 0`, the generated rate law will rescale the rate constant, giving `k*X^2/2` instead of `k*X^2` for ODEs and `k*X*(X-1)/2` instead of `k*X*(X-1)` for jumps. This can be disabled when directly `convert`ing a @@ -105,7 +105,7 @@ parametric_stoichiometry) section. ## How to set default values for initial conditions and parameters? How to set defaults when using the `@reaction_network` macro is described in -more detail [here](@ref dsl_description_defaults). There are several ways to do +more detail [here](@ref dsl_advanced_options_default_vals). There are several ways to do this. Using the DSL, one can use the `@species` and `@parameters` options: ```@example faq3 using Catalyst diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index 94b36738ca..5972a1b3e5 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -1,7 +1,7 @@ # [Introduction to Catalyst](@id introduction_to_catalyst) In this tutorial we provide an introduction to using Catalyst to specify chemical reaction networks, and then to solve ODE, jump, and SDE models -generated from them. At the end we show what mathematical rate laws and +generated from them[^1]. At the end we show what mathematical rate laws and transition rate functions (i.e. intensities or propensities) are generated by Catalyst for ODE, SDE and jump process models. @@ -151,8 +151,7 @@ underlying problem. variable-based parameter mappings, `u₀symmap` and `psymmap`, while when directly passing `repressilator` we could use either those or the `Symbol`-based mappings, `u₀map` and `pmap`. `Symbol`-based mappings can - always be converted to `symbolic` mappings using [`symmap_to_varmap`](@ref), - see the [Basic Syntax](@ref basic_examples) section for more details. + always be converted to `symbolic` mappings using [`symmap_to_varmap`](@ref). !!! note diff --git a/docs/src/inverse_problems/behaviour_optimisation.md b/docs/src/inverse_problems/behaviour_optimisation.md index c3b879d33d..40da2254df 100644 --- a/docs/src/inverse_problems/behaviour_optimisation.md +++ b/docs/src/inverse_problems/behaviour_optimisation.md @@ -1,5 +1,5 @@ # [Optimization for non-data fitting purposes](@id behaviour_optimisation) -In previous tutorials we have described how to use [PEtab.jl](@ref petab_parameter_fitting) and [Optimization.jl](@ref optimization_parameter_fitting) for parameter fitting. This involves solving an optimisation problem (to find the parameter set yielding the best model-to-data fit). There are, however, other situations that require solving optimisation problems. Typically, these involve the creation of a custom cost function, which optimum can then be found using Optimization.jl. In this tutorial we will describe this process, demonstrating how parameter space can be searched to find values that achieve a desired system behaviour. A more throughout description on how to solve these problems is provided by [Optimization.jl's documentation](https://docs.sciml.ai/Optimization/stable/) and the literature[^1]. +In previous tutorials we have described how to use PEtab.jl and [Optimization.jl](@ref optimization_parameter_fitting) for parameter fitting. This involves solving an optimisation problem (to find the parameter set yielding the best model-to-data fit). There are, however, other situations that require solving optimisation problems. Typically, these involve the creation of a custom cost function, which optimum can then be found using Optimization.jl. In this tutorial we will describe this process, demonstrating how parameter space can be searched to find values that achieve a desired system behaviour. A more throughout description on how to solve these problems is provided by [Optimization.jl's documentation](https://docs.sciml.ai/Optimization/stable/) and the literature[^1]. ## [Maximising the pulse amplitude of an incoherent feed forward loop](@id behaviour_optimisation_IFFL_example) Incoherent feedforward loops (network motifs where a single component both activates and deactivates a downstream component) are able to generate pulses in response to step inputs[^2]. In this tutorial we will consider such an incoherent feedforward loop, attempting to generate a system with as prominent a response pulse as possible. diff --git a/docs/src/inverse_problems/global_sensitivity_analysis.md b/docs/src/inverse_problems/global_sensitivity_analysis.md index 91019e2383..750d528578 100644 --- a/docs/src/inverse_problems/global_sensitivity_analysis.md +++ b/docs/src/inverse_problems/global_sensitivity_analysis.md @@ -1,6 +1,6 @@ # [Global Sensitivity Analysis](@id global_sensitivity_analysis) *Global sensitivity analysis* (GSA) is used to study the sensitivity of a function's outputs with respect to its input[^1]. Within the context of chemical reaction network modelling it is primarily used for two purposes: -- [When fitting a model's parameters to data](@ref petab_parameter_fitting), it can be applied to the cost function of the optimisation problem. Here, GSA helps determine which parameters do, and do not, affect the model's fit to the data. This can be used to identify parameters that are less relevant to the observed data. +- When fitting a model's parameters to data, it can be applied to the cost function of the optimisation problem. Here, GSA helps determine which parameters do, and do not, affect the model's fit to the data. This can be used to identify parameters that are less relevant to the observed data. - [When measuring some system behaviour or property](@ref behaviour_optimisation), it can help determine which parameters influence that property. E.g. for a model of a biofuel-producing circuit in a synthetic organism, GSA could determine which system parameters have the largest impact on the total rate of biofuel production. GSA can be carried out using the [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) package. This tutorial contains a brief introduction of how to use it for GSA on Catalyst models, with [GlobalSensitivity providing a more complete documentation](https://docs.sciml.ai/GlobalSensitivity/stable/). @@ -52,7 +52,7 @@ on the domain $10^β ∈ (-3.0,-1.0)$, $10^a ∈ (-2.0,0.0)$, $10^γ ∈ (-2.0,0 !!! note We should make a couple of notes about the example above: - - Here, we write our parameters on the forms $10^β$, $10^a$, and $10^γ$, which transforms them into log-space. As [previously described](@ref optimization_parameter_fitting_logarithmic_scale), this is advantageous in the context of inverse problems such as this one. + - Here, we write our parameters on the forms $10^β$, $10^a$, and $10^γ$, which transforms them into log-space. This is advantageous in the context of inverse problems such as this one. - For GSA, where a function is evaluated a large number of times, it is ideal to write it as performant as possible. Hence, we initially create a base `ODEProblem`, and then apply the [`remake`](@ref simulation_structure_interfacing_problems_remake) function to it in each evaluation of `peak_cases` to generate a problem which is solved for that specific parameter set. - Again, as [previously described in other inverse problem tutorials](@ref optimization_parameter_fitting_basics), when exploring a function over large parameter spaces, we will likely simulate our model for unsuitable parameter sets. To reduce time spent on these, and to avoid excessive warning messages, we provide the `maxiters = 100000` and `verbose = false` arguments to `solve`. - As we have encountered in [a few other cases](@ref optimization_parameter_fitting_basics), the `gsa` function is not able to take parameter inputs of the map form usually used for Catalyst. Hence, as a first step in `peak_cases` we convert the parameter vector to this form. Next, we remember that the order of the parameters when we e.g. evaluate the GSA output, or set the parameter bounds, corresponds to the order used in `ps = [:β => p[1], :a => p[2], :γ => p[3]]`. diff --git a/docs/src/inverse_problems/optimization_ode_param_fitting.md b/docs/src/inverse_problems/optimization_ode_param_fitting.md index 4b82c0da70..ae729d2fc2 100644 --- a/docs/src/inverse_problems/optimization_ode_param_fitting.md +++ b/docs/src/inverse_problems/optimization_ode_param_fitting.md @@ -129,13 +129,13 @@ If we from previous knowledge know that $kD = 0.1$, and only want to fit the val fixed_p_prob_generator(prob, p) = remake(prob; p = vcat(p[1], 0.1, p[2])) nothing # hide ``` -Here, it takes the `ODEProblem` (`prob`) we simulate, and the parameter set used, during the optimisation process (`p`), and creates a modified `ODEProblem` (by setting a customised parameter vector [using `remake`](@ref simulation_structure_interfacing_remake)). Now we create our modified loss function: +Here, it takes the `ODEProblem` (`prob`) we simulate, and the parameter set used, during the optimisation process (`p`), and creates a modified `ODEProblem` (by setting a customised parameter vector [using `remake`](@ref simulation_structure_interfacing_problems_remake)). Now we create our modified loss function: ```@example diffeq_param_estim_1 loss_function_fixed_kD = build_loss_objective(oprob, Tsit5(), L2Loss(data_ts, data_vals), Optimization.AutoForwardDiff(); prob_generator = fixed_p_prob_generator, maxiters=10000, verbose=false, save_idxs=4) nothing # hide ``` -We can create an `OptimizationProblem` from this one like previously, but keep in mind that it (and its output results) only contains two parameter values ($k$* and $kP$): +We can create an `OptimizationProblem` from this one like previously, but keep in mind that it (and its output results) only contains two parameter values ($k$ and $kP$): ```@example diffeq_param_estim_1 optprob_fixed_kD = OptimizationProblem(loss_function_fixed_kD, [1.0, 1.0]) optsol_fixed_kD = solve(optprob_fixed_kD, Optim.NelderMead()) @@ -160,7 +160,7 @@ nothing # hide corresponds to the same true parameter values as used previously (`[:kB => 1.0, :kD => 0.1, :kP => 0.5]`). ## [Parameter fitting to multiple experiments](@id optimization_parameter_fitting_multiple_experiments) -Say that we had measured our model for several different initial conditions, and would like to fit our model to all these measurements simultaneously. This can be done by first creating a [corresponding `EnsembleProblem`](@ref advanced_simulations_ensemble_problems). How to then create loss functions for these are described in more detail [here](https://docs.sciml.ai/DiffEqParamEstim/stable/tutorials/ensemble/). +Say that we had measured our model for several different initial conditions, and would like to fit our model to all these measurements simultaneously. This can be done by first creating a [corresponding `EnsembleProblem`](@ref ensemble_simulations). How to then create loss functions for these are described in more detail [here](https://docs.sciml.ai/DiffEqParamEstim/stable/tutorials/ensemble/). ## [Optimisation solver options](@id optimization_parameter_fitting_solver_options) Optimization.jl supports various [optimisation solver options](https://docs.sciml.ai/Optimization/stable/API/solve/) that can be supplied to the `solve` command. For example, to set a maximum number of seconds (after which the optimisation process is terminated), you can use the `maxtime` argument: diff --git a/docs/src/model_simulation/ode_simulation_performance.md b/docs/src/model_simulation/ode_simulation_performance.md index ae4d204bf1..9d6249d5f3 100644 --- a/docs/src/model_simulation/ode_simulation_performance.md +++ b/docs/src/model_simulation/ode_simulation_performance.md @@ -8,7 +8,7 @@ Generally, this short checklist provides a quick guide for dealing with ODE perf 1. If performance is not critical, use [the default solver choice](@ref ode_simulation_performance_solvers) and do not worry further about the issue. 2. If improved performance would be useful, read about solver selection (both in [this tutorial](@ref ode_simulation_performance_solvers) and [OrdinaryDiffEq's documentation](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/)) and then try a few different solvers to find one with good performance. 3. If you have a large ODE (approximately 100 variables or more), try the [various options for efficient Jacobian computation](@ref ode_simulation_performance_jacobian) (noting that some are non-trivial to use, and should only be investigated if truly required). -4. If you plan to simulate your ODE many times, try [parallelise it on CPUs or GPUs](@ref investigating) (with preference for the former, which is easier to use). +4. If you plan to simulate your ODE many times, try [parallelise it on CPUs or GPUs](@ref ode_simulation_performance_parallelisation) (with preference for the former, which is easier to use). ## [Regarding stiff and non-stiff problems and solvers](@id ode_simulation_performance_stiffness) Generally, ODE problems can be categorised into [*stiff ODEs* and *non-stiff ODEs*](https://en.wikipedia.org/wiki/Stiff_equation). This categorisation is important due to stiff ODEs requiring specialised solvers. A common cause of failure to simulate an ODE is the use of a non-stiff solver for a stiff problem. There is no exact way to determine whether a given ODE is stiff or not, however, systems with several different time scales (e.g. a CRN with both slow and fast reactions) typically generate stiff ODEs. diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index 54c84479b2..272f638d5c 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -275,7 +275,7 @@ nothing # hide ``` If the `@default_noise_scaling` option is used, that term is only applied to reactions *without* `noise_scaling` metadata. -While the `@default_noise_scaling` option is unavailable for [programmatically created models](@ref programmatic_CRN_construction), the [`remake_reactionsystem`](@ref) function can be used to achieve a similar effect. +While the `@default_noise_scaling` option is unavailable for [programmatically created models](@ref programmatic_CRN_construction), the [`remake_reactionsystem`](@ref simulation_structure_interfacing_problems_remake) function can be used to achieve a similar effect. ## [Performing jump simulations using stochastic chemical kinetics](@id simulation_intro_jumps) diff --git a/docs/src/model_simulation/simulation_plotting.md b/docs/src/model_simulation/simulation_plotting.md index 56471fafcc..f8c40d8684 100644 --- a/docs/src/model_simulation/simulation_plotting.md +++ b/docs/src/model_simulation/simulation_plotting.md @@ -58,7 +58,7 @@ can be used to plot `X` only. When only a single argument is given, the vector f plot(sol; idxs = brusselator.X + brusselator.Y) ``` -## [Multi-plot plots](@id simulation_plotting_options) +## [Multi-plot plots](@id simulation_plotting_options_subplots) It is possible to save plots in variables. These can then be used as input to the `plot` command. Here, the plot command can be used to create plots containing multiple plots (by providing multiple inputs). E.g. here we plot the concentration of `X` and `Y` in separate subplots: ```@example simulation_plotting plt_X = plot(sol; idxs = [:X]) @@ -71,7 +71,7 @@ When working with subplots, the [`layout`](https://docs.juliaplots.org/latest/la plot(plt_X, plt_Y; layout = (2,1), size = (700,500)) ``` -## [Saving plots](@id simulation_plotting_options) +## [Saving plots](@id simulation_plotting_options_saving) Once a plot has been saved to a variable, the `savefig` function can be used to save it to a file. Here we save our Brusselator plot simulation (the first argument) to a file called "saved_plot.png" (the second argument): ```@example simulation_plotting plt = plot(sol) @@ -80,7 +80,7 @@ rm("saved_plot.png") # hide ``` The plot file type is automatically determined from the extension (if none is given, a .png file is created). -## [Phase-space plots](@id simulation_plotting_options) +## [Phase-space plots](@id simulation_plotting_options_phasespace) By default, simulations are plotted as species concentrations over time. However, [phase space](https://en.wikipedia.org/wiki/Phase_space#:~:text=In%20dynamical%20systems%20theory%20and,point%20in%20the%20phase%20space.) plots are also possible. This is done by designating the axis arguments using the `idxs` option, but providing them as a tuple. E.g. here we plot our simulation in $X-Y$ space: ```@example simulation_plotting plot(sol; idxs = (:X, :Y)) diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index eae285f296..0b45057a41 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -162,7 +162,7 @@ get_S(oprob) ``` ## [Interfacing using symbolic representations](@id simulation_structure_interfacing_symbolic_representation) -When e.g. [programmatic modelling is used](@ref programmatic_CRN_construction), species and parameters can be represented as *symbolic variables*. These can be used to index a problem, just like symbol-based representations can. Here we create a simple [two-state model](@ref rbasic_CRN_library_two_statesef) programmatically, and use its symbolic variables to check, and update, an initial condition: +When e.g. [programmatic modelling is used](@ref programmatic_CRN_construction), species and parameters can be represented as *symbolic variables*. These can be used to index a problem, just like symbol-based representations can. Here we create a simple [two-state model](@ref basic_CRN_library_two_states) programmatically, and use its symbolic variables to check, and update, an initial condition: ```@example structure_indexing_symbolic_variables using Catalyst t = default_t() diff --git a/docs/src/steady_state_functionality/dynamical_systems.md b/docs/src/steady_state_functionality/dynamical_systems.md index 01b21fcc57..5b844e065d 100644 --- a/docs/src/steady_state_functionality/dynamical_systems.md +++ b/docs/src/steady_state_functionality/dynamical_systems.md @@ -4,7 +4,7 @@ The [DynamicalSystems.jl package](https://github.com/JuliaDynamics/DynamicalSyst ## [Finding basins of attraction](@id dynamical_systems_basins_of_attraction) Given enough time, an ODE will eventually reach a so-called [*attractor*](https://en.wikipedia.org/wiki/Attractor). For chemical reaction networks (CRNs), this will typically be either a *steady state* or a *limit cycle*. Since ODEs are deterministic, which attractor a simulation will reach is uniquely determined by the initial condition (assuming parameter values are fixed). Conversely, each attractor is associated with a set of initial conditions such that model simulations originating in these will tend to that attractor. These sets are called *basins of attraction*. Here, phase space (the space of all possible states of the system) can be divided into a number of basins of attraction equal to the number of attractors. -DynamicalSystems.jl provides a simple interface for finding an ODE's basins of attraction across any given subspace of phase space. In this example we will use the bistable [Wilhelm model](https://bmcsystbiol.biomedcentral.com/articles/10.1186/1752-0509-3-90) (which steady states we have previous [computed using homotopy continuation](@ref homotopy_continuation_basic_example)). As a first step, we create an `ODEProblem` corresponding to the model which basins of attraction we wish to compute. For this application, `u0` and `tspan` is unused, and their values are of little importance (the only exception is than `tspan`, for implementation reason, must provide a not too small interval, we recommend minimum `(0.0, 1.0)`). +DynamicalSystems.jl provides a simple interface for finding an ODE's basins of attraction across any given subspace of phase space. In this example we will use the bistable [Wilhelm model](https://bmcsystbiol.biomedcentral.com/articles/10.1186/1752-0509-3-90) (which steady states we have previous [computed using homotopy continuation](@ref homotopy_continuation)). As a first step, we create an `ODEProblem` corresponding to the model which basins of attraction we wish to compute. For this application, `u0` and `tspan` is unused, and their values are of little importance (the only exception is than `tspan`, for implementation reason, must provide a not too small interval, we recommend minimum `(0.0, 1.0)`). ```@example dynamical_systems_basins using Catalyst wilhelm_model = @reaction_network begin diff --git a/docs/src/steady_state_functionality/steady_state_stability_computation.md b/docs/src/steady_state_functionality/steady_state_stability_computation.md index 8e43dcfe28..b23a0b0d88 100644 --- a/docs/src/steady_state_functionality/steady_state_stability_computation.md +++ b/docs/src/steady_state_functionality/steady_state_stability_computation.md @@ -1,5 +1,5 @@ # Steady state stability computation -After system steady states have been found using [HomotopyContinuation.jl](@ref homotopy_continuation), [NonlinearSolve.jl](@ref nonlinear_solve), or other means, their stability can be computed using Catalyst's `steady_state_stability` function. Systems with conservation laws will automatically have these removed, permitting stability computation on systems with singular Jacobian. +After system steady states have been found using [HomotopyContinuation.jl](@ref homotopy_continuation), [NonlinearSolve.jl](@ref steady_state_solving), or other means, their stability can be computed using Catalyst's `steady_state_stability` function. Systems with conservation laws will automatically have these removed, permitting stability computation on systems with singular Jacobian. !!! warn Catalyst currently computes steady state stabilities using the naive approach of checking whether a system's largest eigenvalue real part is negative. While more advanced stability computation methods exist (and would be a welcome addition to Catalyst), there is no direct plans to implement these. Furthermore, Catalyst uses a tolerance `tol = 10*sqrt(eps())` to determine whether a computed eigenvalue is far away enough from 0 to be reliably used. This threshold can be changed through the `tol` keyword argument. diff --git a/docs/src/inverse_problems/petab_ode_param_fitting.md b/docs/unpublished/petab_ode_param_fitting.md similarity index 100% rename from docs/src/inverse_problems/petab_ode_param_fitting.md rename to docs/unpublished/petab_ode_param_fitting.md From 839c5ae7d1f86ff5415d508067b06fe8fa00f402 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 16:36:59 -0400 Subject: [PATCH 265/446] Stop failing doc builds for codecov token error Codecov is giving a lot of token errors these days, which cause build failures. Let's disable this for now. --- .github/workflows/Documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 7e9af3e1dd..a743dd3cc8 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -28,4 +28,4 @@ jobs: with: file: lcov.info token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: true + fail_ci_if_error: false From 151d00f801c8ba562ab6fd6d3c18df7e5bbac632 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 16:37:41 -0400 Subject: [PATCH 266/446] stop failing when codecov is broken Codecov keeps giving token errors / restrictions on calls that cause test failures. Let's disable test failures from this. --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 74708a6e41..e3f54adf5b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -37,7 +37,7 @@ jobs: with: file: lcov.info token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: true + fail_ci_if_error: false - uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} From 7db671485aaabbaf14a475cd83dec562387a19e2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 10 Jun 2024 16:38:29 -0400 Subject: [PATCH 267/446] init --- .../introduction_to_catalyst.md | 2 +- .../model_creation/compositional_modeling.md | 2 +- .../model_creation/constraint_equations.md | 2 +- docs/src/model_creation/dsl_advanced.md | 74 +++++++++++++++++++ 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index bcbd7877be..bf63cd4e60 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -1,7 +1,7 @@ # [Introduction to Catalyst](@id introduction_to_catalyst) In this tutorial we provide an introduction to using Catalyst to specify chemical reaction networks, and then to solve ODE, jump, and SDE models -generated from them[^1]. At the end we show what mathematical rate laws and +generated from them[1]. At the end we show what mathematical rate laws and transition rate functions (i.e. intensities or propensities) are generated by Catalyst for ODE, SDE and jump process models. diff --git a/docs/src/model_creation/compositional_modeling.md b/docs/src/model_creation/compositional_modeling.md index 82e9bfc9d3..1f0bfac0cd 100644 --- a/docs/src/model_creation/compositional_modeling.md +++ b/docs/src/model_creation/compositional_modeling.md @@ -139,7 +139,7 @@ nothing # hide Here we assume the user will pass in the repressor species as a ModelingToolkit variable, and specify a name for the network. We use Catalyst's interpolation ability to substitute the value of these variables into the DSL (see -[Interpolation of Julia Variables](@ref dsl_description_interpolation_of_variables)). To make the repressilator we now make +[Interpolation of Julia Variables](@ref dsl_advanced_options_symbolics_and_DSL_interpolation)). To make the repressilator we now make three genes, and then compose them together ```@example ex1 t = default_t() diff --git a/docs/src/model_creation/constraint_equations.md b/docs/src/model_creation/constraint_equations.md index b8a08769cc..b8468dc62a 100644 --- a/docs/src/model_creation/constraint_equations.md +++ b/docs/src/model_creation/constraint_equations.md @@ -52,7 +52,7 @@ end Notice, here we interpolated the variable `V` with `$V` to ensure we use the same symbolic unknown variable in the `rn` as we used in building `osys`. See the doc section on [interpolation of variables](@ref -dsl_description_interpolation_of_variables) for more information. +dsl_advanced_options_symbolics_and_DSL_interpolation) for more information. We can now merge the two systems into one complete `ReactionSystem` model using [`ModelingToolkit.extend`](@ref): diff --git a/docs/src/model_creation/dsl_advanced.md b/docs/src/model_creation/dsl_advanced.md index a7dfc694c6..ebfa8fadde 100644 --- a/docs/src/model_creation/dsl_advanced.md +++ b/docs/src/model_creation/dsl_advanced.md @@ -463,3 +463,77 @@ A reaction's metadata can be accessed using specific functions, e.g. `Catalyst.h rx = @reaction p, 0 --> X, [description="A production reaction"] Catalyst.getdescription(rx) ``` + +## [Working with symbolic variables and the DSL](@id dsl_advanced_options_symbolics_and_DSL) +We have previously described how Catalyst represents its models symbolically (enabling e.g. symbolic differentiation of expressions stored in models). While Catalyst utilises this for many internal operation, these symbolic representations can also be accessed and harnessed by the user. Primarily, doing so is much easier during programmatic (as opposed to DSL-based) modelling. Indeed, the section on [programmatic modelling](@ref programmatic_CRN_construction) goes into more details about symbolic representation in models, and how these can be used. It is, however, also ways to utilise these methods during DSL-based modelling. Below we briefly describe two methods for doing so. + +### [Using `@unpack` to extract symbolic variables from `ReactionSystem`s](@id dsl_advanced_options_symbolics_and_DSL_unpack) +Let us consider a simple [birth-death process](@ref basic_CRN_library_bd) created using the DSL: +```@example dsl_advanced_programmatic_unpack +using Catalyst # hide +bd_model = @reaction_network begin + (p,d), 0 <--> X +end +nothing # hide +``` +Since we have not explicitly declared `p`, `d`, and `X` using `@parameters` and `@species`, we cannot represent these symbolically (only using `Symbol`s). If we wish to do so, however, we can fetch these into our current scope using the `@unpack` macro: +```@example dsl_advanced_programmatic_unpack +@unpack p, d, X = bd_model +nothing # hide +``` +This lists first the quantities we wish to fetch (does not need to be the model's full set of parameters and species), then `=`, followed by the model variable. `p`, `d` and `X` are now symbolic variables in the current scope, just as if they had been declared using `@parameters` or `@species`. We can confirm this: +```@example dsl_advanced_programmatic_unpack +X +``` +Next, we can now use these to e.g. designate initial conditions and parameter values for model simulations: +```@example dsl_advanced_programmatic_unpack +using OrdinaryDiffEq, Plots # hide +u0 = [X => 0.1] +tspan = (0.0, 10.0) +ps = [p => 1.0, d => 0.2] +oprob = ODEProblem(bd_model, u0, tspan, ps) +sol = solve(oprob) +plot(sol) +``` + +!!! warn + Just like when using `@parameters` and `@species`, `@unpack` will overwrite any variables in the current scope which share name with the imported quantities. + +### [Interpolating variables into the DSL](@id dsl_advanced_options_symbolics_and_DSL_interpolation) +Catalyst's DSL allows Julia variables to be interpolated for the network name, within rate constant expressions, or for species/stoichiometries within reactions. Using the lower-level symbolic interface we can then define symbolic variables and parameters outside of `@reaction_network`, which can then be used within expressions in the DSL. + +Interpolation is carried out by pre-appending the interpolating variable with a `$`. For example, here we declare the parameters and species of a birth-death model, and interpolate these into the model: +```@example dsl_advanced_programmatic_interpolation +using Catalyst # hide +t = default_t() +@species X(t) +@parameters p d +bd_model = @reaction_network begin + ($p, $d), 0 <--> $X +end +``` +Additional information (such as default values or metadata) supplied to `p`, `d`, and `X` is carried through to the DSL. However, interpolation for this purpose is of limited value, as such information [can be declared within the DSL](@ref dsl_advanced_options_declaring_species_and_parameters). However, it is possible to interpolate larger algebraic expressions into the DSL, e.g. here +```@example dsl_advanced_programmatic_interpolation +@species X1(t) X2(t) X3(t) E(t) +@parameters d +d_rate = d/(1 + E) +degradation_model = @reaction_network begin + $d_rate, X1 --> 0 + $d_rate, X2 --> 0 + $d_rate, X3 --> 0 +end +``` +we declare an expression `d_rate`, which then can be inserted into the DSL via interpolation. + +It is also possible to use interpolation in combination with the `@reaction` macro. E.g. the reactions of the above network can be declared individually using +```@example dsl_advanced_programmatic_interpolation +rxs = [ + @reaction $d_rate, $X1 --> 0 + @reaction $d_rate, $X2 --> 0 + @reaction $d_rate, $X3 --> 0 +] +nothing # hide +``` + +!!! note + When using interpolation, expressions like `2$spec` won't work; the multiplication symbol must be explicitly included like `2*$spec`. \ No newline at end of file From 3178724fcccc9c5bf3e911a66d64c3d61a2b511f Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 16:39:57 -0400 Subject: [PATCH 268/446] fix Catalyst using warning --- .../lattice_jump_systems.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 099484cb13..fae7bb9e2d 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -17,8 +17,8 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, # Converts u0 and p to their internal forms. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) - # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. - # These elements are length 1 vectors (if the parameter is uniform), + # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. + # These elements are length 1 vectors (if the parameter is uniform), # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) @@ -40,7 +40,7 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator # Computes hopping constants and mass action jumps (requires some internal juggling). # Currently, JumpProcesses requires uniform vertex parameters (hence `p=first.(dprob.p[1])`). # Currently, the resulting JumpProblem does not depend on parameters (no way to incorporate these). - # Hence the parameters of this one does nto actually matter. If at some point JumpProcess can + # Hence the parameters of this one does nto actually matter. If at some point JumpProcess can # handle parameters this can be updated and improved. # The non-spatial DiscreteProblem have a u0 matrix with entries for all combinations of species and vertexes. hopping_constants = make_hopping_constants(dprob, lrs) @@ -68,7 +68,7 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst # For each edge, finds each position in `hopping_constants`. for (e_idx, e) in enumerate(edges(lrs.lattice)) dst_idx = findfirst(isequal(e.dst), lrs.lattice.fadjlist[e.src]) - # For each species, sets that hopping rate. + # For each species, sets that hopping rate. for s_idx in 1:(lrs.num_species) hopping_constants[s_idx, e.src][dst_idx] = get_component_value( all_diff_rates[s_idx], e_idx) @@ -124,7 +124,7 @@ end ### Extra ### # Temporary. Awaiting implementation in SII, or proper implementation withinCatalyst (with more general functionality). -function int_map(map_in, sys) where {T, S} +function int_map(map_in, sys) return [ModelingToolkit.variable_index(sys, pair[1]) => pair[2] for pair in map_in] end @@ -136,9 +136,9 @@ end # statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(states(rs))) # eqs = equations(js) # invttype = non_spat_dprob.tspan[1] === nothing ? Float64 : typeof(1 / non_spat_dprob.tspan[2]) -# +# # # Assembles the non-spatial mass action jumps. -# p = (non_spat_dprob.p isa DiffEqBase.NullParameters || non_spat_dprob.p === nothing) ? Num[] : non_spat_dprob.p +# p = (non_spat_dprob.p isa DiffEqBase.NullParameters || non_spat_dprob.p === nothing) ? Num[] : non_spat_dprob.p # majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) # return ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) # end From 591ff99e5d8224af10bd025173c37877819bedf5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 10 Jun 2024 16:49:19 -0400 Subject: [PATCH 269/446] misc updates --- docs/src/api.md | 1 + docs/src/inverse_problems/global_sensitivity_analysis.md | 2 +- docs/src/model_simulation/simulation_introduction.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/api.md b/docs/src/api.md index a0f386f3a2..f34cbfa8e3 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -272,6 +272,7 @@ hillar ```@docs Base.convert ModelingToolkit.structural_simplify +set_default_noise_scaling ``` ## Chemistry-related functionalities diff --git a/docs/src/inverse_problems/global_sensitivity_analysis.md b/docs/src/inverse_problems/global_sensitivity_analysis.md index 750d528578..cd65631c2f 100644 --- a/docs/src/inverse_problems/global_sensitivity_analysis.md +++ b/docs/src/inverse_problems/global_sensitivity_analysis.md @@ -52,7 +52,7 @@ on the domain $10^β ∈ (-3.0,-1.0)$, $10^a ∈ (-2.0,0.0)$, $10^γ ∈ (-2.0,0 !!! note We should make a couple of notes about the example above: - - Here, we write our parameters on the forms $10^β$, $10^a$, and $10^γ$, which transforms them into log-space. This is advantageous in the context of inverse problems such as this one. + - Here, we write our parameters on the forms $10^β$, $10^a$, and $10^γ$, which transforms them into log-space. As [previously described](@ref optimization_parameter_fitting_log_scale), this is advantageous in the context of inverse problems such as this one. - For GSA, where a function is evaluated a large number of times, it is ideal to write it as performant as possible. Hence, we initially create a base `ODEProblem`, and then apply the [`remake`](@ref simulation_structure_interfacing_problems_remake) function to it in each evaluation of `peak_cases` to generate a problem which is solved for that specific parameter set. - Again, as [previously described in other inverse problem tutorials](@ref optimization_parameter_fitting_basics), when exploring a function over large parameter spaces, we will likely simulate our model for unsuitable parameter sets. To reduce time spent on these, and to avoid excessive warning messages, we provide the `maxiters = 100000` and `verbose = false` arguments to `solve`. - As we have encountered in [a few other cases](@ref optimization_parameter_fitting_basics), the `gsa` function is not able to take parameter inputs of the map form usually used for Catalyst. Hence, as a first step in `peak_cases` we convert the parameter vector to this form. Next, we remember that the order of the parameters when we e.g. evaluate the GSA output, or set the parameter bounds, corresponds to the order used in `ps = [:β => p[1], :a => p[2], :γ => p[3]]`. diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index 272f638d5c..bf5fc7158e 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -275,7 +275,7 @@ nothing # hide ``` If the `@default_noise_scaling` option is used, that term is only applied to reactions *without* `noise_scaling` metadata. -While the `@default_noise_scaling` option is unavailable for [programmatically created models](@ref programmatic_CRN_construction), the [`remake_reactionsystem`](@ref simulation_structure_interfacing_problems_remake) function can be used to achieve a similar effect. +While the `@default_noise_scaling` option is unavailable for [programmatically created models](@ref programmatic_CRN_construction), the `set_default_noise_scaling` function can be used to achieve a similar effect. ## [Performing jump simulations using stochastic chemical kinetics](@id simulation_intro_jumps) From ca63c4378e903fce65b09ded71b1e8f24f1ac32a Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 16:54:23 -0400 Subject: [PATCH 270/446] mv SS tests that uses extensions to extension section --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index ca6c8a918f..2c16dd4575 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,7 +31,6 @@ using SafeTestsets, Test # Tests various miscellaneous features. @time @safetestset "API" begin include("miscellaneous_tests/api.jl") end @time @safetestset "Units" begin include("miscellaneous_tests/units.jl") end - @time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end @time @safetestset "Compound Species" begin include("miscellaneous_tests/compound_macro.jl") end @time @safetestset "Reaction Balancing" begin include("miscellaneous_tests/reaction_balancing.jl") end @time @safetestset "ReactionSystem Serialisation" begin include("miscellaneous_tests/reactionsystem_serialisation.jl") end @@ -66,6 +65,7 @@ using SafeTestsets, Test # Tests extensions. @time @safetestset "BifurcationKit Extension" begin include("extensions/bifurcation_kit.jl") end + @time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end @time @safetestset "HomotopyContinuation Extension" begin include("extensions/homotopy_continuation.jl") end @time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end From 840a188b676ae62760033a26042ab4059e4240e6 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 17:09:59 -0400 Subject: [PATCH 271/446] rearrange stablity test --- test/runtests.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 2c16dd4575..bf6be2bbfd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -65,8 +65,10 @@ using SafeTestsets, Test # Tests extensions. @time @safetestset "BifurcationKit Extension" begin include("extensions/bifurcation_kit.jl") end - @time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end @time @safetestset "HomotopyContinuation Extension" begin include("extensions/homotopy_continuation.jl") end @time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end + # test stability (uses HomotopyContinuation extension) + @time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end + end # @time From 389b4ca8a766b8cc51e503830c9be605d27699de Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Mon, 10 Jun 2024 18:48:47 -0400 Subject: [PATCH 272/446] Update Invalidations.yml --- .github/workflows/Invalidations.yml | 33 +++-------------------------- 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/.github/workflows/Invalidations.yml b/.github/workflows/Invalidations.yml index 66c86a3627..0a6a27a88c 100644 --- a/.github/workflows/Invalidations.yml +++ b/.github/workflows/Invalidations.yml @@ -4,37 +4,10 @@ on: pull_request: concurrency: - # Skip intermediate builds: always. - # Cancel intermediate builds: always. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - evaluate: - # Only run on PRs to the default branch. - # In the PR trigger above branches can be specified only explicitly whereas this check should work for master, main, or any other default branch - if: github.base_ref == github.event.repository.default_branch - runs-on: ubuntu-latest - steps: - - uses: julia-actions/setup-julia@v2 - with: - version: '1' - - uses: actions/checkout@v4 - - uses: julia-actions/julia-buildpkg@v1 - - uses: julia-actions/julia-invalidations@v1 - id: invs_pr - - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - - uses: julia-actions/julia-buildpkg@v1 - - uses: julia-actions/julia-invalidations@v1 - id: invs_default - - - name: Report invalidation counts - run: | - echo "Invalidations on default branch: ${{ steps.invs_default.outputs.total }} (${{ steps.invs_default.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY - echo "This branch: ${{ steps.invs_pr.outputs.total }} (${{ steps.invs_pr.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY - - name: Check if the PR does increase number of invalidations - if: steps.invs_pr.outputs.total > steps.invs_default.outputs.total - run: exit 1 + evaluate-invalidations: + name: "Evaluate Invalidations" + uses: "SciML/.github/.github/workflows/invalidations.yml@v1" From 5fbb58ff5059659928024591e4dfd074cbc96e8a Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 10 Jun 2024 19:07:26 -0400 Subject: [PATCH 273/446] up --- docs/make.jl | 2 +- docs/src/inverse_problems/behaviour_optimisation.md | 2 +- docs/src/model_creation/dsl_advanced.md | 2 +- docs/src/model_creation/dsl_basics.md | 4 ++-- docs/src/model_creation/model_visualisation.md | 1 + docs/src/model_simulation/simulation_structure_interfacing.md | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 32df32290d..8595cd6ffa 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -41,7 +41,7 @@ makedocs(sitename = "Catalyst.jl", clean = true, pages = pages, pagesonly = true, - warnonly = true) + warnonly = [:missing_docs]) deploydocs(repo = "github.com/SciML/Catalyst.jl.git"; push_preview = true) diff --git a/docs/src/inverse_problems/behaviour_optimisation.md b/docs/src/inverse_problems/behaviour_optimisation.md index 40da2254df..2d4c0cca4e 100644 --- a/docs/src/inverse_problems/behaviour_optimisation.md +++ b/docs/src/inverse_problems/behaviour_optimisation.md @@ -38,7 +38,7 @@ function pulse_amplitude(p, _) SciMLBase.successful_retcode(sol) || return Inf return -(maximum(sol[:Z]) - sol[:Z][1]) end -nothing # here +nothing # hide ``` This cost function takes two arguments (a parameter value `p`, and an additional one which we will ignore here but discuss later). It first calculates the new initial steady state concentration for the given parameter set. Next, it creates an updated `ODEProblem` using the steady state as initial conditions and the, to the cost function provided, input parameter set. While we could create a new `ODEProblem` within the cost function, cost functions are often called a large number of times during the optimisation process (making performance important). Here, using [`remake` on a previously created `ODEProblem`](@ref simulation_structure_interfacing_problems_remake) is more performant than creating a new one. Just like [when using Optimization.jl to fit parameters to data](@ref optimization_parameter_fitting), we use the `verbose = false` option to prevent unnecessary simulation printouts, and a reduced `maxiters` value to reduce time spent simulating (for the model) unsuitable parameter sets. We also use `SciMLBase.successful_retcode(sol)` to check whether the simulation return code indicates a successful simulation (and if it did not, returns a large cost function value). Finally, Optimization.jl finds the function's *minimum value*, so to find the *maximum* relative pulse amplitude, we make our cost function return the negative pulse amplitude. diff --git a/docs/src/model_creation/dsl_advanced.md b/docs/src/model_creation/dsl_advanced.md index ebfa8fadde..3e42269b28 100644 --- a/docs/src/model_creation/dsl_advanced.md +++ b/docs/src/model_creation/dsl_advanced.md @@ -363,7 +363,7 @@ end nothing # hide ``` !!! note - If only a single observable is declared, the `begin .. end` block is not required and the observable can be declared directly after the `@observables` option. + If only a single observable is declared, the `begin ... end` block is not required and the observable can be declared directly after the `@observables` option. [Metadata](@ref dsl_advanced_options_species_and_parameters_metadata) can be supplied to an observable directly after its declaration (but before its formula). If so, the metadata must be separated from the observable with a `,`, and the observable plus the metadata encapsulated by `()`. E.g. to add a [description metadata](@ref dsl_advanced_options_species_and_parameters_metadata) to our observable we can use ```@example dsl_advanced_observables diff --git a/docs/src/model_creation/dsl_basics.md b/docs/src/model_creation/dsl_basics.md index b3a3d2bea3..a091f49453 100644 --- a/docs/src/model_creation/dsl_basics.md +++ b/docs/src/model_creation/dsl_basics.md @@ -201,7 +201,7 @@ end Here, `P`'s production rate will be reduced as `A` decays. We can [print the ODE this model produces with `Latexify`](@ref visualisation_latex): ```@example dsl_basics using Latexify -latexify(rn_13; form=:ode) +latexify(rn_13; form = :ode) ``` In this case, we can generate an equivalent model by instead adding `A` as both a substrate and a product to `P`'s production reaction: @@ -213,7 +213,7 @@ end ``` We can confirm that this generates the same ODE: ```@example dsl_basics -latexify(rn_13_alt; form=:ode) +latexify(rn_13_alt; form = :ode) ``` Here, while these models will generate identical ODE, SDE, and jump simulations, the chemical reaction network models themselves are not equivalent. Generally, as pointed out in the two notes below, using the second form is preferable. !!! warn diff --git a/docs/src/model_creation/model_visualisation.md b/docs/src/model_creation/model_visualisation.md index 7bf2a735c2..96768ecae0 100644 --- a/docs/src/model_creation/model_visualisation.md +++ b/docs/src/model_creation/model_visualisation.md @@ -18,6 +18,7 @@ To display its reaction (using LaTeX formatting) we run `latexify` with our mode ```@example visualisation_latex using Latexify latexify(brusselator) +brusselator # hide ``` Here, we note that the output of `latexify(brusselator)` is identical to how a model is displayed by default. Indeed, the reason is that Catalyst internally uses Latexify's `latexify` function to display its models. It is also possible to display the ODE equations a model would generate by adding the `form = :ode` argument: ```@example visualisation_latex diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index 0b45057a41..d8c9463234 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -101,7 +101,7 @@ integrator.ps[:k₂] ``` Note that here, species-interfacing yields (or changes) a simulation's current value for a species, not its initial condition. -If you are interfacing with jump simulation integrators, please read this, highly relevant, section +If you are interfacing with jump simulation integrators, you must always call `reset_aggregated_jumps!(integrator)` afterwards. ## [Interfacing solution objects](@id simulation_structure_interfacing_solutions) From 55dce8b3a751cad715077ab4e688b975ef8c4732 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 10 Jun 2024 20:06:53 -0400 Subject: [PATCH 274/446] change internals, handle events --- src/registered_functions.jl | 67 ++++++++++++++++--- .../custom_crn_functions.jl | 38 +++++++++++ 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/src/registered_functions.jl b/src/registered_functions.jl index af7b227432..4821861a6b 100644 --- a/src/registered_functions.jl +++ b/src/registered_functions.jl @@ -116,8 +116,23 @@ expand_registered_functions(expr) Takes an expression, and expands registered function expressions. E.g. `mm(X,v,K)` is replaced with v*X/(X+K). Currently supported functions: `mm`, `mmr`, `hill`, `hillr`, and `hill`. """ -function expand_registered_functions!(expr) - iscall(expr) || return expr +function expand_registered_functions(expr) + if hasnode(is_catalyst_function, expr) + expr = replacenode(expr, expand_catalyst_function) + end + return expr +end + +# Checks whether an expression corresponds to a catalyst function call (e.g. `mm(X,v,K)`). +function is_catalyst_function(expr) + iscall(expr) || (return false) + return operation(expr) in [Catalyst.mm, Catalyst.mmr, Catalyst.hill, Catalyst.hillr, Catalyst.hillar] +end + +# If the input expression corresponds to a catalyst function call (e.g. `mm(X,v,K)`), returns +# it in its expanded form. If not, returns the input expression. +function expand_catalyst_function(expr) + is_catalyst_function(expr) || (return expr) args = arguments(expr) if operation(expr) == Catalyst.mm return args[2] * args[1] / (args[1] + args[3]) @@ -131,23 +146,55 @@ function expand_registered_functions!(expr) return args[3] * (args[1]^args[5]) / ((args[1])^args[5] + (args[2])^args[5] + (args[4])^args[5]) end - for i in 1:length(args) - args[i] = expand_registered_functions!(args[i]) - end - return expr end + # If applied to a Reaction, return a reaction with its rate modified. function expand_registered_functions(rx::Reaction) - Reaction(expand_registered_functions!(deepcopy(rx.rate)), rx.substrates, rx.products, + Reaction(expand_registered_functions(rx.rate), rx.substrates, rx.products, rx.substoich, rx.prodstoich, rx.netstoich, rx.only_use_rate, rx.metadata) end + # If applied to a Equation, returns it with it applied to lhs and rhs function expand_registered_functions(eq::Equation) - return expand_registered_functions!(deepcopy(eq.lhs)) ~ expand_registered_functions!(deepcopy(eq.rhs)) + return expand_registered_functions(eq.lhs) ~ expand_registered_functions(eq.rhs) end + +# If applied to a continuous event, returns it applied to eqs and affect. +function expand_registered_functions(ce::ModelingToolkit.SymbolicContinuousCallback) + eqs = expand_registered_functions(ce.eqs) + affect = expand_registered_functions(ce.affect) + return ModelingToolkit.SymbolicContinuousCallback(eqs, affect) +end + +# If applied to a discrete event, returns it applied to condition and affects. +function expand_registered_functions(de::ModelingToolkit.SymbolicDiscreteCallback) + condition = expand_registered_functions(de.condition) + affects = expand_registered_functions(de.affects) + return ModelingToolkit.SymbolicDiscreteCallback(condition, affects) +end + +# If applied to a vector, applies it to every element in the vector +function expand_registered_functions(vec::Vector) + return [Catalyst.expand_registered_functions(element) for element in vec] +end + # If applied to a ReactionSystem, applied function to all Reactions and other Equations, and return updated system. +# Currently, `ModelingToolkit.has_X_events` returns `true` even if event vector is empty (hence +# this function cannot be used). function expand_registered_functions(rs::ReactionSystem) - @set! rs.eqs = [Catalyst.expand_registered_functions(eq) for eq in get_eqs(rs)] - @set! rs.rxs = [Catalyst.expand_registered_functions(rx) for rx in get_rxs(rs)] + if isdefined(Main, :Infiltrator) + Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__) + end + + @set! rs.eqs = Catalyst.expand_registered_functions(get_eqs(rs)) + @set! rs.rxs = Catalyst.expand_registered_functions(get_rxs(rs)) + if !isempty(ModelingToolkit.get_continuous_events(rs)) + @set! rs.continuous_events = + Catalyst.expand_registered_functions(ModelingToolkit.get_continuous_events(rs)) + end + if !isempty(ModelingToolkit.get_discrete_events(rs)) + @set! rs.discrete_events = + Catalyst.expand_registered_functions(ModelingToolkit.get_discrete_events(rs)) + end return rs end diff --git a/test/reactionsystem_core/custom_crn_functions.jl b/test/reactionsystem_core/custom_crn_functions.jl index 6a62e6bb99..5df4ee5b83 100644 --- a/test/reactionsystem_core/custom_crn_functions.jl +++ b/test/reactionsystem_core/custom_crn_functions.jl @@ -2,6 +2,7 @@ # Fetch packages. using Catalyst, Test +using ModelingToolkit: get_continuous_events, get_discrete_events using Symbolics: derivative # Sets stable rng number. @@ -174,4 +175,41 @@ let @test isequal(only(Catalyst.get_rxs(rs_expanded_funcs)).rate, v*X/(X + K)) @test isequal(last(Catalyst.get_eqs(rs)).lhs, Catalyst.mm(V,v,K)) @test isequal(last(Catalyst.get_eqs(rs_expanded_funcs)).lhs, v*V/(V + K)) +end + +# Tests on model with events. +let + # Creates a model, saves it, and creates an expanded version. + rs = @reaction_network begin + @continuous_events begin + [mm(X,v,K) ~ 1.0] => [X ~ X] + end + @discrete_events begin + [1.0] => [X ~ mmr(X,v,K) + Y*(v + K)] + 1.0 => [X ~ X] + (hill(X,v,K,n) > 1000.0) => [X ~ hillr(X,v,K,n) + 2] + end + v0 + hillar(X,Y,v,K,n), X --> Y + end + rs_saved = deepcopy(rs) + rs_expanded = Catalyst.expand_registered_functions(rs) + + # Checks that the original model is unchanged (equality currently does not consider events). + @test rs == rs_saved + @test get_continuous_events(rs) == get_continuous_events(rs_saved) + @test get_discrete_events(rs) == get_discrete_events(rs_saved) + + # Checks that the new system is expanded. + @unpack v0, X, Y, v, K, n = rs + continuous_events = [ + [v*X/(X + K) ~ 1.0] => [X ~ X] + ] + discrete_events = [ + [1.0] => [X ~ v*K/(X + K) + Y*(v + K)] + 1.0 => [X ~ X] + (v * (X^n) / (X^n + K^n) > 1000.0) => [X ~ v * (K^n) / (X^n + K^n) + 2] + ] + @test isequal(only(Catalyst.get_rxs(rs_expanded)).rate, v0 + v * (X^n) / (X^n + Y^n + K^n)) + @test isequal(get_continuous_events(rs_expanded).eqs, [v*X/(K + X) ~ 1.0]) + @test isequal(get_continuous_events(rs_expanded).eqs, [v*X/(K + X) ~ 1.0]) end \ No newline at end of file From 3c0c9aa20be9e8d1c29cf5fe8f63b5188fa93c2d Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 10 Jun 2024 20:10:53 -0400 Subject: [PATCH 275/446] change (@ref) ref that is not working --- docs/src/faqs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/faqs.md b/docs/src/faqs.md index 45d69c9982..23c0dd7617 100644 --- a/docs/src/faqs.md +++ b/docs/src/faqs.md @@ -96,7 +96,7 @@ Note, when using `convert(ODESystem, mixedsys; combinatoric_ratelaws=false)` the calling `ODEProblem(mixedsys,...; combinatoric_ratelaws=false)`. As described above, this disables Catalyst's standard rescaling of reaction rates when generating reaction rate laws, see also the [Reaction rate laws used in -simulations](@ref) section. Leaving this keyword out for systems with floating +simulations](@ref introduction_to_catalyst_ratelaws) section. Leaving this keyword out for systems with floating point stoichiometry will give an error message. For a more extensive documentation of using non-integer stoichiometric From 2b904736dea01b2a7f4f7adbfab7a1252ce09fba Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 10 Jun 2024 20:12:39 -0400 Subject: [PATCH 276/446] formating --- src/registered_functions.jl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/registered_functions.jl b/src/registered_functions.jl index 4821861a6b..47458a9bce 100644 --- a/src/registered_functions.jl +++ b/src/registered_functions.jl @@ -116,7 +116,7 @@ expand_registered_functions(expr) Takes an expression, and expands registered function expressions. E.g. `mm(X,v,K)` is replaced with v*X/(X+K). Currently supported functions: `mm`, `mmr`, `hill`, `hillr`, and `hill`. """ -function expand_registered_functions(expr) +function expand_registered_functions(expr) if hasnode(is_catalyst_function, expr) expr = replacenode(expr, expand_catalyst_function) end @@ -126,7 +126,8 @@ end # Checks whether an expression corresponds to a catalyst function call (e.g. `mm(X,v,K)`). function is_catalyst_function(expr) iscall(expr) || (return false) - return operation(expr) in [Catalyst.mm, Catalyst.mmr, Catalyst.hill, Catalyst.hillr, Catalyst.hillar] + return operation(expr) in [ + Catalyst.mm, Catalyst.mmr, Catalyst.hill, Catalyst.hillr, Catalyst.hillar] end # If the input expression corresponds to a catalyst function call (e.g. `mm(X,v,K)`), returns @@ -184,17 +185,15 @@ end function expand_registered_functions(rs::ReactionSystem) if isdefined(Main, :Infiltrator) Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__) - end + end @set! rs.eqs = Catalyst.expand_registered_functions(get_eqs(rs)) @set! rs.rxs = Catalyst.expand_registered_functions(get_rxs(rs)) if !isempty(ModelingToolkit.get_continuous_events(rs)) - @set! rs.continuous_events = - Catalyst.expand_registered_functions(ModelingToolkit.get_continuous_events(rs)) + @set! rs.continuous_events = Catalyst.expand_registered_functions(ModelingToolkit.get_continuous_events(rs)) end if !isempty(ModelingToolkit.get_discrete_events(rs)) - @set! rs.discrete_events = - Catalyst.expand_registered_functions(ModelingToolkit.get_discrete_events(rs)) + @set! rs.discrete_events = Catalyst.expand_registered_functions(ModelingToolkit.get_discrete_events(rs)) end return rs end From bd86212cb056b2013b47d23f7c13895b4a34c895 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Tue, 11 Jun 2024 03:05:11 +0200 Subject: [PATCH 277/446] Update custom_crn_functions.jl --- test/reactionsystem_core/custom_crn_functions.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/reactionsystem_core/custom_crn_functions.jl b/test/reactionsystem_core/custom_crn_functions.jl index 5df4ee5b83..eea1447e2a 100644 --- a/test/reactionsystem_core/custom_crn_functions.jl +++ b/test/reactionsystem_core/custom_crn_functions.jl @@ -210,6 +210,6 @@ let (v * (X^n) / (X^n + K^n) > 1000.0) => [X ~ v * (K^n) / (X^n + K^n) + 2] ] @test isequal(only(Catalyst.get_rxs(rs_expanded)).rate, v0 + v * (X^n) / (X^n + Y^n + K^n)) - @test isequal(get_continuous_events(rs_expanded).eqs, [v*X/(K + X) ~ 1.0]) - @test isequal(get_continuous_events(rs_expanded).eqs, [v*X/(K + X) ~ 1.0]) -end \ No newline at end of file + @test isequal(get_continuous_events(rs_expanded), continuous_events) + @test isequal(get_continuous_events(rs_expanded), discrete_events) +end From 072512ae2a05b68c5070a046c7f9db642ef73c09 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Tue, 11 Jun 2024 03:46:33 +0200 Subject: [PATCH 278/446] test fix --- test/reactionsystem_core/custom_crn_functions.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/reactionsystem_core/custom_crn_functions.jl b/test/reactionsystem_core/custom_crn_functions.jl index eea1447e2a..b6835e6980 100644 --- a/test/reactionsystem_core/custom_crn_functions.jl +++ b/test/reactionsystem_core/custom_crn_functions.jl @@ -209,6 +209,8 @@ let 1.0 => [X ~ X] (v * (X^n) / (X^n + K^n) > 1000.0) => [X ~ v * (K^n) / (X^n + K^n) + 2] ] + continuous_events = ModelingToolkit.SymbolicContinuousCallback.(continuous_events) + discrete_events = ModelingToolkit.SymbolicDiscreteCallback.(discrete_events) @test isequal(only(Catalyst.get_rxs(rs_expanded)).rate, v0 + v * (X^n) / (X^n + Y^n + K^n)) @test isequal(get_continuous_events(rs_expanded), continuous_events) @test isequal(get_continuous_events(rs_expanded), discrete_events) From 9c1607d2ab2e4da583554750a889af634f16ce52 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Tue, 11 Jun 2024 04:20:12 +0200 Subject: [PATCH 279/446] yet another test fix... --- test/reactionsystem_core/custom_crn_functions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/reactionsystem_core/custom_crn_functions.jl b/test/reactionsystem_core/custom_crn_functions.jl index b6835e6980..c4f115d7c3 100644 --- a/test/reactionsystem_core/custom_crn_functions.jl +++ b/test/reactionsystem_core/custom_crn_functions.jl @@ -213,5 +213,5 @@ let discrete_events = ModelingToolkit.SymbolicDiscreteCallback.(discrete_events) @test isequal(only(Catalyst.get_rxs(rs_expanded)).rate, v0 + v * (X^n) / (X^n + Y^n + K^n)) @test isequal(get_continuous_events(rs_expanded), continuous_events) - @test isequal(get_continuous_events(rs_expanded), discrete_events) + @test isequal(get_discrete_events(rs_expanded), discrete_events) end From 8f05cdfa71c1f3c15059e4fc561be394c650db93 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Tue, 11 Jun 2024 09:45:26 -0400 Subject: [PATCH 280/446] Update src/registered_functions.jl Co-authored-by: Sam Isaacson --- src/registered_functions.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registered_functions.jl b/src/registered_functions.jl index 47458a9bce..3e7e146cef 100644 --- a/src/registered_functions.jl +++ b/src/registered_functions.jl @@ -195,5 +195,6 @@ function expand_registered_functions(rs::ReactionSystem) if !isempty(ModelingToolkit.get_discrete_events(rs)) @set! rs.discrete_events = Catalyst.expand_registered_functions(ModelingToolkit.get_discrete_events(rs)) end + reset_networkproperties!(rs) return rs end From 0787d78b7f08d6c222de01ac45ff7ce5af12cc3b Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 11 Jun 2024 09:50:46 -0400 Subject: [PATCH 281/446] remove debug, update docstring, update comments, create function tuple. --- src/registered_functions.jl | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/registered_functions.jl b/src/registered_functions.jl index 47458a9bce..d154cf9c9a 100644 --- a/src/registered_functions.jl +++ b/src/registered_functions.jl @@ -109,12 +109,19 @@ function Symbolics.derivative(::typeof(hillar), args::NTuple{5, Any}, ::Val{5}) (args[1]^args[5] + args[2]^args[5] + args[4]^args[5])^2 end +# Tuple storing all registered function (for use in various functionalities). +const registered_funcs = (mm, mmr, hill, hillr, hillar) + ### Custom CRN FUnction-related Functions ### """ -expand_registered_functions(expr) +expand_registered_functions(in) -Takes an expression, and expands registered function expressions. E.g. `mm(X,v,K)` is replaced with v*X/(X+K). Currently supported functions: `mm`, `mmr`, `hill`, `hillr`, and `hill`. +Takes an expression, and expands registered function expressions. E.g. `mm(X,v,K)` is replaced +with v*X/(X+K). Currently supported functions: `mm`, `mmr`, `hill`, `hillr`, and `hill`. Can +be applied to a reaction system, a reaction, an equation, or a symbolic expression. The input +is not modified, while an output with any functions expanded is returned. If applied to a +reaction system model, any cached network properties are reset. """ function expand_registered_functions(expr) if hasnode(is_catalyst_function, expr) @@ -126,8 +133,7 @@ end # Checks whether an expression corresponds to a catalyst function call (e.g. `mm(X,v,K)`). function is_catalyst_function(expr) iscall(expr) || (return false) - return operation(expr) in [ - Catalyst.mm, Catalyst.mmr, Catalyst.hill, Catalyst.hillr, Catalyst.hillar] + return operation(expr) in registered_funcs end # If the input expression corresponds to a catalyst function call (e.g. `mm(X,v,K)`), returns @@ -155,7 +161,7 @@ function expand_registered_functions(rx::Reaction) rx.substoich, rx.prodstoich, rx.netstoich, rx.only_use_rate, rx.metadata) end -# If applied to a Equation, returns it with it applied to lhs and rhs +# If applied to a Equation, returns it with it applied to lhs and rhs. function expand_registered_functions(eq::Equation) return expand_registered_functions(eq.lhs) ~ expand_registered_functions(eq.rhs) end @@ -174,7 +180,7 @@ function expand_registered_functions(de::ModelingToolkit.SymbolicDiscreteCallbac return ModelingToolkit.SymbolicDiscreteCallback(condition, affects) end -# If applied to a vector, applies it to every element in the vector +# If applied to a vector, applies it to every element in the vector. function expand_registered_functions(vec::Vector) return [Catalyst.expand_registered_functions(element) for element in vec] end @@ -183,10 +189,6 @@ end # Currently, `ModelingToolkit.has_X_events` returns `true` even if event vector is empty (hence # this function cannot be used). function expand_registered_functions(rs::ReactionSystem) - if isdefined(Main, :Infiltrator) - Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__) - end - @set! rs.eqs = Catalyst.expand_registered_functions(get_eqs(rs)) @set! rs.rxs = Catalyst.expand_registered_functions(get_rxs(rs)) if !isempty(ModelingToolkit.get_continuous_events(rs)) From d744ffa0ae948406466894804df4a8f7ae2cbecc Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 12 Jun 2024 13:27:59 -0400 Subject: [PATCH 282/446] init --- .../model_creation/mm_kinetics.svg | 128 ------------------ .../model_creation/sir_outbreaks.svg | 128 ------------------ .../incomplete_brusselator_simulation.svg | 54 -------- .../introduction_to_catalyst.md | 1 + .../examples/basic_CRN_library.md | 14 +- .../ode_simulation_performance.md | 3 +- 6 files changed, 10 insertions(+), 318 deletions(-) delete mode 100644 docs/src/assets/long_ploting_times/model_creation/mm_kinetics.svg delete mode 100644 docs/src/assets/long_ploting_times/model_creation/sir_outbreaks.svg delete mode 100644 docs/src/assets/long_ploting_times/model_simulation/incomplete_brusselator_simulation.svg diff --git a/docs/src/assets/long_ploting_times/model_creation/mm_kinetics.svg b/docs/src/assets/long_ploting_times/model_creation/mm_kinetics.svg deleted file mode 100644 index 824a5fd376..0000000000 --- a/docs/src/assets/long_ploting_times/model_creation/mm_kinetics.svg +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/src/assets/long_ploting_times/model_creation/sir_outbreaks.svg b/docs/src/assets/long_ploting_times/model_creation/sir_outbreaks.svg deleted file mode 100644 index 3e213ebbdd..0000000000 --- a/docs/src/assets/long_ploting_times/model_creation/sir_outbreaks.svg +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/src/assets/long_ploting_times/model_simulation/incomplete_brusselator_simulation.svg b/docs/src/assets/long_ploting_times/model_simulation/incomplete_brusselator_simulation.svg deleted file mode 100644 index 4f9f01fedf..0000000000 --- a/docs/src/assets/long_ploting_times/model_simulation/incomplete_brusselator_simulation.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index bf63cd4e60..4ef1e897f3 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -196,6 +196,7 @@ jprob = JumpProblem(repressilator, dprob, Direct(), save_positions=(false,false) # now, let's solve and plot the jump process: sol = solve(jprob, SSAStepper(), saveat=10.) plot(sol) +plot(sol, plotdensity = 1000, fmt =:png) # hide ``` We see that oscillations remain, but become much noisier. Note, in constructing diff --git a/docs/src/model_creation/examples/basic_CRN_library.md b/docs/src/model_creation/examples/basic_CRN_library.md index 76287686b5..cbe3b49758 100644 --- a/docs/src/model_creation/examples/basic_CRN_library.md +++ b/docs/src/model_creation/examples/basic_CRN_library.md @@ -116,11 +116,11 @@ oplt = plot(osol; title = "Reaction rate equation (ODE)") splt = plot(ssol; title = "Chemical Langevin equation (SDE)") jplt = plot(jsol; title = "Stochastic chemical kinetics (Jump)") plot(oplt, splt, jplt; lw = 2, size=(800,800), layout = (3,1)) -plot!(bottom_margin = 3Plots.Measures.mm) # hide -nothing # hide +plot(plot(osol; title = "Reaction rate equation (ODE)", plotdensity = 1000, fmt =:png), + plot(ssol; title = "Chemical Langevin equation (SDE)", plotdensity = 1000, fmt =:png), + plot(jsol; title = "Stochastic chemical kinetics (Jump)", plotdensity = 1000, fmt =:png); + lw = 2, size=(800,800), layout = (3,1), bottom_margin = 3Plots.Measures.mm) # hide ``` -![MM Kinetics](../../assets/long_ploting_times/model_creation/mm_kinetics.svg) -Note that, due to the large amounts of the species involved, the stochastic trajectories are very similar to the deterministic one. ## [SIR infection model](@id basic_CRN_library_sir) The [SIR model](https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SIR_model) is the simplest model of the spread of an infectious disease. While the real system is very different from the chemical and cellular processes typically modelled with CRNs, it (and several other epidemiological systems) can be modelled using the same CRN formalism. The SIR model consists of three species: susceptible ($S$), infected ($I$), and removed ($R$) individuals, and two reaction events: infection and recovery. @@ -160,9 +160,11 @@ jplt1 = plot(jsol1; title = "Outbreak") jplt2 = plot(jsol2; title = "Outbreak") jplt3 = plot(jsol3; title = "No outbreak") plot(jplt1, jplt2, jplt3; lw = 3, size=(800,700), layout = (3,1)) -nothing # hide +plot(plot(jsol1; title = "Outbreak", plotdensity = 1000, fmt =:png), + plot(jsol2; title = "Outbreak", plotdensity = 1000, fmt =:png), + plot(jsol3; title = "No outbreak", plotdensity = 1000, fmt =:png); + lw = 3, size=(800,700), layout = (3,1)) # hide ``` -![SIR Outbreak](../../assets/long_ploting_times/model_creation/sir_outbreaks.svg) ## [Chemical cross-coupling](@id basic_CRN_library_cc) In chemistry, [cross-coupling](https://en.wikipedia.org/wiki/Cross-coupling_reaction) is when a catalyst combines two substrates to form a product. In this example, the catalyst ($C$) first binds one substrate ($S₁$) to form an intermediary complex ($S₁C$). Next, the complex binds the second substrate ($S₂$) to form another complex ($CP$). Finally, the catalyst releases the now-formed product ($P$). This system is an extended version of the [Michaelis-Menten system presented earlier](@ref basic_CRN_library_mm). diff --git a/docs/src/model_simulation/ode_simulation_performance.md b/docs/src/model_simulation/ode_simulation_performance.md index 9d6249d5f3..4556956557 100644 --- a/docs/src/model_simulation/ode_simulation_performance.md +++ b/docs/src/model_simulation/ode_simulation_performance.md @@ -31,9 +31,8 @@ oprob = ODEProblem(brusselator, u0, tspan, ps) sol1 = solve(oprob, Tsit5()) plot(sol1) -nothing # hide +plot(sol1, plotdensity = 1000, fmt =:png) # hide ``` -![Incomplete Brusselator Simulation](../assets/long_ploting_times/model_simulation/incomplete_brusselator_simulation.svg) We get a warning, indicating that the simulation was terminated. Furthermore, the resulting plot ends at $t ≈ 12$, meaning that the simulation was not completed (as the simulation's endpoint is $t = 20$). Indeed, we can confirm this by checking the *return code* of the solution object: ```@example ode_simulation_performance_1 From e45fc0cf807e9643ef998553a55efd5ce7d1e1b3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 12 Jun 2024 13:59:42 -0400 Subject: [PATCH 283/446] get stuff to hie properly --- .../introduction_to_catalyst.md | 2 +- .../examples/basic_CRN_library.md | 18 +++++++++--------- .../ode_simulation_performance.md | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index 4ef1e897f3..8e852d7f1b 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -196,7 +196,7 @@ jprob = JumpProblem(repressilator, dprob, Direct(), save_positions=(false,false) # now, let's solve and plot the jump process: sol = solve(jprob, SSAStepper(), saveat=10.) plot(sol) -plot(sol, plotdensity = 1000, fmt =:png) # hide +plot(sol, plotdensity = 1000, fmt = :png) # hide ``` We see that oscillations remain, but become much noisier. Note, in constructing diff --git a/docs/src/model_creation/examples/basic_CRN_library.md b/docs/src/model_creation/examples/basic_CRN_library.md index cbe3b49758..533e80f5ed 100644 --- a/docs/src/model_creation/examples/basic_CRN_library.md +++ b/docs/src/model_creation/examples/basic_CRN_library.md @@ -115,11 +115,11 @@ using Plots oplt = plot(osol; title = "Reaction rate equation (ODE)") splt = plot(ssol; title = "Chemical Langevin equation (SDE)") jplt = plot(jsol; title = "Stochastic chemical kinetics (Jump)") -plot(oplt, splt, jplt; lw = 2, size=(800,800), layout = (3,1)) -plot(plot(osol; title = "Reaction rate equation (ODE)", plotdensity = 1000, fmt =:png), - plot(ssol; title = "Chemical Langevin equation (SDE)", plotdensity = 1000, fmt =:png), - plot(jsol; title = "Stochastic chemical kinetics (Jump)", plotdensity = 1000, fmt =:png); - lw = 2, size=(800,800), layout = (3,1), bottom_margin = 3Plots.Measures.mm) # hide +plot(oplt, splt, jplt; lw = 2, size=(800,800), layout = (3,1)) # hide +oplt = plot(osol; title = "Reaction rate equation (ODE)", plotdensity = 1000, fmt = :png) # hide +splt = plot(ssol; title = "Chemical Langevin equation (SDE)", plotdensity = 1000, fmt = :png) # hide +jplt = plot(jsol; title = "Stochastic chemical kinetics (Jump)", plotdensity = 1000, fmt = :png) # hide +plot(oplt, splt, jplt; lw = 2, size=(800,800), layout = (3,1)) ``` ## [SIR infection model](@id basic_CRN_library_sir) @@ -160,10 +160,10 @@ jplt1 = plot(jsol1; title = "Outbreak") jplt2 = plot(jsol2; title = "Outbreak") jplt3 = plot(jsol3; title = "No outbreak") plot(jplt1, jplt2, jplt3; lw = 3, size=(800,700), layout = (3,1)) -plot(plot(jsol1; title = "Outbreak", plotdensity = 1000, fmt =:png), - plot(jsol2; title = "Outbreak", plotdensity = 1000, fmt =:png), - plot(jsol3; title = "No outbreak", plotdensity = 1000, fmt =:png); - lw = 3, size=(800,700), layout = (3,1)) # hide +jplt1 = plot(jsol1; title = "Outbreak", plotdensity = 1000, fmt = :png) # hide +jplt2 = plot(jsol2; title = "Outbreak", plotdensity = 1000, fmt = :png) # hide +jplt3 = plot(jsol3; title = "No outbreak", plotdensity = 1000, fmt = :png) # hide +plot(jplt1, jplt2, jplt3; lw = 3, size=(800,700), layout = (3,1), plotdensity = 1000, fmt = :png) # hide ``` ## [Chemical cross-coupling](@id basic_CRN_library_cc) diff --git a/docs/src/model_simulation/ode_simulation_performance.md b/docs/src/model_simulation/ode_simulation_performance.md index 4556956557..40e699657b 100644 --- a/docs/src/model_simulation/ode_simulation_performance.md +++ b/docs/src/model_simulation/ode_simulation_performance.md @@ -31,7 +31,7 @@ oprob = ODEProblem(brusselator, u0, tspan, ps) sol1 = solve(oprob, Tsit5()) plot(sol1) -plot(sol1, plotdensity = 1000, fmt =:png) # hide +plot(sol1, plotdensity = 1000, fmt = :png) # hide ``` We get a warning, indicating that the simulation was terminated. Furthermore, the resulting plot ends at $t ≈ 12$, meaning that the simulation was not completed (as the simulation's endpoint is $t = 20$). Indeed, we can confirm this by checking the *return code* of the solution object: From 57f8605102b28a4bddcdf6ec6b8e903d96a47ba0 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 13 Jun 2024 08:32:32 -0400 Subject: [PATCH 284/446] Update to general namespace separator This is the one place where it's used? Thoguh, does this code need to exist? It seems like it's a separate implementation of SII that is a bit buggy, @TorkelE might be best to just replace with SII? --- src/reactionsystem_conversions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 8e49896854..68b8a819a2 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -798,7 +798,7 @@ function _symbol_to_var(sys, sym) if hasproperty(sys, sym) var = getproperty(sys, sym, namespace = false) else - strs = split(String(sym), "₊") # need to check if this should be split of not!!! + strs = split(String(sym), ModelingToolkit.NAMESPACE_SEPARATOR) # need to check if this should be split of not!!! if length(strs) > 1 var = getproperty(sys, Symbol(strs[1]), namespace = false) for str in view(strs, 2:length(strs)) From b9a72f00cc7899f6ca11ac2befd8c841af5aeadf Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 13 Jun 2024 08:52:25 -0400 Subject: [PATCH 285/446] Update dsl_options.jl --- test/dsl/dsl_options.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dsl/dsl_options.jl b/test/dsl/dsl_options.jl index 9aff7afe52..819a887427 100644 --- a/test/dsl/dsl_options.jl +++ b/test/dsl/dsl_options.jl @@ -493,7 +493,7 @@ let @test plot(sol; idxs=:X).series_list[1].plotattributes[:y][end] ≈ 10.0 @test plot(sol; idxs=[X, Y]).series_list[2].plotattributes[:y][end] ≈ 3.0 @test plot(sol; idxs=[rn.X, rn.Y]).series_list[2].plotattributes[:y][end] ≈ 3.0 - @test_broken plot(sol; idxs=[:X, :Y]).series_list[2].plotattributes[:y][end] ≈ 3.0 # (https://github.com/SciML/ModelingToolkit.jl/issues/2778) + @test plot(sol; idxs=[:X, :Y]).series_list[2].plotattributes[:y][end] ≈ 3.0 # (https://github.com/SciML/ModelingToolkit.jl/issues/2778) end # Compares programmatic and DSL system with observables. @@ -950,4 +950,4 @@ let rl = oderatelaw(reactions(rn3)[1]; combinatoric_ratelaw) @unpack k1, A = rn3 @test isequal(rl, k1*A^2) -end \ No newline at end of file +end From 4d8c04d683495c3c6029ecf453465cb63ba914e1 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Thu, 13 Jun 2024 09:15:26 -0400 Subject: [PATCH 286/446] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b9371b3656..eb7e1abeb7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Catalyst" uuid = "479239e8-5488-4da2-87a7-35f2df7eef83" -version = "13.5.1" +version = "14.0.0-DEV" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" From f0f4a09c7c3a5a45d1a10d044e47537ad5dcce17 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 14 Jun 2024 08:55:12 -0400 Subject: [PATCH 287/446] update JumpProblem for latest version --- src/Catalyst.jl | 3 + .../lattice_jump_systems.jl | 79 ++-- .../lattice_reaction_systems.jl | 12 +- .../spatial_ODE_systems.jl | 5 +- src/spatial_reaction_systems/utility.jl | 59 +-- test/runtests.jl | 4 +- .../lattice_reaction_systems.jl | 321 +++++++------ .../lattice_reaction_systems_ODEs.jl | 11 +- .../lattice_reaction_systems_jumps.jl | 84 ++-- .../lattice_reaction_systems_lattice_types.jl | 0 .../simulate_PDEs.jl | 0 .../lattice_reaction_systems.jl | 423 ------------------ test/spatial_test_networks.jl | 9 + 13 files changed, 312 insertions(+), 698 deletions(-) rename test/{spatial_reaction_systems => spatial_modelling}/lattice_reaction_systems_ODEs.jl (98%) rename test/{spatial_reaction_systems => spatial_modelling}/lattice_reaction_systems_jumps.jl (63%) rename test/{spatial_reaction_systems => spatial_modelling}/lattice_reaction_systems_lattice_types.jl (100%) rename test/{spatial_reaction_systems => spatial_modelling}/simulate_PDEs.jl (100%) delete mode 100644 test/spatial_reaction_systems/lattice_reaction_systems.jl diff --git a/src/Catalyst.jl b/src/Catalyst.jl index d386e4f98c..edf896dd54 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -183,6 +183,9 @@ export make_edge_p_values, make_directed_edge_values include("spatial_reaction_systems/spatial_ODE_systems.jl") include("spatial_reaction_systems/lattice_jump_systems.jl") +# General spatial modelling utility functions. +include("spatial_reaction_systems/utility.jl") + ### ReactionSystem Serialisation ### # Has to be at the end (because it uses records of all metadata declared by Catalyst). include("reactionsystem_serialisation/serialisation_support.jl") diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index cbe8374859..44b26ecbbd 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -7,28 +7,25 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") end - # Converts potential symmaps to varmaps - # Vertex and edge parameters may be given in a tuple, or in a common vector, making parameter case complicated. + # Converts potential symmaps to varmaps. u0_in = symmap_to_varmap(lrs, u0_in) - p_in = (p_in isa Tuple{<:Any, <:Any}) ? - (symmap_to_varmap(lrs, p_in[1]), symmap_to_varmap(lrs, p_in[2])) : - symmap_to_varmap(lrs, p_in) + p_in = symmap_to_varmap(lrs, p_in) # Converts u0 and p to their internal forms. + # u0 is simply a vector with all the species' initial condition values across all vertices. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. - u0 = lattice_process_u0(u0_in, species(lrs), num_verts(lrs)) - # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. - # These elements are length 1 vectors (if the parameter is uniform), - # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). - vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), - edge_parameters(lrs), lrs) - - # Returns a DiscreteProblem. - # Previously, a Tuple was used for (vert_ps, edge_ps), but this was converted to a Vector internally. - return DiscreteProblem(u0, tspan, [vert_ps, edge_ps], args...; kwargs...) + u0 = lattice_process_u0(u0_in, species(lrs), lrs) + # vert_ps and `edge_ps` are vector maps, taking each parameter's Symbolics representation to its value(s). + # vert_ps values are vectors. Here, index (i) is a parameter's value in vertex i. + # edge_ps values are sparse matrices. Here, index (i,j) is a parameter's value in the edge from vertex i to vertex j. + # Uniform vertex/edge parameters store only a single value (a length 1 vector, or size 1x1 sparse matrix). + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + + # Returns a DiscreteProblem (which basically just stores the processed input). + return DiscreteProblem(u0, tspan, [vert_ps; edge_ps], args...; kwargs...) end -# Builds a spatial JumpProblem from a DiscreteProblem containg a Lattice Reaction System. +# Builds a spatial JumpProblem from a DiscreteProblem containing a `LatticeReactionSystem`. function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(reactionsystem(lrs)), combinatoric_ratelaws = get_combinatoric_ratelaws(reactionsystem(lrs)), kwargs...) # Error checks. @@ -37,50 +34,53 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator end # Computes hopping constants and mass action jumps (requires some internal juggling). - # Currently, JumpProcesses requires uniform vertex parameters (hence `p=first.(dprob.p[1])`). # Currently, the resulting JumpProblem does not depend on parameters (no way to incorporate these). - # Hence the parameters of this one does nto actually matter. If at some point JumpProcess can + # Hence the parameters of this one does not actually matter. If at some point JumpProcess can # handle parameters this can be updated and improved. # The non-spatial DiscreteProblem have a u0 matrix with entries for all combinations of species and vertexes. hopping_constants = make_hopping_constants(dprob, lrs) sma_jumps = make_spatial_majumps(dprob, lrs) non_spat_dprob = DiscreteProblem(reshape(dprob.u0, num_species(lrs), num_verts(lrs)), dprob.tspan, first.(dprob.p[1])) + # Creates and returns a spatial JumpProblem (masked lattices are not supported by these). + spatial_system = has_masked_lattice(lrs) ? get_lattice_graph(lrs) : lattice(lrs) return JumpProblem(non_spat_dprob, aggregator, sma_jumps; - hopping_constants, spatial_system = lattice(lrs), name, kwargs...) + hopping_constants, spatial_system , name, kwargs...) end # Creates the hopping constants from a discrete problem and a lattice reaction system. function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) # Creates the all_diff_rates vector, containing for each species, its transport rate across all edges. # If transport rate is uniform for one species, the vector have a single element, else one for each edge. - spatial_rates_dict = Dict(compute_all_transport_rates(dprob.p[1], dprob.p[2], lrs)) + spatial_rates_dict = Dict(compute_all_transport_rates(Dict(dprob.p), lrs)) all_diff_rates = [haskey(spatial_rates_dict, s) ? spatial_rates_dict[s] : [0.0] for s in species(lrs)] - # Creates the hopping constant Matrix. It contains one element for each combination of species and vertex. - # Each element is a Vector, containing the outgoing hopping rates for that species, from that vertex, on that edge. - hopping_constants = [Vector{Float64}(undef, length(lattice(lrs).fadjlist[j])) - for i in 1:(num_species(lrs)), j in 1:(num_verts(lrs))] - - # For each edge, finds each position in `hopping_constants`. - for (e_idx, e) in enumerate(edges(lattice(lrs))) - dst_idx = findfirst(isequal(e.dst), lattice(lrs).fadjlist[e.src]) - # For each species, sets that hopping rate. - for s_idx in 1:(num_species(lrs)) - hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx) - end + # Creates an array (of the same size as the hopping constant array) containing all edges. + # First the array is a NxM matrix (number of species x number of vertices). Each element is a + # vector containing all edges leading out from that vertex (sorted by destination index). + edge_array = [Pair{Int64,Int64}[] for _1 in 1:num_species(lrs), _2 in 1:num_verts(lrs)] + for e in edge_iterator(lrs), s_idx in 1:num_species(lrs) + push!(edge_array[s_idx, e[1]], e) end - + foreach(e_vec -> sort!(e_vec; by = e -> e[2]), edge_array) + + # Creates the hopping constants array. It has the same shape as the edge array, but each + # element is that species transportation rate along that edge + hopping_constants = [ + [Catalyst.get_edge_value(all_diff_rates[s_idx], e) for e in edge_array[s_idx, src_idx]] + for s_idx in 1:num_species(lrs), src_idx in 1:num_verts(lrs) + ] return hopping_constants end # Creates a SpatialMassActionJump struct from a (spatial) DiscreteProblem and a LatticeReactionSystem. # Could implementation a version which, if all reaction's rates are uniform, returns a MassActionJump. -# Not sure if there is any form of performance improvement from that though. Possibly is not the case. +# Not sure if there is any form of performance improvement from that though. Likely not the case. function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) # Creates a vector, storing which reactions have spatial components. - is_spatials = [Catalyst.has_spatial_vertex_component(rx.rate, lrs; vert_ps = dprob.p[1]) for rx in reactions(reactionsystem(lrs))] + is_spatials = [has_spatial_vertex_component(rx.rate, dprob.p) + for rx in reactions(reactionsystem(lrs))] # Creates templates for the rates (uniform and spatial) and the stoichiometries. # We cannot fetch reactant_stoich and net_stoich from a (non-spatial) MassActionJump. @@ -91,10 +91,10 @@ function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) net_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(reactionsystem(lrs)))) # Loops through reactions with non-spatial rates, computes their rates and stoichiometries. - cur_rx = 1; + cur_rx = 1 for (is_spat, rx) in zip(is_spatials, reactions(reactionsystem(lrs))) is_spat && continue - u_rates[cur_rx] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1])[1] + u_rates[cur_rx] = compute_vertex_value(rx.rate, lrs; ps = dprob.p)[1] substoich_map = Pair.(rx.substrates, rx.substoich) reactant_stoich[cur_rx] = int_map(substoich_map, reactionsystem(lrs)) net_stoich[cur_rx] = int_map(rx.netstoich, reactionsystem(lrs)) @@ -103,8 +103,7 @@ function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) # Loops through reactions with spatial rates, computes their rates and stoichiometries. for (is_spat, rx) in zip(is_spatials, reactions(reactionsystem(lrs))) is_spat || continue - s_rates[cur_rx - length(u_rates), :] = compute_vertex_value(rx.rate, lrs; - vert_ps = dprob.p[1]) + s_rates[cur_rx - length(u_rates), :] .= compute_vertex_value(rx.rate, lrs; ps = dprob.p) substoich_map = Pair.(rx.substrates, rx.substoich) reactant_stoich[cur_rx] = int_map(substoich_map, reactionsystem(lrs)) net_stoich[cur_rx] = int_map(rx.netstoich, reactionsystem(lrs)) @@ -114,7 +113,7 @@ function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) isempty(u_rates) && (u_rates = nothing) (count(is_spatials) == 0) && (s_rates = nothing) - return SpatialMassActionJump(u_rates, s_rates, reactant_stoich, net_stoich) + return SpatialMassActionJump(u_rates, s_rates, reactant_stoich, net_stoich, nothing) end ### Extra ### diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index a543267137..43f410932e 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -307,6 +307,16 @@ grid_dims(lrs::LatticeReactionSystem) = grid_dims(lattice(lrs)) grid_dims(lattice::GridLattice{N,T}) where {N,T} = return N grid_dims(lattice::Graph) = error("Grid dimensions is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") +""" + get_lattice_graph(lrs::LatticeReactionSystem) + +Returns lrs's lattice, but in as a graph. Currently does not work for Cartesian lattices. +""" +function get_lattice_graph(lrs::LatticeReactionSystem) + has_graph_lattice(lrs) && return lattice(lrs) + return Graphs.SimpleGraphFromIterator(Graphs.SimpleEdge(e[1], e[2]) + for e in edge_iterator(lrs)) +end ### Catalyst-based Getters ### @@ -393,7 +403,7 @@ end D_vals = make_edge_p_values(lrs, make_edge_p_value) ``` """ -function make_edge_p_values(lrs::LatticeReactionSystem, make_edge_p_value::Function, ) +function make_edge_p_values(lrs::LatticeReactionSystem, make_edge_p_value::Function) if has_graph_lattice(lrs) error("The `make_edge_p_values` function is only meant for lattices with (Cartesian or masked) grid structures. It cannot be applied to graph lattices.") end diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 6ec501b635..bd9fdf8e4e 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -186,7 +186,7 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, u0 = lattice_process_u0(u0_in, species(lrs), lrs) # vert_ps and `edge_ps` are vector maps, taking each parameter's Symbolics representation to its value(s). # vert_ps values are vectors. Here, index (i) is a parameter's value in vertex i. - # edge_ps becomes a sparse matrix. Here, index (i,j) is a parameter's value in the edge from vertex i to vertex j. + # edge_ps values are sparse matrices. Here, index (i,j) is a parameter's value in the edge from vertex i to vertex j. # Uniform vertex/edge parameters store only a single value (a length 1 vector, or size 1x1 sparse matrix). # In the `ODEProblem` vert_ps and edge_ps are merged (but for building the ODEFunction, they are separate). vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) @@ -321,6 +321,9 @@ end # Defines the forcing functor's effect on the (spatial) ODE system. function (f_func::LatticeTransportODEf)(du, u, p, t) + println(du) + println(u) + println(p) # Updates for non-spatial reactions. for vert_i in 1:(f_func.num_verts) # Gets the indices of all the species at vertex i. diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 2ec7e104b0..43c7932185 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -327,36 +327,25 @@ end # The expression is assumed to be valid in vertexes (and can have vertex parameter and state components). # If at least one component is non-uniform, output is a vector of length equal to the number of vertexes. # If all components are uniform, the output is a length one vector. -function compute_vertex_value(exp, lrs::LatticeReactionSystem; u=nothing, vert_ps=nothing) +function compute_vertex_value(exp, lrs::LatticeReactionSystem; u = [], ps = []) # Finds the symbols in the expression. Checks that all correspond to unknowns or vertex parameters. relevant_syms = Symbolics.get_variables(exp) if any(any(isequal(sym) in edge_parameters(lrs)) for sym in relevant_syms) - error("An edge parameter was encountered in expressions: $exp. Here, on vertex-based components are expected.") + error("An edge parameter was encountered in expressions: $exp. Here, only vertex-based components are expected.") end - # Creates a Function tha computes the expressions value for a parameter set. + + # Creates a Function that computes the expressions value for a parameter set. exp_func = drop_expr(@RuntimeGeneratedFunction(build_function(exp, relevant_syms...))) + # Creates a dictionary with the value(s) for all edge parameters. - if !isnothing(u) && !isnothing(vert_ps) - all_syms = [species(lrs); vertex_parameters(lrs)] - all_vals = [u; vert_ps] - elseif !isnothing(u) && isnothing(vert_ps) - all_syms = species(lrs) - all_vals = u - - elseif isnothing(u) && !isnothing(vert_ps) - all_syms = vertex_parameters(lrs) - all_vals = vert_ps - else - error("Either u or vertex_ps have to be provided to has_spatial_vertex_component.") - end - sym_val_dict = vals_to_dict(all_syms, all_vals) + value_dict = Dict(vcat(u, ps)) # If all values are uniform, compute value once. Else, do it at all edges. - if !has_spatial_vertex_component(exp, lrs; u, vert_ps) - return [exp_func([sym_val_dict[sym][1] for sym in relevant_syms]...)] + if all(length(value_dict[sym]) == 1 for sym in relevant_syms) + return [exp_func([value_dict[sym][1] for sym in relevant_syms]...)] end - return [exp_func([get_component_value(sym_val_dict[sym], idxV) for sym in relevant_syms]...) - for idxV in 1:num_verts(lrs)] + return [exp_func([get_vertex_value(value_dict[sym], vert_idx) for sym in relevant_syms]...) + for vert_idx in 1:num_verts(lrs)] end ### System Property Checks ### @@ -373,26 +362,10 @@ function has_spatial_edge_component(exp, lrs::LatticeReactionSystem, edge_ps) return any(length(edge_ps[p_idx]) != 1 for p_idx in p_idxs) end -# For a Symbolic expression, a LatticeReactionSystem, and a parameter list of the internal format (vector of vectors): -# Checks if any vertex parameter in the expression have a spatial component (that is, is not uniform). -function has_spatial_vertex_component(exp, lrs::LatticeReactionSystem; - u = nothing, vert_ps = nothing) - # Finds all the symbols in the expression. - exp_syms = Symbolics.get_variables(exp) - - # If vertex parameter values where given, checks if any of these have non-uniform values. - if !isnothing(vert_ps) - exp_vert_ps = filter(sym -> any(isequal(sym), vertex_parameters(lrs)), exp_syms) - p_idxs = [ModelingToolkit.parameter_index(reactionsystem(lrs), sym) for sym in exp_vert_ps] - any(length(vert_ps[p_idx]) != 1 for p_idx in p_idxs) && return true - end - - # If states values where given, checks if any of these have non-uniform values. - if !isnothing(u) - exp_u = filter(sym -> any(isequal(sym), species(lrs)), exp_syms) - u_idxs = [ModelingToolkit.variable_index(reactionsystem(lrs), sym) for sym in exp_u] - any(length(u[u_idx]) != 1 for u_idx in u_idxs) && return true - end - - return false +# For a Symbolic expression, and a parameter set, checks if any relevant parameters have a +# spatial component. Filters out any parameters that are edge parameters. +function has_spatial_vertex_component(exp, ps) + relevant_syms = Symbolics.get_variables(exp) + value_dict = Dict(filter(p -> p[2] isa Vector, ps)) + return any(length(value_dict[sym]) > 1 for sym in relevant_syms) end diff --git a/test/runtests.jl b/test/runtests.jl index bf6be2bbfd..23cb457f4b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,6 +9,7 @@ using SafeTestsets, Test ### Run Tests ### @time begin + @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_jumps.jl") end # Tests the `ReactionSystem` structure and its properties. @time @safetestset "Reaction Structure" begin include("reactionsystem_core/reaction.jl") end @@ -53,8 +54,9 @@ using SafeTestsets, Test # Tests spatial modelling and simulations. @time @safetestset "PDE Systems Simulations" begin include("spatial_modelling/simulate_PDEs.jl") end @time @safetestset "Lattice Reaction Systems" begin include("spatial_modelling/lattice_reaction_systems.jl") end + @time @safetestset "Spatial Lattice Variants" begin include("spatial_modelling/lattice_reaction_systems_lattice_types.jl") end @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_ODEs.jl") end - @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end + @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_jumps.jl") end # Tests network visualisation. @time @safetestset "Latexify" begin include("visualisation/latexify.jl") end diff --git a/test/spatial_modelling/lattice_reaction_systems.jl b/test/spatial_modelling/lattice_reaction_systems.jl index d25c72694c..b2823b3111 100644 --- a/test/spatial_modelling/lattice_reaction_systems.jl +++ b/test/spatial_modelling/lattice_reaction_systems.jl @@ -1,13 +1,13 @@ ### Preparations ### # Fetch packages. -using Catalyst, Graphs, Test -using Symbolics: BasicSymbolic, unwrap -t = default_t() +using Catalyst, Graphs, OrdinaryDiffEq, Test -# Pre declares a grid. -grid = Graphs.grid([2, 2]) +# Fetch test networks. +include("../spatial_test_networks.jl") +# Pre declares a grid. +grids = [very_small_2d_cartesian_grid, very_small_2d_masked_grid, very_small_2d_graph_grid] ### Tests LatticeReactionSystem Getters Correctness ### @@ -16,16 +16,18 @@ let rs = @reaction_network begin (p, 1), 0 <--> X end - tr = @transport_reaction d X - lrs = LatticeReactionSystem(rs, [tr], grid) - - @unpack X, p = rs - d = edge_parameters(lrs)[1] - @test issetequal(species(lrs), [X]) - @test issetequal(spatial_species(lrs), [X]) - @test issetequal(parameters(lrs), [p, d]) - @test issetequal(vertex_parameters(lrs), [p]) - @test issetequal(edge_parameters(lrs), [d]) + tr = @transport_reaction d X + for grid in grids + lrs = LatticeReactionSystem(rs, [tr], grid) + + @unpack X, p = rs + d = edge_parameters(lrs)[1] + @test issetequal(species(lrs), [X]) + @test issetequal(spatial_species(lrs), [X]) + @test issetequal(parameters(lrs), [p, d]) + @test issetequal(vertex_parameters(lrs), [p]) + @test issetequal(edge_parameters(lrs), [d]) + end end # Test case 2. @@ -48,14 +50,16 @@ let end tr_1 = @transport_reaction dX X tr_2 = @transport_reaction dY Y - lrs = LatticeReactionSystem(rs, [tr_1, tr_2], grid) - - @unpack X, Y, pX, pY, dX, dY = rs - @test issetequal(species(lrs), [X, Y]) - @test issetequal(spatial_species(lrs), [X, Y]) - @test issetequal(parameters(lrs), [pX, pY, dX, dY]) - @test issetequal(vertex_parameters(lrs), [pX, pY, dY]) - @test issetequal(edge_parameters(lrs), [dX]) + for grid in grids + lrs = LatticeReactionSystem(rs, [tr_1, tr_2], grid) + + @unpack X, Y, pX, pY, dX, dY = rs + @test issetequal(species(lrs), [X, Y]) + @test issetequal(spatial_species(lrs), [X, Y]) + @test issetequal(parameters(lrs), [pX, pY, dX, dY]) + @test issetequal(vertex_parameters(lrs), [pX, pY, dY]) + @test issetequal(edge_parameters(lrs), [dX]) + end end # Test case 4. @@ -66,14 +70,16 @@ let (pY, 1), 0 <--> Y end tr_1 = @transport_reaction dX X - lrs = LatticeReactionSystem(rs, [tr_1], grid) - - @unpack dX, p, X, Y, pX, pY = rs - @test issetequal(species(lrs), [X, Y]) - @test issetequal(spatial_species(lrs), [X]) - @test issetequal(parameters(lrs), [dX, p, pX, pY]) - @test issetequal(vertex_parameters(lrs), [dX, p, pX, pY]) - @test issetequal(edge_parameters(lrs), []) + for grid in grids + lrs = LatticeReactionSystem(rs, [tr_1], grid) + + @unpack dX, p, X, Y, pX, pY = rs + @test issetequal(species(lrs), [X, Y]) + @test issetequal(spatial_species(lrs), [X]) + @test issetequal(parameters(lrs), [dX, p, pX, pY]) + @test issetequal(vertex_parameters(lrs), [dX, p, pX, pY]) + @test issetequal(edge_parameters(lrs), []) + end end # Test case 5. @@ -88,21 +94,24 @@ let end @unpack dX, X, V = rs @parameters dV dW + @variables t @species W(t) tr_1 = TransportReaction(dX, X) tr_2 = @transport_reaction dY Y tr_3 = @transport_reaction dZ Z tr_4 = TransportReaction(dV, V) - tr_5 = TransportReaction(dW, W) - lrs = LatticeReactionSystem(rs, [tr_1, tr_2, tr_3, tr_4, tr_5], grid) - - @unpack pX, pY, pZ, pV, dX, dY, X, Y, Z, V = rs - dZ, dV, dW = edge_parameters(lrs)[2:end] - @test issetequal(species(lrs), [W, X, Y, Z, V]) - @test issetequal(spatial_species(lrs), [X, Y, Z, V, W]) - @test issetequal(parameters(lrs), [pX, pY, dX, dY, pZ, pV, dZ, dV, dW]) - @test issetequal(vertex_parameters(lrs), [pX, pY, dY, pZ, pV]) - @test issetequal(edge_parameters(lrs), [dX, dZ, dV, dW]) + tr_5 = TransportReaction(dW, W) + for grid in grids + lrs = LatticeReactionSystem(rs, [tr_1, tr_2, tr_3, tr_4, tr_5], grid) + + @unpack pX, pY, pZ, pV, dX, dY, X, Y, Z, V = rs + dZ, dV, dW = edge_parameters(lrs)[2:end] + @test issetequal(species(lrs), [W, X, Y, Z, V]) + @test issetequal(spatial_species(lrs), [X, Y, Z, V, W]) + @test issetequal(parameters(lrs), [pX, pY, dX, dY, pZ, pV, dZ, dV, dW]) + @test issetequal(vertex_parameters(lrs), [pX, pY, dY, pZ, pV]) + @test issetequal(edge_parameters(lrs), [dX, dZ, dV, dW]) + end end # Test case 6. @@ -111,9 +120,11 @@ let (p, 1), 0 <--> X end tr = @transport_reaction d X - lrs = LatticeReactionSystem(rs, [tr], grid) + for grid in grids + lrs = LatticeReactionSystem(rs, [tr], grid) - @test nameof(lrs) == :customname + @test nameof(lrs) == :customname + end end ### Tests Spatial Reactions Getters Correctness ### @@ -180,6 +191,7 @@ end # Test reactions with constants in rate. let + @variables t @species X(t) Y(t) tr_1 = TransportReaction(1.5, X) @@ -223,6 +235,7 @@ end # Test creation of TransportReaction with non-parameters in rate. # Tests that it works even when rate is highly nested. let + @variables t @species X(t) Y(t) @parameters D1 D2 D3 @test_throws ErrorException TransportReaction(D1 + D2*(D3 + Y), X) @@ -235,7 +248,9 @@ let (p, d), 0 <--> X end tr = @transport_reaction D Y - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + for grid in grids + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + end end # Network where the rate depend on a species @@ -245,7 +260,9 @@ let (p, d), 0 <--> X end tr = @transport_reaction D*Y X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + for grid in grids + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + end end # Network with edge parameter in non-spatial reaction rate. @@ -255,7 +272,9 @@ let (p, d), 0 <--> X end tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + for grid in grids + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + end end # Network where metadata has been added in rs (which is not seen in transport reaction). @@ -265,104 +284,140 @@ let (p, d), 0 <--> X end tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) + for grid in grids + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) - rs = @reaction_network begin - @parameters D [description="Parameter with added metadata"] - (p, d), 0 <--> X + rs = @reaction_network begin + @parameters D [description="Parameter with added metadata"] + (p, d), 0 <--> X + end + tr = @transport_reaction D X + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) end - tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) end -### Test Designation of Parameter Types ### -# Currently not supported. Won't be until the LatticeReactionSystem internal update is merged. +### Tests Grid Vertex and Edge Number Computation ### -# Checks that parameter types designated in the non-spatial `ReactionSystem` is handled correctly. -# Broken lattice tests have local branches that fixes them. -@test_broken let - # Declares LatticeReactionSystem with designated parameter types. - rs = @reaction_network begin - @parameters begin - k1 - l1 - k2::Float64 = 2.0 - l2::Float64 - k3::Int64 = 2, [description="A parameter"] - l3::Int64 - k4::Float32, [description="Another parameter"] - l4::Float32 - k5::Rational{Int64} - l5::Rational{Int64} - D1::Float32 - D2, [edgeparameter=true] - D3::Rational{Int64}, [edgeparameter=true] - end - (k1,l1), X1 <--> Y1 - (k2,l2), X2 <--> Y2 - (k3,l3), X3 <--> Y3 - (k4,l4), X4 <--> Y4 - (k5,l5), X5 <--> Y5 +# Tests that the correct numbers are computed for num_edges. +let + # Function counting the values in an iterator by stepping through it. + function iterator_count(iterator) + count = 0 + foreach(e -> count+=1, iterator) + return count + end + + # Cartesian and masked grid (test diagonal edges as well). + for lattice in [small_1d_cartesian_grid, small_2d_cartesian_grid, small_3d_cartesian_grid, + random_1d_masked_grid, random_2d_masked_grid, random_3d_masked_grid] + lrs1 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) + lrs2 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice; diagonal_connections=true) + @test lrs1.num_edges == iterator_count(edge_iterator(lrs1)) + @test lrs2.num_edges == iterator_count(edge_iterator(lrs2)) + end + + # Graph grids (cannot test diagonal connections). + for lattice in [small_2d_graph_grid, small_3d_graph_grid, undirected_cycle, small_directed_cycle, unconnected_graph] + lrs1 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) + @test lrs1.num_edges == iterator_count(edge_iterator(lrs1)) end - tr1 = @transport_reaction $(rs.D1) X1 - tr2 = @transport_reaction $(rs.D2) X2 - tr3 = @transport_reaction $(rs.D3) X3 - lrs = LatticeReactionSystem(rs, [tr1, tr2, tr3], grid) - - # Loops through all parameters, ensuring that they have the correct type - p_types = Dict([ModelingToolkit.nameof(p) => typeof(unwrap(p)) for p in parameters(lrs)]) - @test p_types[:k1] == BasicSymbolic{Real} - @test p_types[:l1] == BasicSymbolic{Real} - @test p_types[:k2] == BasicSymbolic{Float64} - @test p_types[:l2] == BasicSymbolic{Float64} - @test p_types[:k3] == BasicSymbolic{Int64} - @test p_types[:l3] == BasicSymbolic{Int64} - @test p_types[:k4] == BasicSymbolic{Float32} - @test p_types[:l4] == BasicSymbolic{Float32} - @test p_types[:k5] == BasicSymbolic{Rational{Int64}} - @test p_types[:l5] == BasicSymbolic{Rational{Int64}} - @test p_types[:D1] == BasicSymbolic{Float32} - @test p_types[:D2] == BasicSymbolic{Real} - @test p_types[:D3] == BasicSymbolic{Rational{Int64}} end -# Checks that programmatically declared parameters (with types) can be used in `TransportReaction`s. -# Checks that LatticeReactionSystem with non-default parameter types can be simulated. -@test_broken let - rs = @reaction_network begin - @parameters p::Float32 +### Tests Edge Value Computation Helper Functions ### + +# Checks that computes the correct values across various types of grids. +let + # Prepares the model and the function that determines the edge values. + rn = @reaction_network begin (p,d), 0 <--> X end - @parameters D::Rational{Int64} - tr = TransportReaction(D, rs.X) - lrs = LatticeReactionSystem(rs, [tr], grid) + tr = @transport_reaction D X + function make_edge_p_value(src_vert, dst_vert) + return prod(src_vert) + prod(dst_vert) + end - p_types = Dict([ModelingToolkit.nameof(p) => typeof(unwrap(p)) for p in parameters(lrs)]) - @test p_types[:p] == BasicSymbolic{Float32} - @test p_types[:d] == BasicSymbolic{Real} - @test p_types[:D] == BasicSymbolic{Rational{Int64}} - - u0 = [:X => [0.25, 0.5, 2.0, 4.0]] - ps = [rs.p => 2.0, rs.d => 1.0, D => 1//2] - - # Currently broken. This requires some non-trivial reworking of internals. - # However, spatial internals have already been reworked (and greatly improved) in an unmerged PR. - # This will be sorted out once that has finished. - @test_broken false - # oprob = ODEProblem(lrs, u0, (0.0, 10.0), ps) - # sol = solve(oprob, Tsit5()) - # @test sol[end] == [1.0, 1.0, 1.0, 1.0] + # Loops through a variety of grids, checks that `make_edge_p_values` yields the correct values. + for grid in [small_1d_cartesian_grid, small_2d_cartesian_grid, small_3d_cartesian_grid, + small_1d_masked_grid, small_2d_masked_grid, small_3d_masked_grid, + random_1d_masked_grid, random_2d_masked_grid, random_3d_masked_grid] + lrs = LatticeReactionSystem(rn, [tr], grid) + flat_to_grid_idx = Catalyst.get_index_converters(lattice(lrs), num_verts(lrs))[1] + edge_values = make_edge_p_values(lrs, make_edge_p_value) + + for e in edge_iterator(lrs) + @test edge_values[e[1], e[2]] == make_edge_p_value(flat_to_grid_idx[e[1]], flat_to_grid_idx[e[2]]) + end + end end -# Tests that LatticeReactionSystem cannot be generated where transport reactions depend on parameters -# that have a type designated in the non-spatial `ReactionSystem`. -@test_broken false -# let -# rs = @reaction_network begin -# @parameters D::Int64 -# (p,d), 0 <--> X -# end -# tr = @transport_reaction D X -# @test_throws Exception LatticeReactionSystem(rs, tr, grid) -# end \ No newline at end of file +# Checks that all species ends up in the correct place in in a pure flow system (checking various dimensions). +let + # Prepares a system with a single species which is transported only. + rn = @reaction_network begin + @species X(t) + end + n = 5 + tr = @transport_reaction D X + tspan = (0.0, 10000.0) + u0 = [:X => 1.0] + + # Checks the 1d case. + lrs = LatticeReactionSystem(rn, [tr], CartesianGrid(n)) + ps = [:D => make_directed_edge_values(lrs, (10.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test isapprox(solve(oprob, Tsit5())[end][5], n, rtol=1e-6) + + # Checks the 2d case (both with 1d and 2d flow). + lrs = LatticeReactionSystem(rn, [tr], CartesianGrid((n,n))) + + ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (0.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test all(isapprox.(solve(oprob, Tsit5())[end][5:5:25], n, rtol=1e-6)) + + ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test isapprox(solve(oprob, Tsit5())[end][25], n^2, rtol=1e-6) + + # Checks the 3d case (both with 1d and 2d flow). + lrs = LatticeReactionSystem(rn, [tr], CartesianGrid((n,n,n))) + + ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (0.0, 0.0), (0.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test all(isapprox.(solve(oprob, Tsit5())[end][5:5:125], n, rtol=1e-6)) + + ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0), (0.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test all(isapprox.(solve(oprob, Tsit5())[end][25:25:125], n^2, rtol=1e-6)) + + ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0), (1.0, 0.0))] + oprob = ODEProblem(lrs, u0, tspan, ps) + @test isapprox(solve(oprob, Tsit5())[end][125], n^3, rtol=1e-6) +end + +# Checks that erroneous input yields errors. +let + rn = @reaction_network begin + (p,d), 0 <--> X + end + tr = @transport_reaction D X + tspan = (0.0, 10000.0) + make_edge_p_value(src_vert, dst_vert) = rand() + + # Graph grids. + lrs = LatticeReactionSystem(rn, [tr], path_graph(5)) + @test_throws Exception make_edge_p_values(lrs, make_edge_p_value,) + @test_throws Exception make_directed_edge_values(lrs, (1.0, 0.0)) + + # Wrong dimensions to `make_directed_edge_values`. + lrs_1d = LatticeReactionSystem(rn, [tr], CartesianGrid(5)) + lrs_2d = LatticeReactionSystem(rn, [tr], fill(true,5,5)) + lrs_3d = LatticeReactionSystem(rn, [tr], CartesianGrid((5,5,5))) + + @test_throws Exception make_directed_edge_values(lrs_1d, (1.0, 0.0), (1.0, 0.0)) + @test_throws Exception make_directed_edge_values(lrs_1d, (1.0, 0.0), (1.0, 0.0), (1.0, 0.0)) + @test_throws Exception make_directed_edge_values(lrs_2d, (1.0, 0.0)) + @test_throws Exception make_directed_edge_values(lrs_2d, (1.0, 0.0), (1.0, 0.0), (1.0, 0.0)) + @test_throws Exception make_directed_edge_values(lrs_3d, (1.0, 0.0)) + @test_throws Exception make_directed_edge_values(lrs_3d, (1.0, 0.0), (1.0, 0.0)) +end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl similarity index 98% rename from test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl rename to test/spatial_modelling/lattice_reaction_systems_ODEs.jl index 66b8f41952..b8114e4eee 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -30,17 +30,16 @@ for grid in [small_2d_graph_grid, short_path, small_directed_cycle, :R => 50 * rand_v_vals(lattice(lrs)), ] for u0 in [u0_1, u0_2, u0_3] - p1 = [:α => 0.1 / 1000, :β => 0.01] - p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lattice(lrs))] - p3 = [ + pV_1 = [:α => 0.1 / 1000, :β => 0.01] + pV_2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lattice(lrs))] + pV_3 = [ :α => 0.1 / 2000 * rand_v_vals(lattice(lrs)), :β => 0.02 * rand_v_vals(lattice(lrs)), ] - for pV in [p1, p2, p3] + for pV in [pV_1, pV_2, pV_3] pE_1 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) pE_2 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) - pE_3 = map(sp -> sp => rand_e_vals(lrs, 0.01), - spatial_param_syms(lrs)) + pE_3 = map(sp -> sp => rand_e_vals(lrs, 0.01), spatial_param_syms(lrs)) for pE in [pE_1, pE_2, pE_3] isempty(spatial_param_syms(lrs)) && (pE = Vector{Pair{Symbol, Float64}}()) oprob = ODEProblem(lrs, u0, (0.0, 500.0), [pV; pE]) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_modelling/lattice_reaction_systems_jumps.jl similarity index 63% rename from test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl rename to test/spatial_modelling/lattice_reaction_systems_jumps.jl index 361b9d822e..9ad28f9ed4 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_modelling/lattice_reaction_systems_jumps.jl @@ -12,29 +12,29 @@ include("../spatial_test_networks.jl") # Tests that there are no errors during runs for a variety of input forms. let - for grid in [small_2d_grid, short_path, small_directed_cycle] + for grid in [small_2d_graph_grid, small_2d_cartesian_grid, small_2d_masked_grid] for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) u0_1 = [:S => 999, :I => 1, :R => 0] - u0_2 = [:S => round.(Int64, 500.0 .+ 500.0 * rand_v_vals(lattice(lrs))), :I => 1, :R => 0, ] - u0_3 = [:S => 950, :I => round.(Int64, 50 * rand_v_vals(lattice(lrs))), :R => round.(Int64, 50 * rand_v_vals(lattice(lrs)))] - u0_4 = [:S => round.(500.0 .+ 500.0 * rand_v_vals(lattice(lrs))), :I => round.(50 * rand_v_vals(lattice(lrs))), :R => round.(50 * rand_v_vals(lattice(lrs)))] - u0_5 = make_u0_matrix(u0_3, vertices(lattice(lrs)), map(s -> Symbol(s.f), species(reactionsystem(lrs)))) - for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] - p1 = [:α => 0.1 / 1000, :β => 0.01] - p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lattice(lrs))] - p3 = [ + u0_2 = [:S => round.(Int64, 500 .+ 500 * rand_v_vals(lattice(lrs))), :I => 1, :R => 0] + u0_3 = [ + :S => round.(Int64, 500 .+ 500 * rand_v_vals(lattice(lrs))), + :I => round.(Int64, 50 * rand_v_vals(lattice(lrs))), + :R => round.(Int64, 50 * rand_v_vals(lattice(lrs))), + ] + for u0 in [u0_1, u0_2, u0_3] + pV_1 = [:α => 0.1 / 1000, :β => 0.01] + pV_2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lattice(lrs))] + pV_3 = [ :α => 0.1 / 2000 * rand_v_vals(lattice(lrs)), :β => 0.02 * rand_v_vals(lattice(lrs)), ] - p4 = make_u0_matrix(p1, vertices(lattice(lrs)), Symbol.(parameters(reactionsystem(lrs)))) - for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. - pE_1 = map(sp -> sp => 0.01, ModelingToolkit.getname.(edge_parameters(lrs))) - pE_2 = map(sp -> sp => 0.01, ModelingToolkit.getname.(edge_parameters(lrs))) - pE_3 = map(sp -> sp => rand_e_vals(lattice(lrs), 0.01), ModelingToolkit.getname.(edge_parameters(lrs))) - pE_4 = make_u0_matrix(pE_3, edges(lattice(lrs)), ModelingToolkit.getname.(edge_parameters(lrs))) - for pE in [pE_1, pE_2, pE_3, pE_4] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + for pV in [pV_1, pV_2, pV_3] + pE_1 = [sp => 0.01 for sp in spatial_param_syms(lrs)] + pE_2 = [sp => rand_e_vals(lrs)/50.0 for sp in spatial_param_syms(lrs)] + for pE in [pE_1, pE_2] + isempty(spatial_param_syms(lrs)) && (pE = Vector{Pair{Symbol, Float64}}()) + dprob = DiscreteProblem(lrs, u0, (0.0, 1.0), [pV; pE]) jprob = JumpProblem(lrs, dprob, NSM()) @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end @@ -51,47 +51,29 @@ end # In this base case, hopping rates should be on the form D_{s,i,j}. let # Prepares the system. - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + grid = small_2d_graph_grid + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, grid) # Prepares various u0 input types. u0_1 = [:I => 2.0, :S => 1.0, :R => 3.0] - u0_2 = [:I => fill(2., nv(small_2d_grid)), :S => 1.0, :R => 3.0] - u0_3 = [1.0, 2.0, 3.0] - u0_4 = [1.0, fill(2., nv(small_2d_grid)), 3.0] - u0_5 = permutedims(hcat(fill(1., nv(small_2d_grid)), fill(2., nv(small_2d_grid)), fill(3., nv(small_2d_grid)))) + u0_2 = [:I => fill(2., nv(grid)), :S => 1.0, :R => 3.0] # Prepare various (compartment) parameter input types. pV_1 = [:β => 0.2, :α => 0.1] - pV_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] - pV_3 = [0.1, 0.2] - pV_4 = [0.1, fill(0.2, nv(small_2d_grid))] - pV_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + pV_2 = [:β => fill(0.2, nv(grid)), :α => 1.0] # Prepare various (diffusion) parameter input types. pE_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pE_2 = [:dI => 0.02, :dS => fill(0.01, ne(small_2d_grid)), :dR => 0.03] - pE_3 = [0.01, 0.02, 0.03] - pE_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] - pE_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) + pE_2 = [:dI => 0.02, :dS => uniform_e_vals(lrs, 0.01), :dR => 0.03] # Checks hopping rates and u0 are correct. true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] - true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) + true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in grid.fadjlist]) true_maj_scaled_rates = [0.1, 0.2] true_maj_reactant_stoch = [[1 => 1, 2 => 1], [2 => 1]] true_maj_net_stoch = [[1 => -1, 2 => 1], [2 => -1, 3 => 1]] - for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] - # Provides parameters as a tupple. - for pV in [pV_1, pV_3], pE in [pE_1, pE_2, pE_3, pE_4, pE_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV,pE)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.prob.u0 == true_u0 - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - @test jprob.massaction_jump.reactant_stoch == true_maj_reactant_stoch - @test all(issetequal(ns1, ns2) for (ns1, ns2) in zip(jprob.massaction_jump.net_stoch, true_maj_net_stoch)) - end - # Provides parameters as a combined vector. - for pV in [pV_1], pE in [pE_1, pE_2] + for u0 in [u0_1, u0_2] + for pV in [pV_1, pV_2], pE in [pE_1, pE_2] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pE; pV]) jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @@ -105,7 +87,7 @@ end ### SpatialMassActionJump Testing ### -# Checks that the correct structure is produced. +# Checks that the correct structure;s is produced. let # Network for reference: # A, ∅ → X @@ -114,12 +96,12 @@ let # 1, X → ∅ # srs = [@transport_reaction dX X] # Create LatticeReactionSystem - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_3d_grid) + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_3d_graph_grid) # Create JumpProblem u0 = [:X => 1, :Y => rand(1:10, num_verts(lrs))] tspan = (0.0, 100.0) - ps = [:A => 1.0, :B => 5.0 .+ rand(num_verts(lrs)), :dX => rand(num_edges(lrs))] + ps = [:A => 1.0, :B => 5.0 .+ rand_v_vals(lattice(lrs)), :dX => rand_e_vals(lrs)] dprob = DiscreteProblem(lrs, u0, tspan, ps) jprob = JumpProblem(lrs, dprob, NSM()) @@ -134,14 +116,15 @@ let @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end -# Checks that simulations gives a correctly heterogeneous solution. +# Checks that heterogeneous vertex parameters work. Checks that birth-death system with different +# birth rates produce different means. let # Create model. birth_death_network = @reaction_network begin (p,d), 0 <--> X end srs = [(@transport_reaction D X)] - lrs = LatticeReactionSystem(birth_death_network, srs, very_small_2d_grid) + lrs = LatticeReactionSystem(birth_death_network, srs, very_small_2d_graph_grid) # Create JumpProblem. u0 = [:X => 1] @@ -153,7 +136,8 @@ let # Simulate model (a few repeats to ensure things don't succeed by change for uniform rates). # Check that higher p gives higher mean. for i = 1:5 - sol = solve(jprob, SSAStepper(); saveat = 1., seed = i*1234) + sol = solve(jprob, SSAStepper(); saveat = 1.) + println() @test mean(getindex.(sol.u, 1)) < mean(getindex.(sol.u, 2)) < mean(getindex.(sol.u, 3)) < mean(getindex.(sol.u, 4)) end end @@ -192,7 +176,7 @@ let tspan = (0.0, 10.0) pV = [:kB => rates[1], :kD => rates[2]] pE = [:D => diffusivity] - dprob = DiscreteProblem(lrs, u0, tspan, (pV, pE)) + dprob = DiscreteProblem(lrs, u0, tspan, [pV; pE]) # NRM could be added, but doesn't work. Might need Cartesian grid. jump_problems = [JumpProblem(lrs, dprob, alg(); save_positions = (false, false)) for alg in [NSM, DirectCRDirect]] diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl b/test/spatial_modelling/lattice_reaction_systems_lattice_types.jl similarity index 100% rename from test/spatial_reaction_systems/lattice_reaction_systems_lattice_types.jl rename to test/spatial_modelling/lattice_reaction_systems_lattice_types.jl diff --git a/test/spatial_reaction_systems/simulate_PDEs.jl b/test/spatial_modelling/simulate_PDEs.jl similarity index 100% rename from test/spatial_reaction_systems/simulate_PDEs.jl rename to test/spatial_modelling/simulate_PDEs.jl diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl deleted file mode 100644 index b2823b3111..0000000000 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ /dev/null @@ -1,423 +0,0 @@ -### Preparations ### - -# Fetch packages. -using Catalyst, Graphs, OrdinaryDiffEq, Test - -# Fetch test networks. -include("../spatial_test_networks.jl") - -# Pre declares a grid. -grids = [very_small_2d_cartesian_grid, very_small_2d_masked_grid, very_small_2d_graph_grid] - -### Tests LatticeReactionSystem Getters Correctness ### - -# Test case 1. -let - rs = @reaction_network begin - (p, 1), 0 <--> X - end - tr = @transport_reaction d X - for grid in grids - lrs = LatticeReactionSystem(rs, [tr], grid) - - @unpack X, p = rs - d = edge_parameters(lrs)[1] - @test issetequal(species(lrs), [X]) - @test issetequal(spatial_species(lrs), [X]) - @test issetequal(parameters(lrs), [p, d]) - @test issetequal(vertex_parameters(lrs), [p]) - @test issetequal(edge_parameters(lrs), [d]) - end -end - -# Test case 2. -let - rs = @reaction_network begin - @parameters p1 p2 [edgeparameter=true] - end - @unpack p1, p2 = rs - - @test !isedgeparameter(p1) - @test isedgeparameter(p2) -end - -# Test case 3. -let - rs = @reaction_network begin - @parameters pX pY dX [edgeparameter=true] dY - (pX, 1), 0 <--> X - (pY, 1), 0 <--> Y - end - tr_1 = @transport_reaction dX X - tr_2 = @transport_reaction dY Y - for grid in grids - lrs = LatticeReactionSystem(rs, [tr_1, tr_2], grid) - - @unpack X, Y, pX, pY, dX, dY = rs - @test issetequal(species(lrs), [X, Y]) - @test issetequal(spatial_species(lrs), [X, Y]) - @test issetequal(parameters(lrs), [pX, pY, dX, dY]) - @test issetequal(vertex_parameters(lrs), [pX, pY, dY]) - @test issetequal(edge_parameters(lrs), [dX]) - end -end - -# Test case 4. -let - rs = @reaction_network begin - @parameters dX p - (pX, 1), 0 <--> X - (pY, 1), 0 <--> Y - end - tr_1 = @transport_reaction dX X - for grid in grids - lrs = LatticeReactionSystem(rs, [tr_1], grid) - - @unpack dX, p, X, Y, pX, pY = rs - @test issetequal(species(lrs), [X, Y]) - @test issetequal(spatial_species(lrs), [X]) - @test issetequal(parameters(lrs), [dX, p, pX, pY]) - @test issetequal(vertex_parameters(lrs), [dX, p, pX, pY]) - @test issetequal(edge_parameters(lrs), []) - end -end - -# Test case 5. -let - rs = @reaction_network begin - @species W(t) - @parameters pX pY dX [edgeparameter=true] dY - (pX, 1), 0 <--> X - (pY, 1), 0 <--> Y - (pZ, 1), 0 <--> Z - (pV, 1), 0 <--> V - end - @unpack dX, X, V = rs - @parameters dV dW - @variables t - @species W(t) - tr_1 = TransportReaction(dX, X) - tr_2 = @transport_reaction dY Y - tr_3 = @transport_reaction dZ Z - tr_4 = TransportReaction(dV, V) - tr_5 = TransportReaction(dW, W) - for grid in grids - lrs = LatticeReactionSystem(rs, [tr_1, tr_2, tr_3, tr_4, tr_5], grid) - - @unpack pX, pY, pZ, pV, dX, dY, X, Y, Z, V = rs - dZ, dV, dW = edge_parameters(lrs)[2:end] - @test issetequal(species(lrs), [W, X, Y, Z, V]) - @test issetequal(spatial_species(lrs), [X, Y, Z, V, W]) - @test issetequal(parameters(lrs), [pX, pY, dX, dY, pZ, pV, dZ, dV, dW]) - @test issetequal(vertex_parameters(lrs), [pX, pY, dY, pZ, pV]) - @test issetequal(edge_parameters(lrs), [dX, dZ, dV, dW]) - end -end - -# Test case 6. -let - rs = @reaction_network customname begin - (p, 1), 0 <--> X - end - tr = @transport_reaction d X - for grid in grids - lrs = LatticeReactionSystem(rs, [tr], grid) - - @test nameof(lrs) == :customname - end -end - -### Tests Spatial Reactions Getters Correctness ### - -# Test case 1. -let - tr_1 = @transport_reaction dX X - tr_2 = @transport_reaction dY1*dY2 Y - - # @test ModelingToolkit.getname.(species(tr_1)) == ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] # species(::TransportReaction) currently not supported. - # @test ModelingToolkit.getname.(species(tr_2)) == ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] - @test ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] - @test ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] - @test ModelingToolkit.getname.(parameters(tr_1)) == [:dX] - @test ModelingToolkit.getname.(parameters(tr_2)) == [:dY1, :dY2] - - # @test issetequal(species(tr_1), [tr_1.species]) - # @test issetequal(species(tr_2), [tr_2.species]) - @test issetequal(spatial_species(tr_1), [tr_1.species]) - @test issetequal(spatial_species(tr_2), [tr_2.species]) -end - -# Test case 2. -let - rs = @reaction_network begin - @species X(t) Y(t) - @parameters dX dY1 dY2 - end - @unpack X, Y, dX, dY1, dY2 = rs - tr_1 = TransportReaction(dX, X) - tr_2 = TransportReaction(dY1*dY2, Y) - # @test isequal(species(tr_1), [X]) - # @test isequal(species(tr_1), [X]) - @test issetequal(spatial_species(tr_2), [Y]) - @test issetequal(spatial_species(tr_2), [Y]) - @test issetequal(parameters(tr_1), [dX]) - @test issetequal(parameters(tr_2), [dY1, dY2]) -end - -### Tests Spatial Reactions Generation ### - -# Tests TransportReaction with non-trivial rate. -let - rs = @reaction_network begin - @parameters dV dE [edgeparameter=true] - (p,1), 0 <--> X - end - @unpack dV, dE, X = rs - - tr = TransportReaction(dV*dE, X) - @test isequal(tr.rate, dV*dE) -end - -# Tests transport_reactions function for creating TransportReactions. -let - rs = @reaction_network begin - @parameters d - (p,1), 0 <--> X - end - @unpack d, X = rs - trs = TransportReactions([(d, X), (d, X)]) - @test isequal(trs[1], trs[2]) -end - -# Test reactions with constants in rate. -let - @variables t - @species X(t) Y(t) - - tr_1 = TransportReaction(1.5, X) - tr_1_macro = @transport_reaction 1.5 X - @test isequal(tr_1.rate, tr_1_macro.rate) - @test isequal(tr_1.species, tr_1_macro.species) - - tr_2 = TransportReaction(π, Y) - tr_2_macro = @transport_reaction π Y - @test isequal(tr_2.rate, tr_2_macro.rate) - @test isequal(tr_2.species, tr_2_macro.species) -end - -### Test Interpolation ### - -# Does not currently work. The 3 tr_macro_ lines generate errors. -# Test case 1. -let - rs = @reaction_network begin - @species X(t) Y(t) Z(t) - @parameters dX dY1 dY2 dZ - end - @unpack X, Y, Z, dX, dY1, dY2, dZ = rs - rate1 = dX - rate2 = dY1*dY2 - species3 = Z - tr_1 = TransportReaction(dX, X) - tr_2 = TransportReaction(dY1*dY2, Y) - tr_3 = TransportReaction(dZ, Z) - tr_macro_1 = @transport_reaction $dX X - tr_macro_2 = @transport_reaction $(rate2) Y - # tr_macro_3 = @transport_reaction dZ $species3 # Currently does not work, something with meta programming. - - @test isequal(tr_1, tr_macro_1) - @test isequal(tr_2, tr_macro_2) # Unsure why these fails, since for components equality hold: `isequal(tr_1.species, tr_macro_1.species)` and `isequal(tr_1.rate, tr_macro_1.rate)` are both true. - # @test isequal(tr_3, tr_macro_3) -end - -### Tests Error generation ### - -# Test creation of TransportReaction with non-parameters in rate. -# Tests that it works even when rate is highly nested. -let - @variables t - @species X(t) Y(t) - @parameters D1 D2 D3 - @test_throws ErrorException TransportReaction(D1 + D2*(D3 + Y), X) - @test_throws ErrorException TransportReaction(Y, X) -end - -# Network where diffusion species is not declared in non-spatial network. -let - rs = @reaction_network begin - (p, d), 0 <--> X - end - tr = @transport_reaction D Y - for grid in grids - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) - end -end - -# Network where the rate depend on a species -let - rs = @reaction_network begin - @species Y(t) - (p, d), 0 <--> X - end - tr = @transport_reaction D*Y X - for grid in grids - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) - end -end - -# Network with edge parameter in non-spatial reaction rate. -let - rs = @reaction_network begin - @parameters p [edgeparameter=true] - (p, d), 0 <--> X - end - tr = @transport_reaction D X - for grid in grids - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) - end -end - -# Network where metadata has been added in rs (which is not seen in transport reaction). -let - rs = @reaction_network begin - @species X(t) [description="Species with added metadata"] - (p, d), 0 <--> X - end - tr = @transport_reaction D X - for grid in grids - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) - - rs = @reaction_network begin - @parameters D [description="Parameter with added metadata"] - (p, d), 0 <--> X - end - tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) - end -end - - -### Tests Grid Vertex and Edge Number Computation ### - -# Tests that the correct numbers are computed for num_edges. -let - # Function counting the values in an iterator by stepping through it. - function iterator_count(iterator) - count = 0 - foreach(e -> count+=1, iterator) - return count - end - - # Cartesian and masked grid (test diagonal edges as well). - for lattice in [small_1d_cartesian_grid, small_2d_cartesian_grid, small_3d_cartesian_grid, - random_1d_masked_grid, random_2d_masked_grid, random_3d_masked_grid] - lrs1 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) - lrs2 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice; diagonal_connections=true) - @test lrs1.num_edges == iterator_count(edge_iterator(lrs1)) - @test lrs2.num_edges == iterator_count(edge_iterator(lrs2)) - end - - # Graph grids (cannot test diagonal connections). - for lattice in [small_2d_graph_grid, small_3d_graph_grid, undirected_cycle, small_directed_cycle, unconnected_graph] - lrs1 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) - @test lrs1.num_edges == iterator_count(edge_iterator(lrs1)) - end -end - -### Tests Edge Value Computation Helper Functions ### - -# Checks that computes the correct values across various types of grids. -let - # Prepares the model and the function that determines the edge values. - rn = @reaction_network begin - (p,d), 0 <--> X - end - tr = @transport_reaction D X - function make_edge_p_value(src_vert, dst_vert) - return prod(src_vert) + prod(dst_vert) - end - - # Loops through a variety of grids, checks that `make_edge_p_values` yields the correct values. - for grid in [small_1d_cartesian_grid, small_2d_cartesian_grid, small_3d_cartesian_grid, - small_1d_masked_grid, small_2d_masked_grid, small_3d_masked_grid, - random_1d_masked_grid, random_2d_masked_grid, random_3d_masked_grid] - lrs = LatticeReactionSystem(rn, [tr], grid) - flat_to_grid_idx = Catalyst.get_index_converters(lattice(lrs), num_verts(lrs))[1] - edge_values = make_edge_p_values(lrs, make_edge_p_value) - - for e in edge_iterator(lrs) - @test edge_values[e[1], e[2]] == make_edge_p_value(flat_to_grid_idx[e[1]], flat_to_grid_idx[e[2]]) - end - end -end - -# Checks that all species ends up in the correct place in in a pure flow system (checking various dimensions). -let - # Prepares a system with a single species which is transported only. - rn = @reaction_network begin - @species X(t) - end - n = 5 - tr = @transport_reaction D X - tspan = (0.0, 10000.0) - u0 = [:X => 1.0] - - # Checks the 1d case. - lrs = LatticeReactionSystem(rn, [tr], CartesianGrid(n)) - ps = [:D => make_directed_edge_values(lrs, (10.0, 0.0))] - oprob = ODEProblem(lrs, u0, tspan, ps) - @test isapprox(solve(oprob, Tsit5())[end][5], n, rtol=1e-6) - - # Checks the 2d case (both with 1d and 2d flow). - lrs = LatticeReactionSystem(rn, [tr], CartesianGrid((n,n))) - - ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (0.0, 0.0))] - oprob = ODEProblem(lrs, u0, tspan, ps) - @test all(isapprox.(solve(oprob, Tsit5())[end][5:5:25], n, rtol=1e-6)) - - ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0))] - oprob = ODEProblem(lrs, u0, tspan, ps) - @test isapprox(solve(oprob, Tsit5())[end][25], n^2, rtol=1e-6) - - # Checks the 3d case (both with 1d and 2d flow). - lrs = LatticeReactionSystem(rn, [tr], CartesianGrid((n,n,n))) - - ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (0.0, 0.0), (0.0, 0.0))] - oprob = ODEProblem(lrs, u0, tspan, ps) - @test all(isapprox.(solve(oprob, Tsit5())[end][5:5:125], n, rtol=1e-6)) - - ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0), (0.0, 0.0))] - oprob = ODEProblem(lrs, u0, tspan, ps) - @test all(isapprox.(solve(oprob, Tsit5())[end][25:25:125], n^2, rtol=1e-6)) - - ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0), (1.0, 0.0))] - oprob = ODEProblem(lrs, u0, tspan, ps) - @test isapprox(solve(oprob, Tsit5())[end][125], n^3, rtol=1e-6) -end - -# Checks that erroneous input yields errors. -let - rn = @reaction_network begin - (p,d), 0 <--> X - end - tr = @transport_reaction D X - tspan = (0.0, 10000.0) - make_edge_p_value(src_vert, dst_vert) = rand() - - # Graph grids. - lrs = LatticeReactionSystem(rn, [tr], path_graph(5)) - @test_throws Exception make_edge_p_values(lrs, make_edge_p_value,) - @test_throws Exception make_directed_edge_values(lrs, (1.0, 0.0)) - - # Wrong dimensions to `make_directed_edge_values`. - lrs_1d = LatticeReactionSystem(rn, [tr], CartesianGrid(5)) - lrs_2d = LatticeReactionSystem(rn, [tr], fill(true,5,5)) - lrs_3d = LatticeReactionSystem(rn, [tr], CartesianGrid((5,5,5))) - - @test_throws Exception make_directed_edge_values(lrs_1d, (1.0, 0.0), (1.0, 0.0)) - @test_throws Exception make_directed_edge_values(lrs_1d, (1.0, 0.0), (1.0, 0.0), (1.0, 0.0)) - @test_throws Exception make_directed_edge_values(lrs_2d, (1.0, 0.0)) - @test_throws Exception make_directed_edge_values(lrs_2d, (1.0, 0.0), (1.0, 0.0), (1.0, 0.0)) - @test_throws Exception make_directed_edge_values(lrs_3d, (1.0, 0.0)) - @test_throws Exception make_directed_edge_values(lrs_3d, (1.0, 0.0), (1.0, 0.0)) -end \ No newline at end of file diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index bb106c9ee6..924fecb045 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -31,6 +31,15 @@ function rand_e_vals(lrs::LatticeReactionSystem) return e_vals end +# Generates edge values, where each edge have the same value. +function uniform_e_vals(lrs::LatticeReactionSystem, val) + e_vals = spzeros(num_verts(lrs), num_verts(lrs)) + for e in edge_iterator(lrs) + e_vals[e[1], e[2]] = val + end + return e_vals +end + # Gets a symbol list of spatial parameters. function spatial_param_syms(lrs::LatticeReactionSystem) ModelingToolkit.getname.(edge_parameters(lrs)) From 1e921358e24e75fafe0f035c42f843467b6135a8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 14 Jun 2024 09:28:21 -0400 Subject: [PATCH 288/446] init --- .../bifurcation_kit_extension.jl | 3 +- .../homotopy_continuation_extension.jl | 3 +- .../structural_identifiability_extension.jl | 2 +- src/reactionsystem_conversions.jl | 52 ++++++++++----- src/steady_state_stability.jl | 4 +- test/network_analysis/conservation_laws.jl | 64 +++++++++++++++---- 6 files changed, 93 insertions(+), 35 deletions(-) diff --git a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl index be0becb3a9..6aeafa9bc2 100644 --- a/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl +++ b/ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl @@ -22,7 +22,8 @@ function BK.BifurcationProblem(rs::ReactionSystem, u0_bif, ps, bif_par, args...; # Creates NonlinearSystem. Catalyst.conservationlaw_errorcheck(rs, vcat(ps, u0)) - nsys = convert(NonlinearSystem, rs; remove_conserved = true, defaults = Dict(u0)) + nsys = convert(NonlinearSystem, rs; defaults = Dict(u0), + remove_conserved = true, remove_conserved_warn = false) nsys = complete(nsys) # Makes BifurcationProblem (this call goes through the ModelingToolkit-based BifurcationKit extension). diff --git a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl index db7a8ff0c4..05ae3358e6 100644 --- a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl +++ b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl @@ -49,7 +49,8 @@ end # For a given reaction system, parameter values, and initial conditions, find the polynomial that HC solves to find steady states. function steady_state_polynomial(rs::ReactionSystem, ps, u0) rs = Catalyst.expand_registered_functions(rs) - ns = complete(convert(NonlinearSystem, rs; remove_conserved = true)) + ns = complete(convert(NonlinearSystem, rs; + remove_conserved = true, remove_conserved_warn = false)) pre_varmap = [symmap_to_varmap(rs, u0)..., symmap_to_varmap(rs, ps)...] Catalyst.conservationlaw_errorcheck(rs, pre_varmap) p_vals = ModelingToolkit.varmap_to_vars(pre_varmap, parameters(ns); diff --git a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl index c5906eaf6e..29269b91eb 100644 --- a/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl +++ b/ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl @@ -167,7 +167,7 @@ function make_osys(rs::ReactionSystem; remove_conserved = true) error("Identifiability should only be computed for complete systems. A ReactionSystem can be marked as complete using the `complete` function.") end rs = complete(Catalyst.expand_registered_functions(flatten(rs))) - osys = complete(convert(ODESystem, rs; remove_conserved)) + osys = complete(convert(ODESystem, rs; remove_conserved, remove_conserved_warn = false)) vars = [unknowns(rs); parameters(rs)] # Computes equations for system conservation laws. diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 68b8a819a2..de1f1da4e8 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -450,6 +450,17 @@ diff_2_zero(expr) = (Symbolics.is_derivative(expr) ? 0 : expr) COMPLETENESS_ERROR = "A ReactionSystem must be complete before it can be converted to other system types. A ReactionSystem can be marked as complete using the `complete` function." +# Used to, when required, display a warning about conservation law removeal and remake. +function check_cons_warning(remove_conserved, remove_conserved_warn) + (remove_conserved && remove_conserved_warn) || return + @warn "You are creating a system while eliminating conserved quantities. While this is possible, + if you use the created system to create a problem (e.g. an `ODEProblem`), you *should not* + modify that problem's species values (e.g. using `remake`). Modification of parameter values + is still possible. You might get this warning when creating a problem directly. + + You can remove this warning by setting `remove_conserved_warn = false`." +end + ### System Conversions ### """ @@ -467,15 +478,20 @@ Keyword args and default values: - `remove_conserved=false`, if set to `true` will calculate conservation laws of the underlying set of reactions (ignoring constraint equations), and then apply them to reduce the number of equations. +- `remove_conserved_warn = true`: If `true`, if also `remove_conserved = true`, there will be + a warning regarding limitations of modifying problems generated from the created system. """ function Base.convert(::Type{<:ODESystem}, rs::ReactionSystem; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - include_zero_odes = true, remove_conserved = false, checks = false, - default_u0 = Dict(), default_p = Dict(), + include_zero_odes = true, remove_conserved = false, remove_conserved_warn = true, + checks = false, default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)), kwargs...) + # Error checks. iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, ODESystem) + check_cons_warning(remove_conserved, remove_conserved_warn) + fullrs = Catalyst.flatten(rs) remove_conserved && conservationlaws(fullrs) ists, ispcs = get_indep_sts(fullrs, remove_conserved) @@ -509,16 +525,19 @@ Keyword args and default values: - `remove_conserved=false`, if set to `true` will calculate conservation laws of the underlying set of reactions (ignoring constraint equations), and then apply them to reduce the number of equations. +- `remove_conserved_warn = true`: If `true`, if also `remove_conserved = true`, there will be + a warning regarding limitations of modifying problems generated from the created system. """ function Base.convert(::Type{<:NonlinearSystem}, rs::ReactionSystem; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), include_zero_odes = true, remove_conserved = false, checks = false, - default_u0 = Dict(), default_p = Dict(), + remove_conserved_warn = true, default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)), all_differentials_permitted = false, kwargs...) # Error checks. iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, NonlinearSystem) + check_cons_warning(remove_conserved, remove_conserved_warn) if !isautonomous(rs) error("Attempting to convert a non-autonomous `ReactionSystem` (e.g. where some rate depend on $(get_iv(rs))) to a `NonlinearSystem`. This is not possible. if you are intending to compute system steady states, consider creating and solving a `SteadyStateProblem.") end @@ -589,17 +608,19 @@ Notes: - `remove_conserved=false`, if set to `true` will calculate conservation laws of the underlying set of reactions (ignoring constraint equations), and then apply them to reduce the number of equations. -- Does not currently support `ReactionSystem`s that include coupled algebraic or - differential equations. +- `remove_conserved_warn = true`: If `true`, if also `remove_conserved = true`, there will be + a warning regarding limitations of modifying problems generated from the created system. """ function Base.convert(::Type{<:SDESystem}, rs::ReactionSystem; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), include_zero_odes = true, checks = false, remove_conserved = false, - default_u0 = Dict(), default_p = Dict(), + remove_conserved_warn = true, default_u0 = Dict(), default_p = Dict(), defaults = _merge(Dict(default_u0), Dict(default_p)), kwargs...) + # Error checks. iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, SDESystem) + check_cons_warning(remove_conserved, remove_conserved_warn) flatrs = Catalyst.flatten(rs) @@ -651,7 +672,6 @@ function Base.convert(::Type{<:JumpSystem}, rs::ReactionSystem; name = nameof(rs kwargs...) iscomplete(rs) || error(COMPLETENESS_ERROR) spatial_convert_err(rs::ReactionSystem, JumpSystem) - (remove_conserved !== nothing) && throw(ArgumentError("Catalyst does not support removing conserved species when converting to JumpSystems.")) @@ -684,12 +704,12 @@ function DiffEqBase.ODEProblem(rs::ReactionSystem, u0, tspan, p = DiffEqBase.NullParameters(), args...; check_length = false, name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - include_zero_odes = true, remove_conserved = false, + include_zero_odes = true, remove_conserved = false, remove_conserved_warn = true, checks = false, structural_simplify = false, kwargs...) u0map = symmap_to_varmap(rs, u0) pmap = symmap_to_varmap(rs, p) osys = convert(ODESystem, rs; name, combinatoric_ratelaws, include_zero_odes, checks, - remove_conserved) + remove_conserved, remove_conserved_warn) # Handles potential differential algebraic equations (which requires `structural_simplify`). if structural_simplify @@ -708,12 +728,12 @@ function DiffEqBase.NonlinearProblem(rs::ReactionSystem, u0, p = DiffEqBase.NullParameters(), args...; name = nameof(rs), include_zero_odes = true, combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - remove_conserved = false, checks = false, + remove_conserved = false, remove_conserved_warn = true, checks = false, check_length = false, all_differentials_permitted = false, kwargs...) u0map = symmap_to_varmap(rs, u0) pmap = symmap_to_varmap(rs, p) nlsys = convert(NonlinearSystem, rs; name, combinatoric_ratelaws, include_zero_odes, - checks, all_differentials_permitted, remove_conserved) + checks, all_differentials_permitted, remove_conserved, remove_conserved_warn) nlsys = complete(nlsys) return NonlinearProblem(nlsys, u0map, pmap, args...; check_length, kwargs...) @@ -723,12 +743,12 @@ end function DiffEqBase.SDEProblem(rs::ReactionSystem, u0, tspan, p = DiffEqBase.NullParameters(), args...; name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - include_zero_odes = true, checks = false, check_length = false, - remove_conserved = false, structural_simplify = false, kwargs...) + include_zero_odes = true, checks = false, check_length = false, remove_conserved = false, + remove_conserved_warn = true, structural_simplify = false, kwargs...) u0map = symmap_to_varmap(rs, u0) pmap = symmap_to_varmap(rs, p) sde_sys = convert(SDESystem, rs; name, combinatoric_ratelaws, - include_zero_odes, checks, remove_conserved) + include_zero_odes, checks, remove_conserved, remove_conserved_warn) # Handles potential differential algebraic equations (which requires `structural_simplify`). if structural_simplify @@ -772,12 +792,12 @@ function DiffEqBase.SteadyStateProblem(rs::ReactionSystem, u0, p = DiffEqBase.NullParameters(), args...; check_length = false, name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs), - remove_conserved = false, include_zero_odes = true, + remove_conserved = false, remove_conserved_warn = true, include_zero_odes = true, checks = false, structural_simplify = false, kwargs...) u0map = symmap_to_varmap(rs, u0) pmap = symmap_to_varmap(rs, p) osys = convert(ODESystem, rs; name, combinatoric_ratelaws, include_zero_odes, checks, - remove_conserved) + remove_conserved, remove_conserved_warn) # Handles potential differential algebraic equations (which requires `structural_simplify`). if structural_simplify diff --git a/src/steady_state_stability.jl b/src/steady_state_stability.jl index 42ffec2d0e..de9a229ef8 100644 --- a/src/steady_state_stability.jl +++ b/src/steady_state_stability.jl @@ -117,8 +117,8 @@ function steady_state_jac(rs::ReactionSystem; u0 = [sp => 0.0 for sp in unknowns # Creates an `ODEProblem` with a Jacobian. Dummy values for `u0` and `ps` must be provided. ps = [p => 0.0 for p in parameters(rs)] - return ODEProblem(rs, u0, 0, ps; jac = true, remove_conserved = true, - combinatoric_ratelaws = combinatoric_ratelaws) + return ODEProblem(rs, u0, 0, ps; jac = true, combinatoric_ratelaws, + remove_conserved = true, remove_conserved_warn = false) end # Converts a `u` vector from a vector of values to a map. diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index 0bc563642c..94cb33e614 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -11,6 +11,9 @@ seed = rand(rng, 1:100) # Fetch test networks. include("../test_networks.jl") +# Except where we test the warnings, we do not want to print this warning. +remove_conserved_warn = false + ### Basic Tests ### # Tests basic functionality on system with known conservation laws. @@ -114,23 +117,23 @@ let tspan = (0.0, 20.0) # Simulates model using ODEs and checks that simulations are identical. - osys = complete(convert(ODESystem, rn; remove_conserved = true)) + osys = complete(convert(ODESystem, rn; remove_conserved = true, remove_conserved_warn)) oprob1 = ODEProblem(osys, u0, tspan, p) oprob2 = ODEProblem(rn, u0, tspan, p) - oprob3 = ODEProblem(rn, u0, tspan, p; remove_conserved = true) + oprob3 = ODEProblem(rn, u0, tspan, p; remove_conserved = true, remove_conserved_warn) osol1 = solve(oprob1, Tsit5(); abstol = 1e-8, reltol = 1e-8, saveat= 0.2) osol2 = solve(oprob2, Tsit5(); abstol = 1e-8, reltol = 1e-8, saveat= 0.2) osol3 = solve(oprob3, Tsit5(); abstol = 1e-8, reltol = 1e-8, saveat= 0.2) @test osol1[sps] ≈ osol2[sps] ≈ osol3[sps] # Checks that steady states found using nonlinear solving and steady state simulations are identical. - nsys = complete(convert(NonlinearSystem, rn; remove_conserved = true)) + nsys = complete(convert(NonlinearSystem, rn; remove_conserved = true, remove_conserved_warn)) nprob1 = NonlinearProblem{true}(nsys, u0, p) nprob2 = NonlinearProblem(rn, u0, p) - nprob3 = NonlinearProblem(rn, u0, p; remove_conserved = true) + nprob3 = NonlinearProblem(rn, u0, p; remove_conserved = true, remove_conserved_warn) ssprob1 = SteadyStateProblem{true}(osys, u0, p) ssprob2 = SteadyStateProblem(rn, u0, p) - ssprob3 = SteadyStateProblem(rn, u0, p; remove_conserved = true) + ssprob3 = SteadyStateProblem(rn, u0, p; remove_conserved = true, remove_conserved_warn) nsol1 = solve(nprob1, NewtonRaphson(); abstol = 1e-8) # Nonlinear problems cannot find steady states properly without removing conserved species. nsol3 = solve(nprob3, NewtonRaphson(); abstol = 1e-8) @@ -142,10 +145,10 @@ let # Creates SDEProblems using various approaches. u0_sde = [A => 100.0, B => 20.0, C => 5.0, D => 10.0, E => 3.0, F1 => 8.0, F2 => 2.0, F3 => 20.0] - ssys = complete(convert(SDESystem, rn; remove_conserved = true)) + ssys = complete(convert(SDESystem, rn; remove_conserved = true, remove_conserved_warn)) sprob1 = SDEProblem(ssys, u0_sde, tspan, p) sprob2 = SDEProblem(rn, u0_sde, tspan, p) - sprob3 = SDEProblem(rn, u0_sde, tspan, p; remove_conserved = true) + sprob3 = SDEProblem(rn, u0_sde, tspan, p; remove_conserved = true, remove_conserved_warn) # Checks that the SDEs f and g function evaluates to the same thing. ind_us = ModelingToolkit.get_unknowns(ssys) @@ -186,9 +189,9 @@ let u0_2 = [rn.X => 2.0, rn.Y => 3.0, rn.XY => 4.0] u0_3 = [:X => 2.0, :Y => 3.0, :XY => 4.0] ps = (kB => 2, kD => 1.5) - oprob1 = ODEProblem(rn, u0_1, 10.0, ps; remove_conserved = true) - oprob2 = ODEProblem(rn, u0_2, 10.0, ps; remove_conserved = true) - oprob3 = ODEProblem(rn, u0_3, 10.0, ps; remove_conserved = true) + oprob1 = ODEProblem(rn, u0_1, 10.0, ps; remove_conserved = true, remove_conserved_warn) + oprob2 = ODEProblem(rn, u0_2, 10.0, ps; remove_conserved = true, remove_conserved_warn) + oprob3 = ODEProblem(rn, u0_3, 10.0, ps; remove_conserved = true, remove_conserved_warn) @test solve(oprob1)[sps] ≈ solve(oprob2)[sps] ≈ solve(oprob3)[sps] end @@ -200,7 +203,7 @@ let end u0 = Dict([:X1 => 100.0, :X2 => 120.0]) ps = [:k1 => 0.2, :k2 => 0.15] - sprob = SDEProblem(rn, u0, 10.0, ps; remove_conserved = true) + sprob = SDEProblem(rn, u0, 10.0, ps; remove_conserved = true, remove_conserved_warn) # Checks that conservation laws hold in all simulations. sol = solve(sprob, ImplicitEM(); seed) @@ -213,7 +216,7 @@ let rn = @reaction_network begin (k1,k2), X1 <--> X2 end - osys = complete(convert(ODESystem, rn; remove_conserved = true)) + osys = complete(convert(ODESystem, rn; remove_conserved = true, remove_conserved_warn)) u0 = [osys.X1 => 1.0, osys.X2 => 1.0] ps_1 = [osys.k1 => 2.0, osys.k2 => 3.0] ps_2 = [osys.k1 => 2.0, osys.k2 => 3.0, osys.Γ[1] => 4.0] @@ -234,7 +237,7 @@ let rn = @reaction_network begin (k1,k2), X1 <--> X2 end - @test_throws ArgumentError convert(JumpSystem, rn; remove_conserved = true) + @test_throws ArgumentError convert(JumpSystem, rn; remove_conserved = true, remove_conserved_warn) end # Checks that `conserved` metadata is added correctly to parameters. @@ -245,7 +248,7 @@ let (k1,k2), X1 <--> X2 (k1,k2), Y1 <--> Y2 end - osys = convert(ODESystem, rs; remove_conserved = true) + osys = convert(ODESystem, rs; remove_conserved = true, remove_conserved_warn) # Checks that the correct parameters have the `conserved` metadata. @test Catalyst.isconserved(osys.Γ[1]) @@ -253,3 +256,36 @@ let @test !Catalyst.isconserved(osys.k1) @test !Catalyst.isconserved(osys.k2) end + +# Checks that conservation law elimination warnings are generated in the correct cases. +let + # Prepare model. + rn = @reaction_network begin + (k1,k2), X1 <--> X2 + end + u0 = [:X1 => 1.0, :X2 => 2.0] + tspan = (0.0, 1.0) + ps = [:k1 => 3.0, :k2 => 4.0] + + # Check warnings in system conversion. + for XSystem in [ODESystem, SDESystem, NonlinearSystem] + @test_nowarn convert(XSystem, rn) + @test_logs (:warn, r"You are creating a system while eliminating conserved quantities. While *") convert(XSystem, rn; remove_conserved = true) + @test_nowarn convert(XSystem, rn; remove_conserved_warn = false) + @test_nowarn convert(XSystem, rn; remove_conserved = true, remove_conserved_warn = false) + end + + # Checks during problem creation (separate depending on whether they have a time span or not). + for XProblem in [ODEProblem, SDEProblem] + @test_nowarn XProblem(rn, u0, tspan, ps) + @test_logs (:warn, r"You are creating a system while eliminating conserved quantities. While *") XProblem(rn, u0, tspan, ps; remove_conserved = true) + @test_nowarn XProblem(rn, u0, tspan, ps; remove_conserved_warn = false) + @test_nowarn XProblem(rn, u0, tspan, ps; remove_conserved = true, remove_conserved_warn = false) + end + for XProblem in [NonlinearProblem, SteadyStateProblem] + @test_nowarn XProblem(rn, u0, ps) + @test_logs (:warn, r"You are creating a system while eliminating conserved quantities. While *") XProblem(rn, u0, ps; remove_conserved = true) + @test_nowarn XProblem(rn, u0, ps; remove_conserved_warn = false) + @test_nowarn XProblem(rn, u0, ps; remove_conserved = true, remove_conserved_warn = false) + end +end \ No newline at end of file From e475df80ef2d8cf0f7d4ee97eafe035d464ece2d Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 14 Jun 2024 09:33:10 -0400 Subject: [PATCH 289/446] formatting --- .../homotopy_continuation_extension.jl | 2 +- src/reactionsystem_conversions.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl index 05ae3358e6..916a124423 100644 --- a/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl +++ b/ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl @@ -49,7 +49,7 @@ end # For a given reaction system, parameter values, and initial conditions, find the polynomial that HC solves to find steady states. function steady_state_polynomial(rs::ReactionSystem, ps, u0) rs = Catalyst.expand_registered_functions(rs) - ns = complete(convert(NonlinearSystem, rs; + ns = complete(convert(NonlinearSystem, rs; remove_conserved = true, remove_conserved_warn = false)) pre_varmap = [symmap_to_varmap(rs, u0)..., symmap_to_varmap(rs, ps)...] Catalyst.conservationlaw_errorcheck(rs, pre_varmap) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index de1f1da4e8..8e7fb773c7 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -457,7 +457,7 @@ function check_cons_warning(remove_conserved, remove_conserved_warn) if you use the created system to create a problem (e.g. an `ODEProblem`), you *should not* modify that problem's species values (e.g. using `remake`). Modification of parameter values is still possible. You might get this warning when creating a problem directly. - + You can remove this warning by setting `remove_conserved_warn = false`." end From eeb2c41f30129274d5fae7b9352993c14b6546c7 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 14 Jun 2024 10:40:39 -0400 Subject: [PATCH 290/446] update project version req --- docs/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index b92da6ef5d..7d86a95a90 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -39,7 +39,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" BenchmarkTools = "1.5" BifurcationKit = "0.3.4" CairoMakie = "0.12" -Catalyst = "13" +Catalyst = "14" DataFrames = "1.6" DiffEqParamEstim = "2.2" Distributions = "0.25" From a31019e2093cfeeb4400903fc25c7243888e4aa8 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 14 Jun 2024 13:16:47 -0400 Subject: [PATCH 291/446] Update src/reactionsystem_conversions.jl Co-authored-by: Sam Isaacson --- src/reactionsystem_conversions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 8e7fb773c7..51d9cc18e2 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -455,7 +455,7 @@ function check_cons_warning(remove_conserved, remove_conserved_warn) (remove_conserved && remove_conserved_warn) || return @warn "You are creating a system while eliminating conserved quantities. While this is possible, if you use the created system to create a problem (e.g. an `ODEProblem`), you *should not* - modify that problem's species values (e.g. using `remake`). Modification of parameter values + modify that problem's initial conditions for species (e.g. using `remake`). Changing initial conditions must be done by creating a new Problem from your reaction system or the ModelingToolkit system you converted it into with the new initial condition map. Modification of parameter values is still possible. You might get this warning when creating a problem directly. You can remove this warning by setting `remove_conserved_warn = false`." From e01440834baf10c6ae0481851c991464d5729ae8 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 14 Jun 2024 13:16:54 -0400 Subject: [PATCH 292/446] Update src/reactionsystem_conversions.jl Co-authored-by: Sam Isaacson --- src/reactionsystem_conversions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 51d9cc18e2..40bcc15bfe 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -456,7 +456,7 @@ function check_cons_warning(remove_conserved, remove_conserved_warn) @warn "You are creating a system while eliminating conserved quantities. While this is possible, if you use the created system to create a problem (e.g. an `ODEProblem`), you *should not* modify that problem's initial conditions for species (e.g. using `remake`). Changing initial conditions must be done by creating a new Problem from your reaction system or the ModelingToolkit system you converted it into with the new initial condition map. Modification of parameter values - is still possible. You might get this warning when creating a problem directly. + is still possible, and directly setting numerical values for any conservation law constants will work. You might get this warning when creating a problem directly. You can remove this warning by setting `remove_conserved_warn = false`." end From 2032f8b49530e1fc711f9ddc9a7ac5d15ba94fa3 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 14 Jun 2024 13:17:02 -0400 Subject: [PATCH 293/446] Update src/reactionsystem_conversions.jl Co-authored-by: Sam Isaacson --- src/reactionsystem_conversions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 40bcc15bfe..fe2b4a9cae 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -453,7 +453,7 @@ COMPLETENESS_ERROR = "A ReactionSystem must be complete before it can be convert # Used to, when required, display a warning about conservation law removeal and remake. function check_cons_warning(remove_conserved, remove_conserved_warn) (remove_conserved && remove_conserved_warn) || return - @warn "You are creating a system while eliminating conserved quantities. While this is possible, + @warn "You are creating a system or problem while eliminating conserved quantities. Please note, due to limitations / design choices in ModelingToolkit if you use the created system to create a problem (e.g. an `ODEProblem`), you *should not* modify that problem's initial conditions for species (e.g. using `remake`). Changing initial conditions must be done by creating a new Problem from your reaction system or the ModelingToolkit system you converted it into with the new initial condition map. Modification of parameter values is still possible, and directly setting numerical values for any conservation law constants will work. You might get this warning when creating a problem directly. From 807811e978314b7cb5616bf0061d0bde9a19fc1a Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 14 Jun 2024 13:17:08 -0400 Subject: [PATCH 294/446] Update src/reactionsystem_conversions.jl Co-authored-by: Sam Isaacson --- src/reactionsystem_conversions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index fe2b4a9cae..3296672f2a 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -454,7 +454,7 @@ COMPLETENESS_ERROR = "A ReactionSystem must be complete before it can be convert function check_cons_warning(remove_conserved, remove_conserved_warn) (remove_conserved && remove_conserved_warn) || return @warn "You are creating a system or problem while eliminating conserved quantities. Please note, due to limitations / design choices in ModelingToolkit - if you use the created system to create a problem (e.g. an `ODEProblem`), you *should not* + if you use the created system to create a problem (e.g. an `ODEProblem`), or are directly creating a problem, you *should not* modify that problem's initial conditions for species (e.g. using `remake`). Changing initial conditions must be done by creating a new Problem from your reaction system or the ModelingToolkit system you converted it into with the new initial condition map. Modification of parameter values is still possible, and directly setting numerical values for any conservation law constants will work. You might get this warning when creating a problem directly. From 0a0c800d728d54896e5986699d292c8980304ad1 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Thu, 13 Jun 2024 14:41:58 -0400 Subject: [PATCH 295/446] Update Project.toml --- test/upstream/mtk_problem_inputs.jl | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index ba558dd466..feb74e7cd2 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -178,7 +178,7 @@ begin @named model_vec = ReactionSystem(rxs, t; observed) model_vec = complete(model_vec) - # Declares various u0 versions. + # Declares various u0 versions (scalarised and vector forms). u0_alts_vec = [ # Vectors not providing default values. [X => [1.0, 2.0]], @@ -218,43 +218,31 @@ begin (:X => [1.0, 2.0], :Y => [10.0, 20.0]), ] - # Declares various ps versions. + # Declares various ps versions (vector forms only). p_alts_vec = [ # Vectors not providing default values. [p => [1.0, 2.0]], - [p[1] => 1.0, p[2] => 2.0], [model_vec.p => [1.0, 2.0]], - [model_vec.p[1] => 1.0, model_vec.p[2] => 2.0], [:p => [1.0, 2.0]], # Vectors providing default values. [p => [4.0, 5.0], d => [0.2, 0.5]], - [p[1] => 4.0, p[2] => 5.0, d[1] => 10.0, d[2] => 20.0], [model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]], - [model_vec.p[1] => 4.0, model_vec.p[2] => 5.0, model_vec.d[1] => 10.0, model_vec.d[2] => 20.0], [:p => [4.0, 5.0], :d => [0.2, 0.5]], # Dicts not providing default values. Dict([p => [1.0, 2.0]]), - Dict([p[1] => 1.0, p[2] => 2.0]), Dict([model_vec.p => [1.0, 2.0]]), - Dict([model_vec.p[1] => 1.0, model_vec.p[2] => 2.0]), Dict([:p => [1.0, 2.0]]), # Dicts providing default values. Dict([p => [4.0, 5.0], d => [0.2, 0.5]]), - Dict([p[1] => 4.0, p[2] => 5.0, d[1] => 10.0, d[2] => 20.0]), Dict([model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]]), - Dict([model_vec.p[1] => 4.0, model_vec.p[2] => 5.0, model_vec.d[1] => 10.0, model_vec.d[2] => 20.0]), Dict([:p => [4.0, 5.0], :d => [0.2, 0.5]]), # Tuples not providing default values. (p => [1.0, 2.0]), - (p[1] => 1.0, p[2] => 2.0), (model_vec.p => [1.0, 2.0]), - (model_vec.p[1] => 1.0, model_vec.p[2] => 2.0), (:p => [1.0, 2.0]), # Tuples providing default values. (p => [4.0, 5.0], d => [0.2, 0.5]), - (p[1] => 4.0, p[2] => 5.0, d[1] => 10.0, d[2] => 20.0), (model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]), - (model_vec.p[1] => 4.0, model_vec.p[2] => 5.0, model_vec.d[1] => 10.0, model_vec.d[2] => 20.0), (:p => [4.0, 5.0], :d => [0.2, 0.5]), ] From 9cd90cfde2f430008b37b29fc08dfb8de466688e Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 14 Jun 2024 13:44:12 -0400 Subject: [PATCH 296/446] modify cons law tests --- test/network_analysis/conservation_laws.jl | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index 1ed980d95d..3e8cc6c36f 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -227,6 +227,55 @@ let @test all(sol2[osys.X1 + osys.X2] .== 4.0) end +# Tests system problem updating when conservation laws are eliminated. +# Checks that the correct values are used after the conservation law species are updated. +# Here is an issue related to the broken tests: https://github.com/SciML/Catalyst.jl/issues/952 +let + # Create model and fetch the conservation parameter (Γ). + t = default_t() + @parameters k1 k2 + @species X1(t) X2(t) + rxs = [ + Reaction(k1, [X1], [X2]), + Reaction(k2, [X2], [X1]) + ] + @named rs = ReactionSystem(rxs, t) + osys = convert(ODESystem, complete(rs); remove_conserved = true, remove_conserved_warn = false) + osys = complete(osys) + @unpack Γ = osys + + # Creates an `ODEProblem`. + u0 = [X1 => 1.0, X2 => 2.0] + ps = [k1 => 0.1, k2 => 0.2] + oprob = ODEProblem(osys, u0, (0.0, 1.0), ps) + + # Check `ODEProblem` content. + @test oprob[X1] == 1.0 + @test oprob[X2] == 2.0 + @test oprob.ps[k1] == 0.1 + @test oprob.ps[k2] == 0.2 + @test oprob.ps[Γ[1]] == 3.0 + + # Currently, any kind of updating of species or the conservation parameter(s) is not possible. + + # Update problem parameters using `remake`. + oprob_new = remake(oprob; p = [k1 => 0.3, k2 => 0.4]) + @test oprob_new[k1] == 0.3 + @test oprob_new[k2] == 0.4 + integrator = init(oprob_new, Tsit5()) + @test integrator[k1] == 0.3 + @test integrator[k2] == 0.4 + + # Update problem parameters using direct indexing. + oprob[k1] = 0.5 + oprob[k2] = 0.6 + @test oprob_new[k1] == 0.5 + @test oprob_new[k2] == 0.6 + integrator = init(oprob_new, Tsit5()) + @test integrator[k1] == 0.5 + @test integrator[k2] == 0.6 +end + ### Other Tests ### # Checks that `JumpSystem`s with conservation laws cannot be generated. From 87866b822a4a474797f8986d9e5155a2bfc6047a Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 14 Jun 2024 13:52:12 -0400 Subject: [PATCH 297/446] up --- test/upstream/mtk_problem_inputs.jl | 171 ---------------------------- 1 file changed, 171 deletions(-) diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index feb74e7cd2..a2081971b0 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -158,177 +158,6 @@ let end end -### Vector Species/Parameters Tests ### - -begin - # Declares the model (with vector species/parameters, with/without default values, and observables). - @species X(t)[1:2] Y(t)[1:2] = [10.0, 20.0] XY(t)[1:2] - @parameters p[1:2] d[1:2] = [0.2, 0.5] - rxs = [ - Reaction(p[1], [], [X[1]]), - Reaction(p[2], [], [X[2]]), - Reaction(d[1], [X[1]], []), - Reaction(d[2], [X[2]], []), - Reaction(p[1], [], [Y[1]]), - Reaction(p[2], [], [Y[2]]), - Reaction(d[1], [Y[1]], []), - Reaction(d[2], [Y[2]], []) - ] - observed = [XY[1] ~ X[1] + Y[1], XY[2] ~ X[2] + Y[2]] - @named model_vec = ReactionSystem(rxs, t; observed) - model_vec = complete(model_vec) - - # Declares various u0 versions (scalarised and vector forms). - u0_alts_vec = [ - # Vectors not providing default values. - [X => [1.0, 2.0]], - [X[1] => 1.0, X[2] => 2.0], - [model_vec.X => [1.0, 2.0]], - [model_vec.X[1] => 1.0, model_vec.X[2] => 2.0], - [:X => [1.0, 2.0]], - # Vectors providing default values. - [X => [1.0, 2.0], Y => [10.0, 20.0]], - [X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0], - [model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]], - [model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0], - [:X => [1.0, 2.0], :Y => [10.0, 20.0]], - # Dicts not providing default values. - Dict([X => [1.0, 2.0]]), - Dict([X[1] => 1.0, X[2] => 2.0]), - Dict([model_vec.X => [1.0, 2.0]]), - Dict([model_vec.X[1] => 1.0, model_vec.X[2] => 2.0]), - Dict([:X => [1.0, 2.0]]), - # Dicts providing default values. - Dict([X => [1.0, 2.0], Y => [10.0, 20.0]]), - Dict([X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0]), - Dict([model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]]), - Dict([model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0]), - Dict([:X => [1.0, 2.0], :Y => [10.0, 20.0]]), - # Tuples not providing default values. - (X => [1.0, 2.0]), - (X[1] => 1.0, X[2] => 2.0), - (model_vec.X => [1.0, 2.0]), - (model_vec.X[1] => 1.0, model_vec.X[2] => 2.0), - (:X => [1.0, 2.0]), - # Tuples providing default values. - (X => [1.0, 2.0], Y => [10.0, 20.0]), - (X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0), - (model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]), - (model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0), - (:X => [1.0, 2.0], :Y => [10.0, 20.0]), - ] - - # Declares various ps versions (vector forms only). - p_alts_vec = [ - # Vectors not providing default values. - [p => [1.0, 2.0]], - [model_vec.p => [1.0, 2.0]], - [:p => [1.0, 2.0]], - # Vectors providing default values. - [p => [4.0, 5.0], d => [0.2, 0.5]], - [model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]], - [:p => [4.0, 5.0], :d => [0.2, 0.5]], - # Dicts not providing default values. - Dict([p => [1.0, 2.0]]), - Dict([model_vec.p => [1.0, 2.0]]), - Dict([:p => [1.0, 2.0]]), - # Dicts providing default values. - Dict([p => [4.0, 5.0], d => [0.2, 0.5]]), - Dict([model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]]), - Dict([:p => [4.0, 5.0], :d => [0.2, 0.5]]), - # Tuples not providing default values. - (p => [1.0, 2.0]), - (model_vec.p => [1.0, 2.0]), - (:p => [1.0, 2.0]), - # Tuples providing default values. - (p => [4.0, 5.0], d => [0.2, 0.5]), - (model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]), - (:p => [4.0, 5.0], :d => [0.2, 0.5]), - ] - - # Declares a timespan. - tspan = (0.0, 10.0) -end - -# Perform ODE simulations (singular and ensemble). -let - # Creates normal and ensemble problems. - base_oprob = ODEProblem(model_vec, u0_alts_vec[1], tspan, p_alts_vec[1]) - base_sol = solve(base_oprob, Tsit5(); saveat = 1.0) - base_eprob = EnsembleProblem(base_oprob) - base_esol = solve(base_eprob, Tsit5(); trajectories = 2, saveat = 1.0) - - # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts_vec, p in p_alts_vec - oprob = remake(base_oprob; u0, p) - @test base_sol == solve(oprob, Tsit5(); saveat = 1.0) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, Tsit5(); trajectories = 2, saveat = 1.0) - end -end - -# Perform SDE simulations (singular and ensemble). -let - # Creates normal and ensemble problems. - base_sprob = SDEProblem(model, u0_alts_vec[1], tspan, p_alts_vec[1]) - base_sol = solve(base_sprob, ImplicitEM(); seed, saveat = 1.0) - base_eprob = EnsembleProblem(base_sprob) - base_esol = solve(base_eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) - - # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts_vec, p in p_alts_vec - sprob = remake(base_sprob; u0, p) - @test base_sol == solve(sprob, ImplicitEM(); seed, saveat = 1.0) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) - end -end - -# Perform jump simulations (singular and ensemble). -let - # Creates normal and ensemble problems. - base_dprob = DiscreteProblem(model, u0_alts_vec[1], tspan, p_alts_vec[1]) - base_jprob = JumpProblem(model, base_dprob, Direct(); rng) - base_sol = solve(base_jprob, SSAStepper(); seed, saveat = 1.0) - base_eprob = EnsembleProblem(base_jprob) - base_esol = solve(base_eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) - - # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts_vec, p in p_alts_vec - jprob = remake(base_jprob; u0, p) - @test base_sol == solve(base_jprob, SSAStepper(); seed, saveat = 1.0) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) - end -end - -# Solves a nonlinear problem (EnsembleProblems are not possible for these). -let - base_nlprob = NonlinearProblem(model, u0_alts_vec[1], p_alts_vec[1]) - base_sol = solve(base_nlprob, NewtonRaphson()) - for u0 in u0_alts_vec, p in p_alts_vec - nlprob = remake(base_nlprob; u0, p) - @test base_sol == solve(nlprob, NewtonRaphson()) - end -end - -# Perform steady state simulations (singular and ensemble). -let - # Creates normal and ensemble problems. - base_ssprob = SteadyStateProblem(model, u0_alts_vec[1], p_alts_vec[1]) - base_sol = solve(base_ssprob, DynamicSS(Tsit5())) - base_eprob = EnsembleProblem(base_ssprob) - base_esol = solve(base_eprob, DynamicSS(Tsit5()); trajectories = 2) - - # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts_vec, p in p_alts_vec - ssprob = remake(base_ssprob; u0, p) - @test base_sol == solve(ssprob, DynamicSS(Tsit5())) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, DynamicSS(Tsit5()); trajectories = 2) - end -end - ### Checks Errors On Faulty Inputs ### # Checks various erroneous problem inputs, ensuring that these throw errors. From 6fce70a7745a8bb51dcd6f375a9787070d7208df Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 14 Jun 2024 13:52:50 -0400 Subject: [PATCH 298/446] up --- test/upstream/mtk_problem_inputs.jl | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index ba558dd466..feb74e7cd2 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -178,7 +178,7 @@ begin @named model_vec = ReactionSystem(rxs, t; observed) model_vec = complete(model_vec) - # Declares various u0 versions. + # Declares various u0 versions (scalarised and vector forms). u0_alts_vec = [ # Vectors not providing default values. [X => [1.0, 2.0]], @@ -218,43 +218,31 @@ begin (:X => [1.0, 2.0], :Y => [10.0, 20.0]), ] - # Declares various ps versions. + # Declares various ps versions (vector forms only). p_alts_vec = [ # Vectors not providing default values. [p => [1.0, 2.0]], - [p[1] => 1.0, p[2] => 2.0], [model_vec.p => [1.0, 2.0]], - [model_vec.p[1] => 1.0, model_vec.p[2] => 2.0], [:p => [1.0, 2.0]], # Vectors providing default values. [p => [4.0, 5.0], d => [0.2, 0.5]], - [p[1] => 4.0, p[2] => 5.0, d[1] => 10.0, d[2] => 20.0], [model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]], - [model_vec.p[1] => 4.0, model_vec.p[2] => 5.0, model_vec.d[1] => 10.0, model_vec.d[2] => 20.0], [:p => [4.0, 5.0], :d => [0.2, 0.5]], # Dicts not providing default values. Dict([p => [1.0, 2.0]]), - Dict([p[1] => 1.0, p[2] => 2.0]), Dict([model_vec.p => [1.0, 2.0]]), - Dict([model_vec.p[1] => 1.0, model_vec.p[2] => 2.0]), Dict([:p => [1.0, 2.0]]), # Dicts providing default values. Dict([p => [4.0, 5.0], d => [0.2, 0.5]]), - Dict([p[1] => 4.0, p[2] => 5.0, d[1] => 10.0, d[2] => 20.0]), Dict([model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]]), - Dict([model_vec.p[1] => 4.0, model_vec.p[2] => 5.0, model_vec.d[1] => 10.0, model_vec.d[2] => 20.0]), Dict([:p => [4.0, 5.0], :d => [0.2, 0.5]]), # Tuples not providing default values. (p => [1.0, 2.0]), - (p[1] => 1.0, p[2] => 2.0), (model_vec.p => [1.0, 2.0]), - (model_vec.p[1] => 1.0, model_vec.p[2] => 2.0), (:p => [1.0, 2.0]), # Tuples providing default values. (p => [4.0, 5.0], d => [0.2, 0.5]), - (p[1] => 4.0, p[2] => 5.0, d[1] => 10.0, d[2] => 20.0), (model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]), - (model_vec.p[1] => 4.0, model_vec.p[2] => 5.0, model_vec.d[1] => 10.0, model_vec.d[2] => 20.0), (:p => [4.0, 5.0], :d => [0.2, 0.5]), ] From aa32a851220046033e0ea664c76fd460c8cf0115 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 14 Jun 2024 14:16:27 -0400 Subject: [PATCH 299/446] up --- docs/src/model_creation/examples/basic_CRN_library.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/model_creation/examples/basic_CRN_library.md b/docs/src/model_creation/examples/basic_CRN_library.md index 533e80f5ed..da7130452d 100644 --- a/docs/src/model_creation/examples/basic_CRN_library.md +++ b/docs/src/model_creation/examples/basic_CRN_library.md @@ -115,11 +115,11 @@ using Plots oplt = plot(osol; title = "Reaction rate equation (ODE)") splt = plot(ssol; title = "Chemical Langevin equation (SDE)") jplt = plot(jsol; title = "Stochastic chemical kinetics (Jump)") -plot(oplt, splt, jplt; lw = 2, size=(800,800), layout = (3,1)) # hide +plot(oplt, splt, jplt; lw = 2, size=(800,800), layout = (3,1)) oplt = plot(osol; title = "Reaction rate equation (ODE)", plotdensity = 1000, fmt = :png) # hide splt = plot(ssol; title = "Chemical Langevin equation (SDE)", plotdensity = 1000, fmt = :png) # hide jplt = plot(jsol; title = "Stochastic chemical kinetics (Jump)", plotdensity = 1000, fmt = :png) # hide -plot(oplt, splt, jplt; lw = 2, size=(800,800), layout = (3,1)) +plot(oplt, splt, jplt; lw = 2, size=(800,800), layout = (3,1), plotdensity = 1000, fmt = :png) # hide ``` ## [SIR infection model](@id basic_CRN_library_sir) From a4de7c81bcfa945890159e6062adb7a382a440c5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 11:11:54 -0400 Subject: [PATCH 300/446] update warning --- src/reactionsystem_conversions.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 3296672f2a..6f7b2255ac 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -453,10 +453,15 @@ COMPLETENESS_ERROR = "A ReactionSystem must be complete before it can be convert # Used to, when required, display a warning about conservation law removeal and remake. function check_cons_warning(remove_conserved, remove_conserved_warn) (remove_conserved && remove_conserved_warn) || return - @warn "You are creating a system or problem while eliminating conserved quantities. Please note, due to limitations / design choices in ModelingToolkit - if you use the created system to create a problem (e.g. an `ODEProblem`), or are directly creating a problem, you *should not* - modify that problem's initial conditions for species (e.g. using `remake`). Changing initial conditions must be done by creating a new Problem from your reaction system or the ModelingToolkit system you converted it into with the new initial condition map. Modification of parameter values - is still possible, and directly setting numerical values for any conservation law constants will work. You might get this warning when creating a problem directly. + @warn "You are creating a system or problem while eliminating conserved quantities. Please note, + due to limitations / design choices in ModelingToolkit if you use the created system to + create a problem (e.g. an `ODEProblem`), or are directly creating a problem, you *should not* + modify that problem's initial conditions for species (e.g. using `remake`). Changing initial + conditions must be done by creating a new Problem from your reaction system or the + ModelingToolkit system you converted it into with the new initial condition map. + Modification of parameter values is still possible, *except* for the modification of any + conservation law constants ($CONSERVED_CONSTANT_SYMBOL), which is not possible. You might + get this warning when creating a problem directly. You can remove this warning by setting `remove_conserved_warn = false`." end From d5f303ad9c95ebefb71544251e8acd20a1e44ee3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 11:14:35 -0400 Subject: [PATCH 301/446] update doc project catalyst version --- docs/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index b92da6ef5d..7d86a95a90 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -39,7 +39,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" BenchmarkTools = "1.5" BifurcationKit = "0.3.4" CairoMakie = "0.12" -Catalyst = "13" +Catalyst = "14" DataFrames = "1.6" DiffEqParamEstim = "2.2" Distributions = "0.25" From 837f51cf5d7f6eb4e95da6fbae953f14fcea79de Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 11:58:09 -0400 Subject: [PATCH 302/446] init --- test/upstream/mtk_structure_indexing.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/upstream/mtk_structure_indexing.jl b/test/upstream/mtk_structure_indexing.jl index 29e7a01755..2fcc9ab553 100644 --- a/test/upstream/mtk_structure_indexing.jl +++ b/test/upstream/mtk_structure_indexing.jl @@ -64,7 +64,8 @@ end # Tests problem indexing and updating. let - for prob in [deepcopy(problems); deepcopy(eproblems)] + @test_broken false # Currently does not work for nonlinearproblems and their ensemble problems (https://github.com/SciML/SciMLBase.jl/issues/720). + for prob in [deepcopy([oprob, sprob, dprob, jprob, ssprob]); deepcopy([eoprob, esprob, edprob, ejprob, essprob])] # Get u values (including observables). @test prob[X] == prob[model.X] == prob[:X] == 4 @test prob[XY] == prob[model.XY] == prob[:XY] == 9 @@ -253,7 +254,8 @@ let # Handles nonlinear and steady state solutions differently. let - for sol in deepcopy([nsol, sssol]) + @test_broken false # Currently a problem for nonlinear solutions and steady state solutions (https://github.com/SciML/SciMLBase.jl/issues/720). + for sol in deepcopy([]) # Get u values. @test sol[X] == sol[model.X] == sol[:X] @test sol[XY] == sol[model.XY][1] == sol[:XY] From 9dae5a7d2ddbb4ce3a16b0e280e8d697be1ec3d8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 12:47:52 -0400 Subject: [PATCH 303/446] doc project catalyst version 13 to 14 --- docs/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index b92da6ef5d..7d86a95a90 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -39,7 +39,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" BenchmarkTools = "1.5" BifurcationKit = "0.3.4" CairoMakie = "0.12" -Catalyst = "13" +Catalyst = "14" DataFrames = "1.6" DiffEqParamEstim = "2.2" Distributions = "0.25" From 88cf23512fb524a60c6856b00a54df4869869fea Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 16:49:11 -0400 Subject: [PATCH 304/446] add remove_conserved_warn = false to test --- test/network_analysis/conservation_laws.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index 1ed980d95d..dd1f9ee2eb 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -270,7 +270,7 @@ let # Checks that simulation reaches known equilibrium u0 = [:X => [3.0, 9.0]] ps = [:k => [1.0, 2.0]] - oprob = ODEProblem(rs, u0, (0.0, 1000.0), ps; remove_conserved = true) + oprob = ODEProblem(rs, u0, (0.0, 1000.0), ps; remove_conserved = true, remove_conserved_warn = false) sol = solve(oprob, Vern7()) @test sol[X[1]] ≈ 8.0 @test sol[X[2]] ≈ 4.0 From 6a8a9a2278f6e460bd6f89432b09bc4ebb85098b Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 16:54:05 -0400 Subject: [PATCH 305/446] up --- docs/src/introduction_to_catalyst/introduction_to_catalyst.md | 4 ++-- docs/src/model_creation/examples/basic_CRN_library.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index 8e852d7f1b..3fe15232c8 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -191,10 +191,10 @@ u₀map = [:m₁ => 0, :m₂ => 0, :m₃ => 0, :P₁ => 20, :P₂ => 0, :P₃ => dprob = DiscreteProblem(repressilator, u₀map, tspan, pmap) # now, we create a JumpProblem, and specify Gillespie's Direct Method as the solver: -jprob = JumpProblem(repressilator, dprob, Direct(), save_positions=(false,false)) +jprob = JumpProblem(repressilator, dprob, Direct()) # now, let's solve and plot the jump process: -sol = solve(jprob, SSAStepper(), saveat=10.) +sol = solve(jprob, SSAStepper()) plot(sol) plot(sol, plotdensity = 1000, fmt = :png) # hide ``` diff --git a/docs/src/model_creation/examples/basic_CRN_library.md b/docs/src/model_creation/examples/basic_CRN_library.md index da7130452d..7279fa4f33 100644 --- a/docs/src/model_creation/examples/basic_CRN_library.md +++ b/docs/src/model_creation/examples/basic_CRN_library.md @@ -120,6 +120,7 @@ oplt = plot(osol; title = "Reaction rate equation (ODE)", plotdensity = 1000, fm splt = plot(ssol; title = "Chemical Langevin equation (SDE)", plotdensity = 1000, fmt = :png) # hide jplt = plot(jsol; title = "Stochastic chemical kinetics (Jump)", plotdensity = 1000, fmt = :png) # hide plot(oplt, splt, jplt; lw = 2, size=(800,800), layout = (3,1), plotdensity = 1000, fmt = :png) # hide +plot!(bottom_margin = 3Plots.Measures.mm) # hide ``` ## [SIR infection model](@id basic_CRN_library_sir) From 68e72b36520766cd6d781eb96b752fd33a79d043 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 17:01:30 -0400 Subject: [PATCH 306/446] update cons warning test check for updated message --- test/network_analysis/conservation_laws.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index 94cb33e614..e359f7a594 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -270,7 +270,7 @@ let # Check warnings in system conversion. for XSystem in [ODESystem, SDESystem, NonlinearSystem] @test_nowarn convert(XSystem, rn) - @test_logs (:warn, r"You are creating a system while eliminating conserved quantities. While *") convert(XSystem, rn; remove_conserved = true) + @test_logs (:warn, r"You are creating a system or problem while eliminating conserved quantities. Please *") convert(XSystem, rn; remove_conserved = true) @test_nowarn convert(XSystem, rn; remove_conserved_warn = false) @test_nowarn convert(XSystem, rn; remove_conserved = true, remove_conserved_warn = false) end @@ -278,13 +278,13 @@ let # Checks during problem creation (separate depending on whether they have a time span or not). for XProblem in [ODEProblem, SDEProblem] @test_nowarn XProblem(rn, u0, tspan, ps) - @test_logs (:warn, r"You are creating a system while eliminating conserved quantities. While *") XProblem(rn, u0, tspan, ps; remove_conserved = true) + @test_logs (:warn, r"You are creating a system or problem while eliminating conserved quantities. Please *") XProblem(rn, u0, tspan, ps; remove_conserved = true) @test_nowarn XProblem(rn, u0, tspan, ps; remove_conserved_warn = false) @test_nowarn XProblem(rn, u0, tspan, ps; remove_conserved = true, remove_conserved_warn = false) end for XProblem in [NonlinearProblem, SteadyStateProblem] @test_nowarn XProblem(rn, u0, ps) - @test_logs (:warn, r"You are creating a system while eliminating conserved quantities. While *") XProblem(rn, u0, ps; remove_conserved = true) + @test_logs (:warn, r"You are creating a system or problem while eliminating conserved quantities. Please *") XProblem(rn, u0, ps; remove_conserved = true) @test_nowarn XProblem(rn, u0, ps; remove_conserved_warn = false) @test_nowarn XProblem(rn, u0, ps; remove_conserved = true, remove_conserved_warn = false) end From d76637caa34531d0591968df3f8889b13e2dbc4a Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 18:11:16 -0400 Subject: [PATCH 307/446] merge fix --- test/network_analysis/conservation_laws.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index ce4774d792..b3c94fe500 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -338,6 +338,7 @@ let @test_nowarn XProblem(rn, u0, ps; remove_conserved = true, remove_conserved_warn = false) end end + # Conservation law simulations for vectorised species. let # Prepares the model. From 43115bb306c5d2c437b78b36dda3a0351f21d2bf Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Jun 2024 00:39:53 +0200 Subject: [PATCH 308/446] Update reactionsystem.jl --- test/reactionsystem_core/reactionsystem.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index 61e8c5120a..11edb6891b 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -316,11 +316,12 @@ let # have slight differences, so checking for both here to be certain. for rs in [rs_prog, rs_dsl] oprob = ODEProblem(rs, u0_alts[1], (0.0, 10000.), ps_alts[1]) - for rs in [rs_prog, rs_dsl], u0 in u0_alts, p in ps_alts - oprob_remade = remake(oprob; u0, p) - sol = solve(oprob_remade, Vern7(); abstol = 1e-8, reltol = 1e-8) - @test sol[end] ≈ [0.5, 5.0, 0.2, 2.5] - end + @test_broken false # Cannot currently `remake` this problem/ + # for rs in [rs_prog, rs_dsl], u0 in u0_alts, p in ps_alts + # oprob_remade = remake(oprob; u0, p) + # sol = solve(oprob_remade, Vern7(); abstol = 1e-8, reltol = 1e-8) + # @test sol[end] ≈ [0.5, 5.0, 0.2, 2.5] + # end end end @@ -814,4 +815,4 @@ end # the code might need adaptation to take the updated reaction system into account. let @test_nowarn Catalyst.reactionsystem_uptodate_check() -end \ No newline at end of file +end From e3770b97acbaa3b7c5401938f0a4d5f9992a5a0c Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Jun 2024 00:40:24 +0200 Subject: [PATCH 309/446] Update reactionsystem.jl --- test/reactionsystem_core/reactionsystem.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index 61e8c5120a..11edb6891b 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -316,11 +316,12 @@ let # have slight differences, so checking for both here to be certain. for rs in [rs_prog, rs_dsl] oprob = ODEProblem(rs, u0_alts[1], (0.0, 10000.), ps_alts[1]) - for rs in [rs_prog, rs_dsl], u0 in u0_alts, p in ps_alts - oprob_remade = remake(oprob; u0, p) - sol = solve(oprob_remade, Vern7(); abstol = 1e-8, reltol = 1e-8) - @test sol[end] ≈ [0.5, 5.0, 0.2, 2.5] - end + @test_broken false # Cannot currently `remake` this problem/ + # for rs in [rs_prog, rs_dsl], u0 in u0_alts, p in ps_alts + # oprob_remade = remake(oprob; u0, p) + # sol = solve(oprob_remade, Vern7(); abstol = 1e-8, reltol = 1e-8) + # @test sol[end] ≈ [0.5, 5.0, 0.2, 2.5] + # end end end @@ -814,4 +815,4 @@ end # the code might need adaptation to take the updated reaction system into account. let @test_nowarn Catalyst.reactionsystem_uptodate_check() -end \ No newline at end of file +end From 95132d071a1f3e9a01e0cd5eb30639e0d37f7f26 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 21:40:51 -0400 Subject: [PATCH 310/446] test fix --- test/network_analysis/conservation_laws.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index b3c94fe500..0f21a1d8aa 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -263,20 +263,20 @@ let # Update problem parameters using `remake`. oprob_new = remake(oprob; p = [k1 => 0.3, k2 => 0.4]) - @test oprob_new[k1] == 0.3 - @test oprob_new[k2] == 0.4 + @test oprob_new.ps[k1] == 0.3 + @test oprob_new.ps[k2] == 0.4 integrator = init(oprob_new, Tsit5()) - @test integrator[k1] == 0.3 - @test integrator[k2] == 0.4 + @test integrator.ps[k1] == 0.3 + @test integrator.ps[k2] == 0.4 # Update problem parameters using direct indexing. oprob[k1] = 0.5 oprob[k2] = 0.6 - @test oprob_new[k1] == 0.5 - @test oprob_new[k2] == 0.6 + @test oprob_new.ps[k1] == 0.5 + @test oprob_new.ps[k2] == 0.6 integrator = init(oprob_new, Tsit5()) - @test integrator[k1] == 0.5 - @test integrator[k2] == 0.6 + @test integrator.ps[k1] == 0.5 + @test integrator.ps[k2] == 0.6 end ### Other Tests ### From f96e89cf3653f924753722cb8f1644e73b8b0c06 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 21:50:31 -0400 Subject: [PATCH 311/446] mark broken mtk test as broken --- test/network_analysis/conservation_laws.jl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index 0f21a1d8aa..a91132be8d 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -352,11 +352,12 @@ let @named rs = ReactionSystem(rxs, t) rs = complete(rs) - # Checks that simulation reaches known equilibrium - u0 = [:X => [3.0, 9.0]] - ps = [:k => [1.0, 2.0]] - oprob = ODEProblem(rs, u0, (0.0, 1000.0), ps; remove_conserved = true) - sol = solve(oprob, Vern7()) - @test sol[X[1]] ≈ 8.0 - @test sol[X[2]] ≈ 4.0 + # Checks that simulation reaches known equilibrium. + @test_broken false # Currently broken on MTK . + # u0 = [:X => [3.0, 9.0]] + # ps = [:k => [1.0, 2.0]] + # oprob = ODEProblem(rs, u0, (0.0, 1000.0), ps; remove_conserved = true) + # sol = solve(oprob, Vern7()) + # @test sol[X[1]][end] ≈ 8.0 + # @test sol[X[2]][end] ≈ 4.0 end \ No newline at end of file From 17791df6916c5e7cff7a7c423b831e16eb6192f2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 21:51:07 -0400 Subject: [PATCH 312/446] mark broken mtk test as broken --- test/network_analysis/conservation_laws.jl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index 5bfb2068ef..2f65a68e36 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -303,11 +303,12 @@ let @named rs = ReactionSystem(rxs, t) rs = complete(rs) - # Checks that simulation reaches known equilibrium - u0 = [:X => [3.0, 9.0]] - ps = [:k => [1.0, 2.0]] - oprob = ODEProblem(rs, u0, (0.0, 1000.0), ps; remove_conserved = true, remove_conserved_warn = false) - sol = solve(oprob, Vern7()) - @test sol[X[1]] ≈ 8.0 - @test sol[X[2]] ≈ 4.0 + # Checks that simulation reaches known equilibrium. + @test_broken false # Currently broken on MTK . + # u0 = [:X => [3.0, 9.0]] + # ps = [:k => [1.0, 2.0]] + # oprob = ODEProblem(rs, u0, (0.0, 1000.0), ps; remove_conserved = true) + # sol = solve(oprob, Vern7()) + # @test sol[X[1]][end] ≈ 8.0 + # @test sol[X[2]][end] ≈ 4.0 end \ No newline at end of file From e2c1558a7d154c5902ce09a64e63ded23a0a7334 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Jun 2024 04:09:56 +0200 Subject: [PATCH 313/446] test fix --- test/network_analysis/conservation_laws.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index a91132be8d..a68417c1fd 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -270,8 +270,8 @@ let @test integrator.ps[k2] == 0.4 # Update problem parameters using direct indexing. - oprob[k1] = 0.5 - oprob[k2] = 0.6 + oprob.ps[k1] = 0.5 + oprob.ps[k2] = 0.6 @test oprob_new.ps[k1] == 0.5 @test oprob_new.ps[k2] == 0.6 integrator = init(oprob_new, Tsit5()) @@ -360,4 +360,4 @@ let # sol = solve(oprob, Vern7()) # @test sol[X[1]][end] ≈ 8.0 # @test sol[X[2]][end] ≈ 4.0 -end \ No newline at end of file +end From 1a089f1cd4cd76e3e59abeed511949e799cedc0e Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Jun 2024 04:19:44 +0200 Subject: [PATCH 314/446] test fix --- test/upstream/mtk_problem_inputs.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index feb74e7cd2..daa09aa83d 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -162,6 +162,7 @@ end begin # Declares the model (with vector species/parameters, with/without default values, and observables). + t = default_t() @species X(t)[1:2] Y(t)[1:2] = [10.0, 20.0] XY(t)[1:2] @parameters p[1:2] d[1:2] = [0.2, 0.5] rxs = [ @@ -415,4 +416,4 @@ let end end end -end \ No newline at end of file +end From 1204fcda054889fd112e650e487f7b4b955cc278 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Jun 2024 04:29:09 +0200 Subject: [PATCH 315/446] test fix --- test/network_analysis/conservation_laws.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index a68417c1fd..1d0a85e217 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -272,9 +272,9 @@ let # Update problem parameters using direct indexing. oprob.ps[k1] = 0.5 oprob.ps[k2] = 0.6 - @test oprob_new.ps[k1] == 0.5 - @test oprob_new.ps[k2] == 0.6 - integrator = init(oprob_new, Tsit5()) + @test oprob.ps[k1] == 0.5 + @test oprob.ps[k2] == 0.6 + integrator = init(oprob, Tsit5()) @test integrator.ps[k1] == 0.5 @test integrator.ps[k2] == 0.6 end From de5992c055213ef22434b9a060397c1cb7c0d253 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 15 Jun 2024 23:06:49 -0400 Subject: [PATCH 316/446] mark broken mtk tests as broken --- test/upstream/mtk_problem_inputs.jl | 71 +++++++++++++++-------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index daa09aa83d..ed59762fab 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -260,74 +260,79 @@ let base_esol = solve(base_eprob, Tsit5(); trajectories = 2, saveat = 1.0) # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts_vec, p in p_alts_vec - oprob = remake(base_oprob; u0, p) - @test base_sol == solve(oprob, Tsit5(); saveat = 1.0) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, Tsit5(); trajectories = 2, saveat = 1.0) - end + @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). + # for u0 in u0_alts_vec, p in p_alts_vec + # oprob = remake(base_oprob; u0, p) + # @test base_sol == solve(oprob, Tsit5(); saveat = 1.0) + # eprob = remake(base_eprob; u0, p) + # @test base_esol == solve(eprob, Tsit5(); trajectories = 2, saveat = 1.0) + # end end # Perform SDE simulations (singular and ensemble). let # Creates normal and ensemble problems. - base_sprob = SDEProblem(model, u0_alts_vec[1], tspan, p_alts_vec[1]) + base_sprob = SDEProblem(model_vec, u0_alts_vec[1], tspan, p_alts_vec[1]) base_sol = solve(base_sprob, ImplicitEM(); seed, saveat = 1.0) base_eprob = EnsembleProblem(base_sprob) base_esol = solve(base_eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts_vec, p in p_alts_vec - sprob = remake(base_sprob; u0, p) - @test base_sol == solve(sprob, ImplicitEM(); seed, saveat = 1.0) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) - end + @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). + # for u0 in u0_alts_vec, p in p_alts_vec + # sprob = remake(base_sprob; u0, p) + # @test base_sol == solve(sprob, ImplicitEM(); seed, saveat = 1.0) + # eprob = remake(base_eprob; u0, p) + # @test base_esol == solve(eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) + # end end # Perform jump simulations (singular and ensemble). let # Creates normal and ensemble problems. - base_dprob = DiscreteProblem(model, u0_alts_vec[1], tspan, p_alts_vec[1]) - base_jprob = JumpProblem(model, base_dprob, Direct(); rng) + base_dprob = DiscreteProblem(model_vec, u0_alts_vec[1], tspan, p_alts_vec[1]) + base_jprob = JumpProblem(model_vec, base_dprob, Direct(); rng) base_sol = solve(base_jprob, SSAStepper(); seed, saveat = 1.0) base_eprob = EnsembleProblem(base_jprob) base_esol = solve(base_eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts_vec, p in p_alts_vec - jprob = remake(base_jprob; u0, p) - @test base_sol == solve(base_jprob, SSAStepper(); seed, saveat = 1.0) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) - end + @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). + # for u0 in u0_alts_vec, p in p_alts_vec + # jprob = remake(base_jprob; u0, p) + # @test base_sol == solve(base_jprob, SSAStepper(); seed, saveat = 1.0) + # eprob = remake(base_eprob; u0, p) + # @test base_esol == solve(eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) + # end end # Solves a nonlinear problem (EnsembleProblems are not possible for these). let - base_nlprob = NonlinearProblem(model, u0_alts_vec[1], p_alts_vec[1]) + base_nlprob = NonlinearProblem(model_vec, u0_alts_vec[1], p_alts_vec[1]) base_sol = solve(base_nlprob, NewtonRaphson()) - for u0 in u0_alts_vec, p in p_alts_vec - nlprob = remake(base_nlprob; u0, p) - @test base_sol == solve(nlprob, NewtonRaphson()) - end + @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). + # for u0 in u0_alts_vec, p in p_alts_vec + # nlprob = remake(base_nlprob; u0, p) + # @test base_sol == solve(nlprob, NewtonRaphson()) + # end end # Perform steady state simulations (singular and ensemble). let # Creates normal and ensemble problems. - base_ssprob = SteadyStateProblem(model, u0_alts_vec[1], p_alts_vec[1]) + base_ssprob = SteadyStateProblem(model_vec, u0_alts_vec[1], p_alts_vec[1]) base_sol = solve(base_ssprob, DynamicSS(Tsit5())) base_eprob = EnsembleProblem(base_ssprob) base_esol = solve(base_eprob, DynamicSS(Tsit5()); trajectories = 2) # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts_vec, p in p_alts_vec - ssprob = remake(base_ssprob; u0, p) - @test base_sol == solve(ssprob, DynamicSS(Tsit5())) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, DynamicSS(Tsit5()); trajectories = 2) - end + @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). + # for u0 in u0_alts_vec, p in p_alts_vec + # ssprob = remake(base_ssprob; u0, p) + # @test base_sol == solve(ssprob, DynamicSS(Tsit5())) + # eprob = remake(base_eprob; u0, p) + # @test base_esol == solve(eprob, DynamicSS(Tsit5()); trajectories = 2) + # end end ### Checks Errors On Faulty Inputs ### From e26430e53a3ee30693969ddbe89a6f2fe3849097 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 18 Jun 2024 12:14:01 -0400 Subject: [PATCH 317/446] add terminal linkage classes --- src/network_analysis.jl | 45 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 2b2cd11570..8db36d9b58 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -359,6 +359,48 @@ end linkageclasses(incidencegraph) = Graphs.connected_components(incidencegraph) +""" + stronglinkageclasses(rn::ReactionSystem) + + Return the strongly connected components of a reaction network's incidence graph (i.e. sub-groups of reaction complexes such that every complex is reachable from every other one in the sub-group). +""" + +function stronglinkageclasses(rn::ReactionSystem) + nps = get_networkproperties(rn) + if isempty(nps.stronglinkageclasses) + nps.stronglinkageclasses = stronglinkageclasses(incidencematgraph(rn)) + end + nps.stronglinkageclasses +end + +stronglinkageclasses(incidencegraph) = Graphs.strongly_connected_components(g) + +""" + terminallinkageclasses(rn::ReactionSystem) + + Return the terminal strongly connected components of a reaction network's incidence graph (i.e. sub-groups of reaction complexes that are 1) strongly connected and 2) every reaction in the component produces a complex in the component). +""" + +function terminallinkageclasses(rn::ReactionSystem) + slcs = stronglinkageclasses(rn) + + tslcs = filter!(slcs, lc->isterminal(lc)) + tslcs +end + +function isterminal(lc::Vector, rn::ReactionSystem) + imat = incidencemat(rn) + + for col in columns(imat) + s = findfirst(==(-1), @view D[:, r]) + if s in Set(lc) + p = findfirst(==(1), @view D[:, r]) + p in Set(lc) ? continue : return false + end + end + true +end + @doc raw""" deficiency(rn::ReactionSystem) @@ -925,6 +967,7 @@ end See documentation for [`cycles`](@ref). """ -function fluxvectors(rs::ReactionSystem) +function fluxmodebasis(rs::ReactionSystem) cycles(rs) end + From 72e33978da3e479244510e053620bff09fc23dda Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 18 Jun 2024 12:14:28 -0400 Subject: [PATCH 318/446] up --- src/network_analysis.jl | 43 ----------------------------------------- 1 file changed, 43 deletions(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 8db36d9b58..9dcf3518fa 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -928,46 +928,3 @@ function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) end prod end - -""" - cycles(rs::ReactionSystem) - - Returns the matrix of cycles (or flux vectors), or reaction fluxes at steady state. These correspond to right eigenvectors of the stoichiometric matrix. Equivalent to [`fluxmodebasis`](@ref). -""" - -function cycles(rs::ReactionSystem) - nps = get_networkproperties(rs) - nsm = netstoichmat(rs) - !isempty(nps.cyclemat) && return nps.cyclemat - nps.cyclemat = cycles(nsm; col_order = nps.col_order) - nps.cyclemat -end - -function cycles(nsm::T; col_order = nothing) where {T <: AbstractMatrix} - - # compute the left nullspace over the integers - N = MT.nullspace(nsm; col_order) - - # if all coefficients for a cycle are negative, make positive - for Nrow in eachcol(N) - all(r -> r <= 0, Nrow) && (Nrow .*= -1) - end - - # check we haven't overflowed - iszero(nsm * N) || error("Calculation of the cycle matrix was inaccurate, " - * "likely due to numerical overflow. Please use a larger integer " - * "type like Int128 or BigInt for the net stoichiometry matrix.") - - T(N) -end - -""" - fluxvectors(rs::ReactionSystem) - - See documentation for [`cycles`](@ref). -""" - -function fluxmodebasis(rs::ReactionSystem) - cycles(rs) -end - From e4b39c6a5f933cfdbbe5943c7e0a77888e43d372 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 18 Jun 2024 12:45:44 -0400 Subject: [PATCH 319/446] added test --- test/network_analysis/network_properties.jl | 78 +++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/test/network_analysis/network_properties.jl b/test/network_analysis/network_properties.jl index 5507a9f655..baee75e32a 100644 --- a/test/network_analysis/network_properties.jl +++ b/test/network_analysis/network_properties.jl @@ -326,3 +326,81 @@ let @test Catalyst.iscomplexbalanced(rn, rates) == true end +### STRONG LINKAGE CLASS TESTS + +let + rn = @reaction_network begin + (k1, k2), A <--> B + C + k3, B + C --> D + k4, D --> E + (k5, k6), E <--> 2F + k7, 2F --> D + (k8, k9), D + E <--> G + end + + rcs, D = reactioncomplexes(rn) + slcs = stronglinkageclasses(rn) + tslcs = terminallinkageclasses(rn) + @test length(slcs) == 3 + @test length(tslcs) == 2 + @test issubset([[1,2], [3,4,5], [6,7]], slcs) + @test issubset([[3,4,5], [6,7]], tslcs) +end + +let + rn = @reaction_network begin + (k1, k2), A <--> B + C + k3, B + C --> D + k4, D --> E + (k5, k6), E <--> 2F + k7, 2F --> D + (k8, k9), D + E --> G + end + + rcs, D = reactioncomplexes(rn) + slcs = stronglinkageclasses(rn) + tslcs = terminallinkageclasses(rn) + @test length(slcs) == 4 + @test length(tslcs) == 2 + @test issubset([[1,2], [3,4,5], [6], [7]], slcs) + @test issubset([[3,4,5], [7]], tslcs) +end + +let + rn = @reaction_network begin + (k1, k2), A <--> B + C + (k3, k4), B + C <--> D + k5, D --> E + (k6, k7), E <--> 2F + k8, 2F --> D + (k9, k10), D + E <--> G + end + + rcs, D = reactioncomplexes(rn) + slcs = stronglinkageclasses(rn) + tslcs = terminallinkageclasses(rn) + @test length(slcs) == 2 + @test length(tslcs) == 2 + @test issubset([[1,2,3,4,5], [6,7]], slcs) + @test issubset([[1,2,3,4,5], [6,7]], tslcs) +end + +let + rn = @reaction_network begin + (k1, k2), A <--> 2B + k3, A --> C + D + (k4, k5), C + D <--> E + k6, 2B --> F + (k7, k8), F <--> 2G + (k9, k10), 2G <--> H + k11, H --> F + end + + rcs, D = reactioncomplexes(rn) + slcs = stronglinkageclasses(rn) + tslcs = terminallinkageclasses(rn) + @test length(slcs) == 3 + @test length(tslcs) == 2 + @test issubset([[1,2], [3,4], [5,6,7]], slcs) + @test issubset([[3,4], [5,6,7]], tslcs) +end From c57ad8c16f1185c73137d50e00a5bbeaac91b9fd Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 18 Jun 2024 12:49:38 -0400 Subject: [PATCH 320/446] added tests --- src/Catalyst.jl | 2 +- src/network_analysis.jl | 19 +++++++++++-------- src/reactionsystem.jl | 2 ++ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index f37b67a7f5..cec9cca52f 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -127,7 +127,7 @@ export @reaction_network, @network_component, @reaction, @species include("network_analysis.jl") export reactioncomplexmap, reactioncomplexes, incidencemat export complexstoichmat -export complexoutgoingmat, incidencematgraph, linkageclasses, deficiency, subnetworks +export complexoutgoingmat, incidencematgraph, linkageclasses, stronglinkageclasses, terminallinkageclasses, deficiency, subnetworks export linkagedeficiencies, isreversible, isweaklyreversible export conservationlaws, conservedquantities, conservedequations, conservationlaw_constants diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 9dcf3518fa..b6f2954ce0 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -373,7 +373,7 @@ function stronglinkageclasses(rn::ReactionSystem) nps.stronglinkageclasses end -stronglinkageclasses(incidencegraph) = Graphs.strongly_connected_components(g) +stronglinkageclasses(incidencegraph) = Graphs.strongly_connected_components(incidencegraph) """ terminallinkageclasses(rn::ReactionSystem) @@ -382,19 +382,22 @@ stronglinkageclasses(incidencegraph) = Graphs.strongly_connected_components(g) """ function terminallinkageclasses(rn::ReactionSystem) - slcs = stronglinkageclasses(rn) - - tslcs = filter!(slcs, lc->isterminal(lc)) - tslcs + nps = get_networkproperties(rn) + if isempty(nps.terminallinkageclasses) + slcs = stronglinkageclasses(rn) + tslcs = filter(lc->isterminal(lc, rn), slcs) + nps.terminallinkageclasses = tslcs + end + nps.terminallinkageclasses end function isterminal(lc::Vector, rn::ReactionSystem) imat = incidencemat(rn) - for col in columns(imat) - s = findfirst(==(-1), @view D[:, r]) + for r in 1:size(imat, 2) + s = findfirst(==(-1), @view imat[:, r]) if s in Set(lc) - p = findfirst(==(1), @view D[:, r]) + p = findfirst(==(1), @view imat[:, r]) p in Set(lc) ? continue : return false end end diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index f0e162930d..c45231f502 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -94,6 +94,8 @@ Base.@kwdef mutable struct NetworkProperties{I <: Integer, V <: BasicSymbolic{Re complexoutgoingmat::Union{Matrix{Int}, SparseMatrixCSC{Int, Int}} = Matrix{Int}(undef, 0, 0) incidencegraph::Graphs.SimpleDiGraph{Int} = Graphs.DiGraph() linkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0) + stronglinkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0) + terminallinkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0) deficiency::Int = 0 end #! format: on From 64d83fda8c93373764239d965c8d743a8817bd13 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 18 Jun 2024 15:55:09 -0400 Subject: [PATCH 321/446] rebase --- src/network_analysis.jl | 45 ----------------------------------------- 1 file changed, 45 deletions(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index b6f2954ce0..f60bfcbc4a 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -359,51 +359,6 @@ end linkageclasses(incidencegraph) = Graphs.connected_components(incidencegraph) -""" - stronglinkageclasses(rn::ReactionSystem) - - Return the strongly connected components of a reaction network's incidence graph (i.e. sub-groups of reaction complexes such that every complex is reachable from every other one in the sub-group). -""" - -function stronglinkageclasses(rn::ReactionSystem) - nps = get_networkproperties(rn) - if isempty(nps.stronglinkageclasses) - nps.stronglinkageclasses = stronglinkageclasses(incidencematgraph(rn)) - end - nps.stronglinkageclasses -end - -stronglinkageclasses(incidencegraph) = Graphs.strongly_connected_components(incidencegraph) - -""" - terminallinkageclasses(rn::ReactionSystem) - - Return the terminal strongly connected components of a reaction network's incidence graph (i.e. sub-groups of reaction complexes that are 1) strongly connected and 2) every reaction in the component produces a complex in the component). -""" - -function terminallinkageclasses(rn::ReactionSystem) - nps = get_networkproperties(rn) - if isempty(nps.terminallinkageclasses) - slcs = stronglinkageclasses(rn) - tslcs = filter(lc->isterminal(lc, rn), slcs) - nps.terminallinkageclasses = tslcs - end - nps.terminallinkageclasses -end - -function isterminal(lc::Vector, rn::ReactionSystem) - imat = incidencemat(rn) - - for r in 1:size(imat, 2) - s = findfirst(==(-1), @view imat[:, r]) - if s in Set(lc) - p = findfirst(==(1), @view imat[:, r]) - p in Set(lc) ? continue : return false - end - end - true -end - @doc raw""" deficiency(rn::ReactionSystem) From 4aa5e974a35821c587ac8f5921faac13fb9f69f9 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 18 Jun 2024 15:56:05 -0400 Subject: [PATCH 322/446] def one --- src/network_analysis.jl | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index f60bfcbc4a..25d9c1c09e 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -886,3 +886,22 @@ function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) end prod end + +### DEFICIENCY ONE + +""" + satisfiesdeficiencyone(rn::ReactionSystem) + + Check if a reaction network obeys the conditions of the deficiency one theorem, which ensures that there is only one equilibrium for every positive stoichiometric compatibility class. +""" + +function satisfiesdeficiencyone(rn::ReactionSystem) + δ = deficiency(rn) + δ_l = linkagedeficiencies(rn) + + complexes, D = reactioncomplexes(rn) + lcs = linkageclasses(rn); tslcs = terminallinkageclasses(rn) + + δ_l .<= 1 && sum(δ_l) = δ && length(lcs) == length(tslcs) +end + From a4e8b6516214d8521f34e899d9a2dee25ca65a17 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 19 Jun 2024 12:17:01 -0400 Subject: [PATCH 323/446] JuliaFormatter --- src/Catalyst.jl | 3 ++- src/network_analysis.jl | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index cec9cca52f..84b1a31b71 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -127,7 +127,8 @@ export @reaction_network, @network_component, @reaction, @species include("network_analysis.jl") export reactioncomplexmap, reactioncomplexes, incidencemat export complexstoichmat -export complexoutgoingmat, incidencematgraph, linkageclasses, stronglinkageclasses, terminallinkageclasses, deficiency, subnetworks +export complexoutgoingmat, incidencematgraph, linkageclasses, stronglinkageclasses, + terminallinkageclasses, deficiency, subnetworks export linkagedeficiencies, isreversible, isweaklyreversible export conservationlaws, conservedquantities, conservedequations, conservationlaw_constants diff --git a/src/network_analysis.jl b/src/network_analysis.jl index b6f2954ce0..4db0913bf7 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -365,7 +365,7 @@ linkageclasses(incidencegraph) = Graphs.connected_components(incidencegraph) Return the strongly connected components of a reaction network's incidence graph (i.e. sub-groups of reaction complexes such that every complex is reachable from every other one in the sub-group). """ -function stronglinkageclasses(rn::ReactionSystem) +function stronglinkageclasses(rn::ReactionSystem) nps = get_networkproperties(rn) if isempty(nps.stronglinkageclasses) nps.stronglinkageclasses = stronglinkageclasses(incidencematgraph(rn)) @@ -381,17 +381,17 @@ stronglinkageclasses(incidencegraph) = Graphs.strongly_connected_components(inci Return the terminal strongly connected components of a reaction network's incidence graph (i.e. sub-groups of reaction complexes that are 1) strongly connected and 2) every reaction in the component produces a complex in the component). """ -function terminallinkageclasses(rn::ReactionSystem) +function terminallinkageclasses(rn::ReactionSystem) nps = get_networkproperties(rn) if isempty(nps.terminallinkageclasses) slcs = stronglinkageclasses(rn) - tslcs = filter(lc->isterminal(lc, rn), slcs) + tslcs = filter(lc -> isterminal(lc, rn), slcs) nps.terminallinkageclasses = tslcs end nps.terminallinkageclasses end -function isterminal(lc::Vector, rn::ReactionSystem) +function isterminal(lc::Vector, rn::ReactionSystem) imat = incidencemat(rn) for r in 1:size(imat, 2) From 9b868d04054e3479297cf975e2d2860a21c3ce97 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 25 Jun 2024 13:20:44 -0400 Subject: [PATCH 324/446] commented code --- src/network_analysis.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 4db0913bf7..b8166c0360 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -378,7 +378,7 @@ stronglinkageclasses(incidencegraph) = Graphs.strongly_connected_components(inci """ terminallinkageclasses(rn::ReactionSystem) - Return the terminal strongly connected components of a reaction network's incidence graph (i.e. sub-groups of reaction complexes that are 1) strongly connected and 2) every reaction in the component produces a complex in the component). + Return the terminal strongly connected components of a reaction network's incidence graph (i.e. sub-groups of reaction complexes that are 1) strongly connected and 2) every outgoing reaction from a complex in the component produces a complex also in the component). """ function terminallinkageclasses(rn::ReactionSystem) @@ -391,11 +391,16 @@ function terminallinkageclasses(rn::ReactionSystem) nps.terminallinkageclasses end + +# Check whether a given linkage class in a reaction network is terminal, i.e. all outgoing reactions from complexes in the linkage class produce a complex also in hte linkage class function isterminal(lc::Vector, rn::ReactionSystem) imat = incidencemat(rn) for r in 1:size(imat, 2) + # Find the index of the reactant complex for a given reaction s = findfirst(==(-1), @view imat[:, r]) + + # If the reactant complex is in the linkage class, check whether the product complex is also in the linkage class. If any of them are not, return false. if s in Set(lc) p = findfirst(==(1), @view imat[:, r]) p in Set(lc) ? continue : return false From 2728402e415109b10d00c5247f8b622a9ed607a1 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 25 Jun 2024 13:32:10 -0400 Subject: [PATCH 325/446] commented tests --- test/network_analysis/network_properties.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/network_analysis/network_properties.jl b/test/network_analysis/network_properties.jl index baee75e32a..811d6418cc 100644 --- a/test/network_analysis/network_properties.jl +++ b/test/network_analysis/network_properties.jl @@ -328,6 +328,8 @@ end ### STRONG LINKAGE CLASS TESTS + +# a) Checks that strong/terminal linkage classes are correctly found. Should identify the (A, B+C) linkage class as non-terminal, since B + C produces D let rn = @reaction_network begin (k1, k2), A <--> B + C @@ -347,6 +349,7 @@ let @test issubset([[3,4,5], [6,7]], tslcs) end +# b) Makes the D + E --> G reaction irreversible. Thus, (D+E) becomes a non-terminal linkage class. Checks whether correctly identifies both (A, B+C) and (D+E) as non-terminal let rn = @reaction_network begin (k1, k2), A <--> B + C @@ -366,6 +369,7 @@ let @test issubset([[3,4,5], [7]], tslcs) end +# From a), makes the B + C <--> D reaction reversible. Thus, the non-terminal (A, B+C) linkage class gets absorbed into the terminal (A, B+C, D, E, 2F) linkage class, and the terminal linkage classes and strong linkage classes coincide. let rn = @reaction_network begin (k1, k2), A <--> B + C @@ -385,6 +389,7 @@ let @test issubset([[1,2,3,4,5], [6,7]], tslcs) end +# Simple test for strong and terminal linkage classes let rn = @reaction_network begin (k1, k2), A <--> 2B From a13d440f4db86bfde707dc6d746680b6e30c5fb8 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:51:13 -0400 Subject: [PATCH 326/446] Update HISTORY.md Co-authored-by: Sam Isaacson --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 435dd22ff7..f7a0b2ef55 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,7 +5,7 @@ ## Catalyst 14.0 #### Breaking changes -Catalyst v14 was prompted by the release of ModelingToolkit v9. This introduced several breaking changes to the package. A summary of these (and how to handle them) can be found [here](https://docs.sciml.ai/Catalyst/stable/v14_migration_guide/). These are briefly summarised in the following bullet points: +Catalyst v14 was prompted by the (breaking) release of ModelingToolkit v9, which introduced several breaking changes to Catalyst. A summary of these (and how to handle them) can be found [here](https://docs.sciml.ai/Catalyst/stable/v14_migration_guide/). These are briefly summarised in the following bullet points: - `ReactionSystem`s must now be *complete* before they are exposed to most forms of simulation and analysis. With the exception of `ReactionSystem`s created through the `@reaction_network` macro, all `ReactionSystem`s are created incomplete. The `complete` function can be used to generate complete `ReactionSystem`s from incomplete ones. - The `states` function has been replaced with `unknowns`. The `get_states` function has been replaced with `get_unknowns`. - Support for most units (with the exception of `s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`) has been dropped until further notice. From c3d848e55c598b6c0f3a04d3f7553e32b2d508e0 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:51:42 -0400 Subject: [PATCH 327/446] Update HISTORY.md Co-authored-by: Sam Isaacson --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index f7a0b2ef55..f9ed15e7b1 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,7 +6,7 @@ #### Breaking changes Catalyst v14 was prompted by the (breaking) release of ModelingToolkit v9, which introduced several breaking changes to Catalyst. A summary of these (and how to handle them) can be found [here](https://docs.sciml.ai/Catalyst/stable/v14_migration_guide/). These are briefly summarised in the following bullet points: -- `ReactionSystem`s must now be *complete* before they are exposed to most forms of simulation and analysis. With the exception of `ReactionSystem`s created through the `@reaction_network` macro, all `ReactionSystem`s are created incomplete. The `complete` function can be used to generate complete `ReactionSystem`s from incomplete ones. +- `ReactionSystem`s must now be marked *complete* before they are exposed to most forms of simulation and analysis. With the exception of `ReactionSystem`s created through the `@reaction_network` macro, all `ReactionSystem`s are *not* marked complete upon construction. The `complete` function can be used to mark `ReactionSystem`s as complete. To construct a `ReactionSystem` that is not marked complete via the DSL the new `@network_component` macro can be used. - The `states` function has been replaced with `unknowns`. The `get_states` function has been replaced with `get_unknowns`. - Support for most units (with the exception of `s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`) has been dropped until further notice. - Problem parameter values are now accessed through `prob.ps[p]` (rather than `prob[p]`). From 11030967813bda4bfdd15dbf9035533bb3029255 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:52:03 -0400 Subject: [PATCH 328/446] Update HISTORY.md Co-authored-by: Sam Isaacson --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index f9ed15e7b1..e534bd6ee2 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -8,7 +8,7 @@ Catalyst v14 was prompted by the (breaking) release of ModelingToolkit v9, which introduced several breaking changes to Catalyst. A summary of these (and how to handle them) can be found [here](https://docs.sciml.ai/Catalyst/stable/v14_migration_guide/). These are briefly summarised in the following bullet points: - `ReactionSystem`s must now be marked *complete* before they are exposed to most forms of simulation and analysis. With the exception of `ReactionSystem`s created through the `@reaction_network` macro, all `ReactionSystem`s are *not* marked complete upon construction. The `complete` function can be used to mark `ReactionSystem`s as complete. To construct a `ReactionSystem` that is not marked complete via the DSL the new `@network_component` macro can be used. - The `states` function has been replaced with `unknowns`. The `get_states` function has been replaced with `get_unknowns`. -- Support for most units (with the exception of `s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`) has been dropped until further notice. +- Support for most units (with the exception of `s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`) has currently been dropped by ModelingToolkit, and hence they are unavailable via Catalyst too. Its is expected that eventually support for relevant chemical units such as molar will return to ModelingToolkit (and should then immediately work in Catalyst too). - Problem parameter values are now accessed through `prob.ps[p]` (rather than `prob[p]`). - A significant bug prevents the safe application of the `remake` function on problems for which `remove_conserved = true` was used. - The `reactionparams`, `numreactionparams`, and `reactionparamsmap` functions have been removed. From 66cd51adf678934301477ab628532390200bdb47 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:52:20 -0400 Subject: [PATCH 329/446] Update HISTORY.md Co-authored-by: Sam Isaacson --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index e534bd6ee2..ca8d97a9cf 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -10,7 +10,7 @@ Catalyst v14 was prompted by the (breaking) release of ModelingToolkit v9, which - The `states` function has been replaced with `unknowns`. The `get_states` function has been replaced with `get_unknowns`. - Support for most units (with the exception of `s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`) has currently been dropped by ModelingToolkit, and hence they are unavailable via Catalyst too. Its is expected that eventually support for relevant chemical units such as molar will return to ModelingToolkit (and should then immediately work in Catalyst too). - Problem parameter values are now accessed through `prob.ps[p]` (rather than `prob[p]`). -- A significant bug prevents the safe application of the `remake` function on problems for which `remove_conserved = true` was used. +- A significant bug prevents the safe application of the `remake` function on problems for which `remove_conserved = true` was used when updating the values of initial conditions. Instead, the values of each conserved constant must be directly specified. - The `reactionparams`, `numreactionparams`, and `reactionparamsmap` functions have been removed. - To be more consistent with ModelingToolkit's immutability requirement for systems, we have removed API functions that mutate `ReactionSystem`s such as `addparam!`, `addreaction!`, `addspecies`, `@add_reactions`, and `merge!`. Please use `ModelingToolkit.extend` and `ModelingToolkit.compose` to generate new merged and/or composed `ReactionSystem`s from multiple component systems. From d1897c59195f42462b986911f98cadcb43e074a0 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:52:30 -0400 Subject: [PATCH 330/446] Update HISTORY.md Co-authored-by: Sam Isaacson --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index ca8d97a9cf..3713a9d47e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -11,7 +11,7 @@ Catalyst v14 was prompted by the (breaking) release of ModelingToolkit v9, which - Support for most units (with the exception of `s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`) has currently been dropped by ModelingToolkit, and hence they are unavailable via Catalyst too. Its is expected that eventually support for relevant chemical units such as molar will return to ModelingToolkit (and should then immediately work in Catalyst too). - Problem parameter values are now accessed through `prob.ps[p]` (rather than `prob[p]`). - A significant bug prevents the safe application of the `remake` function on problems for which `remove_conserved = true` was used when updating the values of initial conditions. Instead, the values of each conserved constant must be directly specified. -- The `reactionparams`, `numreactionparams`, and `reactionparamsmap` functions have been removed. +- The `reactionparams`, `numreactionparams`, and `reactionparamsmap` functions have been deprecated and removed. - To be more consistent with ModelingToolkit's immutability requirement for systems, we have removed API functions that mutate `ReactionSystem`s such as `addparam!`, `addreaction!`, `addspecies`, `@add_reactions`, and `merge!`. Please use `ModelingToolkit.extend` and `ModelingToolkit.compose` to generate new merged and/or composed `ReactionSystem`s from multiple component systems. #### General changes From 3d55c45b25f66ff2e7bf69d7e09c40ade29e29cc Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:52:47 -0400 Subject: [PATCH 331/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 5d426a5e60..9ab3297b89 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -120,7 +120,7 @@ t = default_t() nothing # hide ``` -Similarly, the time differential (primarily relevant when creating combined reaction-equation models) used to be declared through +Similarly, the time differential (primarily relevant when creating combined reaction-ODE models) used to be declared through ```@example v14_migration_3 D = Differential(t) nothing # hide From ab2ad80b59235a5cfad4a5cbed630f986dc738d7 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:52:57 -0400 Subject: [PATCH 332/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 9ab3297b89..187491edfe 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -174,7 +174,7 @@ oprob = ODEProblem(rn, u0, (0.0, 10.0), ps; remove_conserved = true) sol(oprob) # hide ``` -is perfectly fine, attempting to then modify `oprob` (in any manner!) is not possible: +is perfectly fine, attempting to then modify any initial conditions or the value of the conservation constant in `oprob` will silently fail: ```@example v14_migration_5 oprob_remade = remake(oprob; u0 = [:X1 => 5.0]) # NEVER do this. sol(oprob) From 0f4930e0c294aabde2841412a806dfb11e9d0cda Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:53:12 -0400 Subject: [PATCH 333/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 187491edfe..6061460dee 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -182,7 +182,7 @@ sol(oprob) ``` This might generate a silent error, where the remade problem is different from the intended one. -This bug was likely present on earlier versions as well, but was only recently discovered. While we hope it will be fixed soon, the bug is in ModelingToolkit, and will not be fixed until its maintainers find the time to do so. +This bug was likely present on earlier versions as well, but was only recently discovered. While we hope it will be fixed soon, the issue is in ModelingToolkit, and will not be fixed until its maintainers find the time to do so. #### Depending on parameter order is even more dangerous than before In early versions of Catalyst, parameters and species were provided as vectors (e.g. `[1.0, 2.0]`) rather than maps (e.g. `[p => 1.0, d => 2.0]`). While we have already *strongly* recommended users to use the map form (or they might produce unintended results), the vector form is still (technically). Due to recent internal ModelingToolkit updates, the risk of unexpected behaviour when providing parameter values as vectors is even larger than before. From c486367a1355d4b3d9ae0223956dbb4f75f56e75 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:53:20 -0400 Subject: [PATCH 334/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 6061460dee..d4d3c2d64e 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -180,7 +180,7 @@ oprob_remade = remake(oprob; u0 = [:X1 => 5.0]) # NEVER do this. sol(oprob) # hide ``` -This might generate a silent error, where the remade problem is different from the intended one. +This might generate a silent error, where the remade problem is different from the intended one (the value of the conserved constant will not be updated correctly). This bug was likely present on earlier versions as well, but was only recently discovered. While we hope it will be fixed soon, the issue is in ModelingToolkit, and will not be fixed until its maintainers find the time to do so. From 13f3fe0083dbd93d2b3ab81cb19e4639283f5746 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:53:49 -0400 Subject: [PATCH 335/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index d4d3c2d64e..a6aecfa198 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -185,7 +185,7 @@ This might generate a silent error, where the remade problem is different from t This bug was likely present on earlier versions as well, but was only recently discovered. While we hope it will be fixed soon, the issue is in ModelingToolkit, and will not be fixed until its maintainers find the time to do so. #### Depending on parameter order is even more dangerous than before -In early versions of Catalyst, parameters and species were provided as vectors (e.g. `[1.0, 2.0]`) rather than maps (e.g. `[p => 1.0, d => 2.0]`). While we have already *strongly* recommended users to use the map form (or they might produce unintended results), the vector form is still (technically). Due to recent internal ModelingToolkit updates, the risk of unexpected behaviour when providing parameter values as vectors is even larger than before. +In early versions of Catalyst, parameters and species were provided as vectors (e.g. `[1.0, 2.0]`) rather than maps (e.g. `[p => 1.0, d => 2.0]`). While we previously *strongly* recommended users to use the map form (or they might produce unintended results), the vector form was still supported (technically). Due to recent internal ModelingToolkit updates, the purely numeric form is no longer supported and should never be used -- it will potentially lead to incorrect values for parameters and/or initial conditions. Note that if `rn` is a complete `ReactionSystem` you can now specify such mappings via `[rn.p => 1.0, rn.d => 2.0]`. *Users should never use vector-forms to represent parameter and species values* From 0c38ab8a6eb46bc6edf39d10d8075ae886de8a0f Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:54:35 -0400 Subject: [PATCH 336/446] Update HISTORY.md Co-authored-by: Sam Isaacson --- HISTORY.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 3713a9d47e..6318a804c2 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -15,7 +15,15 @@ Catalyst v14 was prompted by the (breaking) release of ModelingToolkit v9, which - To be more consistent with ModelingToolkit's immutability requirement for systems, we have removed API functions that mutate `ReactionSystem`s such as `addparam!`, `addreaction!`, `addspecies`, `@add_reactions`, and `merge!`. Please use `ModelingToolkit.extend` and `ModelingToolkit.compose` to generate new merged and/or composed `ReactionSystem`s from multiple component systems. #### General changes -- The `default_t()` and `default_time_deriv()` functions are now the preferred approaches for creating the default time independent variable and its differential. +- The `default_t()` and `default_time_deriv()` functions are now the preferred approaches for creating the default time independent variable and its differential. i.e. + ```julia + # do + t = default_t() + @species A(t) + + # avoid + @variables t + @species A(t) - It is now possible to add metadata to individual reactions, e.g. using: ```julia rn = @reaction_network begin From 236c8c076fc108a19106375a14ebcdd23669a799 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:57:03 -0400 Subject: [PATCH 337/446] Update HISTORY.md Co-authored-by: Sam Isaacson --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 6318a804c2..2ee70fc0b2 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -56,7 +56,7 @@ p = [:p => 1.0, :d => 0.5] steady_state = [2.0] steady_state_stability(steady_state, rn, p) ``` -Here, `steady_state_stability` takes an optional argument `tol = 10*sqrt(eps())`, which is used to determine whether a eigenvalue real part is reliably less than 0. +Here, `steady_state_stability` takes an optional argument `tol = 10*sqrt(eps())`, which is used to check that the real part of all eigenvalues are at least `tol` away from zero. Eigenvalues within `tol` of zero indicate that stability may not be reliably calculated. - Added a DSL option, `@combinatoric_ratelaws`, which can be used to toggle whether to use combinatorial rate laws within the DSL (this feature was already supported for programmatic modelling). Example: ```julia # Creates model. From aa031fca707616c9b5c9c73e6c5d0e6fbef326c2 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:57:22 -0400 Subject: [PATCH 338/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index a6aecfa198..dc07ffdf63 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -11,7 +11,7 @@ In ModelingToolkit v9 (and thus also Catalyst v14) all systems (e.g. `ReactionSy - Only incomplete systems can be [composed with other systems to form hierarchical models](@ref compositional_modeling). A model's completeness depends on how it was created: -- Models created programmatically (using the `ReactionSystem` constructor) are *incomplete*. +- Models created programmatically (using the `ReactionSystem` constructor) are *not marked as complete* by default. - Models created using the `@reaction_network` DSL are *complete*. - To create *incomplete models using the DSL*, use the `@network_component` macro (in all other aspects identical to `@reaction_network`). - Models generated through the `compose` and `extend` functions are *incomplete*. From 06ff56bb8893d3c50f871acd1a20498d887dacf0 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:57:42 -0400 Subject: [PATCH 339/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index dc07ffdf63..0d31eb13a5 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -6,7 +6,7 @@ Catalyst is built on the [ModelingToolkit.jl](https://github.com/SciML/ModelingT Catalyst version 14 also introduces several new features. These will not be discussed here, however, they are described in Catalyst's [history file](https://github.com/SciML/Catalyst.jl/blob/master/HISTORY.md). ## System completeness -In ModelingToolkit v9 (and thus also Catalyst v14) all systems (e.g. `ReactionSystem`s and `ODESystem`s) are either *complete* or *incomplete* (completeness was already a thing, however, recent updates mean that the user now has to be aware of this). Complete and incomplete systems differ in that +In ModelingToolkit v9 (and thus also Catalyst v14) all systems (e.g. `ReactionSystem`s and `ODESystem`s) are either *complete* or *incomplete*. Complete and incomplete systems differ in that - Only complete systems can be used as inputs to simulations or most tools for model analysis. - Only incomplete systems can be [composed with other systems to form hierarchical models](@ref compositional_modeling). From af04a85780ffeee06e8a0e3a9ef138f6feb30eff Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:57:52 -0400 Subject: [PATCH 340/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 0d31eb13a5..e5a9e1e06b 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -12,7 +12,7 @@ In ModelingToolkit v9 (and thus also Catalyst v14) all systems (e.g. `ReactionSy A model's completeness depends on how it was created: - Models created programmatically (using the `ReactionSystem` constructor) are *not marked as complete* by default. -- Models created using the `@reaction_network` DSL are *complete*. +- Models created using the `@reaction_network` DSL are *automatically marked as complete*. - To create *incomplete models using the DSL*, use the `@network_component` macro (in all other aspects identical to `@reaction_network`). - Models generated through the `compose` and `extend` functions are *incomplete*. From 589812f1a7422bebe5f3e5df9e9d2509365411b4 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:58:22 -0400 Subject: [PATCH 341/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index e5a9e1e06b..b4c78c1ec5 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -13,7 +13,7 @@ In ModelingToolkit v9 (and thus also Catalyst v14) all systems (e.g. `ReactionSy A model's completeness depends on how it was created: - Models created programmatically (using the `ReactionSystem` constructor) are *not marked as complete* by default. - Models created using the `@reaction_network` DSL are *automatically marked as complete*. -- To create *incomplete models using the DSL*, use the `@network_component` macro (in all other aspects identical to `@reaction_network`). +- To *use the DSL to create models that are not marked as complete*, use the `@network_component` macro (which in all other aspects is identical to `@reaction_network`). - Models generated through the `compose` and `extend` functions are *incomplete*. Furthermore, any systems generated through e.g. `convert(ODESystem, rs)` are also complete. From 2d1a383072b3037017cf233a162f7b5083c25cf6 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 17:58:32 -0400 Subject: [PATCH 342/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index b4c78c1ec5..4ca82952b6 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -14,7 +14,7 @@ A model's completeness depends on how it was created: - Models created programmatically (using the `ReactionSystem` constructor) are *not marked as complete* by default. - Models created using the `@reaction_network` DSL are *automatically marked as complete*. - To *use the DSL to create models that are not marked as complete*, use the `@network_component` macro (which in all other aspects is identical to `@reaction_network`). -- Models generated through the `compose` and `extend` functions are *incomplete*. +- Models generated through the `compose` and `extend` functions are *not marked as complete*. Furthermore, any systems generated through e.g. `convert(ODESystem, rs)` are also complete. From a30f105fe8d35c5db0c40a447e94ad9af5c58cf9 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:00:37 -0400 Subject: [PATCH 343/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 4ca82952b6..878e9bbb92 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -30,7 +30,7 @@ rxs = [ ] @named rs = ReactionSystem(rxs, t) ``` -Here we have created an incomplete model. If our model is ready (i.e. we do not wish to compose it with additional models) we mark it as complete: +Here we have created a model that is not marked as complete. If our model is ready (i.e. we do not wish to compose it with additional models) we mark it as complete: ```@example v14_migration_1 rs = complete(rs) ``` From 52fca236d206f4904209eb9ae239bbff0e0e4a5a Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:00:56 -0400 Subject: [PATCH 344/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 878e9bbb92..02fcdb9b21 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -34,7 +34,7 @@ Here we have created a model that is not marked as complete. If our model is rea ```@example v14_migration_1 rs = complete(rs) ``` -Here, `complete` does not change the input model, but simply creates a new (complete) model. We hence overwrite our model variable (`rs`) with `complete`'s output. We can confirm that our model is complete using the `Catalyst.iscomplete` function: +Here, `complete` does not change the input model, but simply creates a new model that is tagged as complete. We hence overwrite our model variable (`rs`) with `complete`'s output. We can confirm that our model is complete using the `Catalyst.iscomplete` function: ```@example v14_migration_1 Catalyst.iscomplete(rs) ``` From 4ee1126df5b9a286fd5ce6c72c19469de063b740 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:01:12 -0400 Subject: [PATCH 345/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 02fcdb9b21..ad200439e6 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -114,7 +114,7 @@ using Catalyst @variables t nothing # hide ``` -A new, preferred, interface has now been introduced: +MTKv9 has introduced a standard global time variable, and as such a new, preferred, interface has been developed: ```@example v14_migration_3 t = default_t() nothing # hide From 2e6511ac26ed1725172e95cdbf46a7052512bf29 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:01:29 -0400 Subject: [PATCH 346/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index ad200439e6..c6cf8fd531 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -49,7 +49,7 @@ sol = solve(oprob) plot(sol) ``` -If we wish to first convert out `ReactionSystem` to an `ODESystem`, the `ODESystem` will be incomplete: +If we wish to first manually convert our `ReactionSystem` to an `ODESystem`, the generated `ODESystem` will *not* be marked as complete ```@example v14_migration_1 osys = convert(ODESystem, rs) Catalyst.iscomplete(osys) From e83139e424a58a6aea057a0b17c9c30161dd7c16 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:01:46 -0400 Subject: [PATCH 347/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index c6cf8fd531..ce6adf9d6e 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -65,7 +65,7 @@ plot(sol) ``` ## Unknowns instead of states -Previously, "states" was used as a term for system variables (both species and non-species variables). Now, the term "unknowns" will be used instead. This means that there have been a number of changes to function names (e.g. `states` => `unknowns` and `get_states` => `get_unknowns`). +Previously, "states" was used as a term for system variables (both species and non-species variables). MTKv9 has switched to using the term "unknowns" instead. This means that there have been a number of changes to function names (e.g. `states` => `unknowns` and `get_states` => `get_unknowns`). E.g. here we declare a `ReactionSystem` model containing both species and non-species unknowns: ```@example v14_migration_2 From 253bd3fc3bf190878dcea9a70761b3b575b5e9d7 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:02:03 -0400 Subject: [PATCH 348/446] Update docs/src/v14_migration_guide.md Co-authored-by: Sam Isaacson --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index ce6adf9d6e..8634945793 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -103,7 +103,7 @@ While this should lead to long-term improvements, unfortunately, as part of the The maintainers of ModelingTOolkit have been notified of this issue. We are unsure when this will be fixed, however, we do not think it will be a permanent change. ## Removed support for system-mutating functions -According to ModelingToolkit policy, created systems should not be modified. In accordance with this, the following functions have been deprecated: `addparam!`, `addreaction!`, `addspecies!`, `@add_reactions`, and `merge!`. Please use `ModelingToolkit.extend` and `ModelingToolkit.compose` to generate new merged and/or composed `ReactionSystems` from multiple component systems. +According to the ModelingToolkit system API, systems should not be mutable. In accordance with this, the following functions have been deprecated: `addparam!`, `addreaction!`, `addspecies!`, `@add_reactions`, and `merge!`. Please use `ModelingToolkit.extend` and `ModelingToolkit.compose` to generate new merged and/or composed `ReactionSystems` from multiple component systems. It is still possible to add default values to a created `ReactionSystem`, i.e. the `setdefaults!` function is still supported. From e3e17cee910d151679e2a5f93dbc7a89341e8209 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 28 Jun 2024 18:04:12 -0400 Subject: [PATCH 349/446] change wrong statement on convert compelteness --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 8634945793..8e1e9737c5 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -16,7 +16,7 @@ A model's completeness depends on how it was created: - To *use the DSL to create models that are not marked as complete*, use the `@network_component` macro (which in all other aspects is identical to `@reaction_network`). - Models generated through the `compose` and `extend` functions are *not marked as complete*. -Furthermore, any systems generated through e.g. `convert(ODESystem, rs)` are also complete. +Furthermore, any systems generated through e.g. `convert(ODESystem, rs)` are *not marked as complete*. Complete models can be generated from incomplete models through the `complete` function. Here is a workflow where we take completeness into account in the simulation of a simple birth-death process. ```@example v14_migration_1 From ccb5f6fd36f29fa61ed85a466a9905f59084556a Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:08:25 -0400 Subject: [PATCH 350/446] Update README.md Co-authored-by: Sam Isaacson --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e3ab7922a..3ef7efabd2 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model - The [Catalyst.jl API](http://docs.sciml.ai/Catalyst/stable/api/catalyst_api) provides functionality for extending networks, building networks programmatically, and for composing multiple networks together. -- Generated models can be simulated using any +- Leveraging ModelingToolkit, generated models can be converted to symbolic reaction rate equation ODE models, symbolic Chemical Langevin Equation models, and symbolic stochastic chemical kinetics (jump process) models. These can be simulated using any [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) [ODE/SDE/jump solver](@ref ref), and can be used within `EnsembleProblem`s for carrying out [parallelized parameter sweeps and statistical sampling](@ref ref). Plot recipes From 56532045af725351e26170fc1184b6bbaf78da1e Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:08:35 -0400 Subject: [PATCH 351/446] Update README.md Co-authored-by: Sam Isaacson --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ef7efabd2..09448d25e2 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model - Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref ref) for generating ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref ref) for all system types. -- A [network analysis suite](@ref ref) permits the computation of linkage classes, deficiencies, and +- A [network analysis suite](@ref ref) permits the computation of linkage classes, deficiencies, reversibility, and other network properties. reversibilities. - [Conservation laws can be detected and utilized](@ref ref) to reduce system sizes, and to generate non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). From c766ca6abf72b5c5c299b3013da79a365aad0d16 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:08:43 -0400 Subject: [PATCH 352/446] Update README.md Co-authored-by: Sam Isaacson --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 09448d25e2..4d9c5ab629 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,6 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref ref) for all system types. - A [network analysis suite](@ref ref) permits the computation of linkage classes, deficiencies, reversibility, and other network properties. - reversibilities. - [Conservation laws can be detected and utilized](@ref ref) to reduce system sizes, and to generate non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). - Catalyst reaction network models can be [coupled with differential and algebraic equations](@ref ref) From 4c64ea74675f238b871f75d8f638cfb1ea1827fc Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:10:42 -0400 Subject: [PATCH 353/446] Update README.md Co-authored-by: Sam Isaacson --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 4d9c5ab629..724d7fa5a4 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,7 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/).) - Model steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using - [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by nonlinear systems - solving using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). + [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by numerically solving steady-state nonlinear equations using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). - [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute bifurcation diagrams](@ref ref) of models' steady states (including finding periodic orbits). - [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute From f6faa8f16eb3c6a580a52e516018d46784aa3589 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:12:32 -0400 Subject: [PATCH 354/446] Update README.md Co-authored-by: Sam Isaacson --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 724d7fa5a4..80f6df5c52 100644 --- a/README.md +++ b/README.md @@ -85,8 +85,7 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model - [Steady states](@ref ref) (and their [stabilities](@ref ref)) can be computed for model ODE representations. #### Features of Catalyst composing with other packages -- [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to [perform model ODE - simulations](@ref ref). +- [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to numerically solver generated reaction rate equation ODE models. - [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) Can be used to [perform model SDE simulations](@ref ref). - [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) Can be used to [model jump From 052a46015852abb97a45136e00a58e9c2528635d Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:12:52 -0400 Subject: [PATCH 355/446] Update README.md Co-authored-by: Sam Isaacson --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 80f6df5c52..29acbaea95 100644 --- a/README.md +++ b/README.md @@ -86,8 +86,7 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model #### Features of Catalyst composing with other packages - [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to numerically solver generated reaction rate equation ODE models. -- [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) Can be used to [perform model - SDE simulations](@ref ref). +- [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) can be used to numerically solve generated Chemical Langevin Equation SDE models. - [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) Can be used to [model jump simulations](@ref ref). - Support for [parallelization of all simulations](@ref ref), including parallelization of From 171d3994c88d033f2818914b4792d2570eb22445 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:17:03 -0400 Subject: [PATCH 356/446] Update README.md Co-authored-by: Sam Isaacson --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 29acbaea95..c536f84dd7 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,7 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model #### Features of Catalyst composing with other packages - [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to numerically solver generated reaction rate equation ODE models. - [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) can be used to numerically solve generated Chemical Langevin Equation SDE models. -- [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) Can be used to [model jump - simulations](@ref ref). +- [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) can be used to numerically sample generated Stochastic Chemical Kinetics Jump Process models. - Support for [parallelization of all simulations](@ref ref), including parallelization of [ODE simulations on GPUs](@ref ref) using [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). From 37c660f3f25e4d3417a009b66169cb88c70e425f Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:17:21 -0400 Subject: [PATCH 357/446] Update README.md Co-authored-by: Sam Isaacson --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c536f84dd7..41f94843fe 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ model describes how the volume of a cell ($V$) is affected by a growth factor ($ factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ ($G \to Gᴾ$) is promoted by sunlight (modeled as the cyclic sinusoid $kₐ*(sin(t)+1)$), which phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($V$) -it undergoes through cell division. First, we declare our model: +it undergoes cell division. First, we declare our model: ```julia using Catalyst cell_model = @reaction_network begin From d42ee6ad4794914def260f158488dd4c9c0b25a0 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 28 Jun 2024 18:17:44 -0400 Subject: [PATCH 358/446] Update README.md Co-authored-by: Sam Isaacson --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41f94843fe..8736460c20 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ it undergoes cell division. First, we declare our model: ```julia using Catalyst cell_model = @reaction_network begin - @parameters Vₘₐₓ g Ω + @parameters Vₘ g Ω @default_noise_scaling Ω @equations begin D(V) ~ g*Gᴾ From dfa9ede990ee4d2664de54314bc70f8b36de3c81 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 28 Jun 2024 18:21:09 -0400 Subject: [PATCH 359/446] remove stuff that we should add back in later on --- README.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/README.md b/README.md index 8736460c20..813985aa0f 100644 --- a/README.md +++ b/README.md @@ -230,11 +230,6 @@ the [Julia Slack](https://julialang.slack.com) channels \#sciml-bridged and \#sc [Julia Zulip sciml-bridged channel](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged). For bugs or feature requests, [open an issue](https://github.com/SciML/Catalyst.jl/issues). -If you are interested in participating in the development of Catalyst, or integrating your package(s) -with it, developer documentation can be found [here](@ref ref). Are you a student (or similar) who -wishes to do a Google Summer of Code (or similar) project tied to Catalyst? Information on how to get -involved, including good first issues to get familiar with working on the package, can be found [here](@ref ref). - ## Supporting and citing Catalyst.jl The software in this ecosystem was developed as part of academic research. If you would like to help support it, please star the repository as such metrics may help us secure funding in the future. If @@ -254,8 +249,4 @@ could cite our work: pages = {1-19}, number = {10}, } -``` - -We also maintain a user survey, asking basic questions about how users utilise the package. The survey -is available [here](ref), and only takes about 5 minutes to fill out. We are grateful to those who -fill out the survey, as this helps us further develop the package. \ No newline at end of file +``` \ No newline at end of file From 974a6fa50898f6959b3f7af9d4928b9ae6014c1a Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 28 Jun 2024 19:40:44 -0400 Subject: [PATCH 360/446] up --- README.md | 102 +++++++++++++++++++++++++++--------------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 813985aa0f..022788958b 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model ## Features #### Features of Catalyst -- [The Catalyst DSL](@ref ref) provides a simple and readable format for manually specifying reaction +- [The Catalyst DSL](https://docs.sciml.ai/Catalyst/dev/model_creation/dsl_basics/) provides a simple and readable format for manually specifying reaction network models using chemical reaction notation. - Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, built on [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and @@ -61,59 +61,60 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model multiple networks together. - Leveraging ModelingToolkit, generated models can be converted to symbolic reaction rate equation ODE models, symbolic Chemical Langevin Equation models, and symbolic stochastic chemical kinetics (jump process) models. These can be simulated using any [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) - [ODE/SDE/jump solver](@ref ref), and can be used within `EnsembleProblem`s for carrying - out [parallelized parameter sweeps and statistical sampling](@ref ref). Plot recipes - are available for [visualization of all solutions](@ref ref). -- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref ref) for generating - ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref ref) for + [ODE/SDE/jump solver](https://docs.sciml.ai/Catalyst/dev/model_simulation/simulation_introduction/), and can be used within `EnsembleProblem`s for carrying + out [parallelized parameter sweeps and statistical sampling](https://docs.sciml.ai/Catalyst/dev/model_simulation/ensemble_simulations/). Plot recipes + are available for [visualization of all solutions](https://docs.sciml.ai/Catalyst/dev/model_simulation/simulation_plotting/). +- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](https://docs.sciml.ai/Catalyst/dev/model_creation/dsl_basics/#dsl_description_stoichiometries_decimal) for generating + ODE models, and symbolic expressions for stoichiometric coefficients [are supported](https://docs.sciml.ai/Catalyst/dev/model_creation/parametric_stoichiometry/) for all system types. -- A [network analysis suite](@ref ref) permits the computation of linkage classes, deficiencies, reversibility, and other network properties. -- [Conservation laws can be detected and utilized](@ref ref) to reduce system sizes, and to generate +- A [network analysis suite](https://docs.sciml.ai/Catalyst/dev/model_creation/network_analysis/) permits the computation of linkage classes, deficiencies, reversibility, and other network properties. +- [Conservation laws can be detected and utilized](https://docs.sciml.ai/Catalyst/dev/model_creation/network_analysis/#network_analysis_deficiency) to reduce system sizes, and to generate non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). -- Catalyst reaction network models can be [coupled with differential and algebraic equations](@ref ref) +- Catalyst reaction network models can be [coupled with differential and algebraic equations](https://docs.sciml.ai/Catalyst/dev/model_creation/constraint_equations/) (which are then incorporated during conversion to ODEs, SDEs, and steady state equations). -- Models can be [coupled with events](@ref ref) that affect the system and its state during simulations. +- Models can be [coupled with events](https://docs.sciml.ai/Catalyst/dev/model_creation/constraint_equations/#constraint_equations_events) that affect the system and its state during simulations. - By leveraging ModelingToolkit, users have a variety of options for generating optimized system representations to use in solvers. These include construction - of [dense or sparse Jacobians](@ref ref), [multithreading or parallelization of generated - derivative functions](@ref ref), [automatic classification of reactions into optimized - jump types for Gillespie type simulations](@ref ref), [automatic construction of - dependency graphs for jump systems](@ref ref), and more. + of [dense or sparse Jacobians](https://docs.sciml.ai/Catalyst/dev/model_simulation/ode_simulation_performance/#ode_simulation_performance_sparse_jacobian), [multithreading or parallelization of generated + derivative functions](https://docs.sciml.ai/Catalyst/dev/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation), [automatic classification of reactions into optimized + jump types for Gillespie type simulations](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#jump_types), [automatic construction of + dependency graphs for jump systems](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-Requiring-Dependency-Graphs), and more. - [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl) symbolic expressions and Julia `Expr`s can be obtained for all rate laws and functions determining the deterministic and stochastic terms within resulting ODE, SDE or jump models. -- [Steady states](@ref ref) (and their [stabilities](@ref ref)) can be computed for model ODE representations. +- [Steady states](https://docs.sciml.ai/Catalyst/dev/steady_state_functionality/homotopy_continuation/) (and their [stabilities](https://docs.sciml.ai/Catalyst/dev/steady_state_functionality/steady_state_stability_computation/)) can be computed for model ODE representations. #### Features of Catalyst composing with other packages - [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to numerically solver generated reaction rate equation ODE models. - [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) can be used to numerically solve generated Chemical Langevin Equation SDE models. - [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) can be used to numerically sample generated Stochastic Chemical Kinetics Jump Process models. -- Support for [parallelization of all simulations](@ref ref), including parallelization of - [ODE simulations on GPUs](@ref ref) using +- Support for [parallelization of all simulations](https://docs.sciml.ai/Catalyst/dev/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation), including parallelization of + [ODE simulations on GPUs](https://docs.sciml.ai/Catalyst/dev/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation_GPU) using [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). - [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX - expressions](@ref ref) corresponding to generated mathematical models or the + expressions](https://docs.sciml.ai/Catalyst/dev/model_creation/model_visualisation/#visualisation_latex) corresponding to generated mathematical models or the underlying set of reactions. -- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](@ref ref) - (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/).) +- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](https://docs.sciml.ai/Catalyst/dev/model_creation/model_visualisation/#visualisation_graphs) + (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). - Model steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by numerically solving steady-state nonlinear equations using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). - [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute - bifurcation diagrams](@ref ref) of models' steady states (including finding periodic orbits). + bifurcation diagrams](https://docs.sciml.ai/Catalyst/dev/steady_state_functionality/bifurcation_diagrams/) of models' steady states (including finding periodic orbits). - [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute - model [basins of attraction](@ref ref) and [Lyapunov spectrums](@ref ref). + model [basins of attraction](https://docs.sciml.ai/Catalyst/dev/steady_state_functionality/dynamical_systems/#dynamical_systems_basins_of_attraction) and [Lyapunov spectrums](https://docs.sciml.ai/Catalyst/dev/steady_state_functionality/dynamical_systems/#dynamical_systems_lyapunov_exponents). - [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used - to [perform structural identifiability analysis](@ref ref). + to [perform structural identifiability analysis](https://docs.sciml.ai/Catalyst/dev/inverse_problems/structural_identifiability/). - [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), - and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](@ref ref). + and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](https://sebapersson.github.io/PEtab.jl/stable/Define_in_julia/). - [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform - [global sensitivity analysis](@ref ref) of model behaviors. - + [global sensitivity analysis](https://docs.sciml.ai/Catalyst/dev/inverse_problems/global_sensitivity_analysis/) of model behaviors. +- [SciMLSensitivity.jl](https://github.com/SciML/SciMLSensitivity.jl) can be used to compute local sensitivities of functions containing forward model simulations. + #### Features of packages built upon Catalyst -- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref ref) via +- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](https://docs.sciml.ai/Catalyst/dev/model_creation/model_file_loading_and_export/#Loading-SBML-files-using-SBMLImporter.jl-and-SBMLToolkit.jl) via [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), - and [from BioNetGen .net files](@ref ref) and various stoichiometric matrix network representations + and [from BioNetGen .net files](https://docs.sciml.ai/Catalyst/dev/model_creation/model_file_loading_and_export/#file_loading_rni_net) and various stoichiometric matrix network representations using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). - [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic ModelingToolkit `ODESystem`s that represent moment closure approximations to moments of the @@ -126,9 +127,6 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model resulting stochastic chemical kinetics with delays models. - [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl), a package for constructing and analyzing bond graphs models, which can take Catalyst models as input. -- [PEtab.jl](https://github.com/sebapersson/PEtab.jl), a package that implements the PEtab format for - fitting reaction network ODEs to data. Input can be provided either as SBML files or as Catalyst - `ReactionSystem`s. ## Illustrative example @@ -149,7 +147,7 @@ model = @reaction_network begin end # Create an ODE that can be simulated. -u0 = [:S => 50, :E => 10, :SE => 0, :P => 0] +u0 = [:S => 50.0, :E => 10.0, :SE => 0.0, :P => 0.0] tspan = (0., 200.) ps = (:kB => 0.01, :kD => 0.1, :kP => 0.1) ode = ODEProblem(model, u0, tspan, ps) @@ -166,7 +164,8 @@ jump simulation ```julia # Create and simulate a jump process (here using Gillespie's direct algorithm). using JumpProcesses -dprob = DiscreteProblem(model, u0, tspan, ps) +u0_integers = [:S => 50, :E => 10, :SE => 0, :P => 0] +dprob = DiscreteProblem(model, u0_integers, tspan, ps) jprob = JumpProblem(model, dprob, Direct()) jump_sol = solve(jprob, SSAStepper()) plot(jump_sol; lw = 2) @@ -180,49 +179,50 @@ instead show how various Catalyst features can compose to create a much more adv model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ ($G \to Gᴾ$) is promoted by sunlight (modeled as the cyclic sinusoid $kₐ*(sin(t)+1)$), which -phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($V$) +phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($Vₘ$) it undergoes cell division. First, we declare our model: ```julia using Catalyst cell_model = @reaction_network begin - @parameters Vₘ g Ω - @default_noise_scaling Ω + @parameters Vₘ g @equations begin D(V) ~ g*Gᴾ end @continuous_events begin - [V ~ Vₘₐₓ] => [V ~ V/2] + [V ~ Vₘ] => [V ~ V/2] end kₚ*(sin(t)+1)/V, G --> Gᴾ kᵢ/V, Gᴾ --> G end ``` -Next, we can use [Latexify.jl](https://korsbo.github.io/Latexify.jl/stable/) to show the ordinary -differential equations associated with this model: -```julia -using Latexify -latexify(cell_model; form = :ode) -``` In this case, we would instead like to perform stochastic simulations, so we transform our model to an SDE: ```julia -u0 = [:V => 0.5, :G => 1.0, :Gᴾ => 0.0] +u0 = [:V => 25.0, :G => 50.0, :Gᴾ => 0.0] tspan = (0.0, 20.0) -ps = [:Vₘₐₓ => 1.0, :g => 0.2, :kₚ => 5.0, :kᵢ => 2.0, :Ω => 0.1] +ps = [:Vₘ => 50.0, :g => 0.2, :kₚ => 100.0, :kᵢ => 60.0] sprob = SDEProblem(cell_model, u0, tspan, ps) ``` -Finally, we simulate it and plot the result. +This produces the following equations: +```math +\begin{align*} +dG(t) &= - \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) + \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt - \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) + \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) & +dGᴾ(t) &= \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) - \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt + \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) - \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) & +dV(t) &= \left(g \cdot Gᴾ(t)\right) dt +\end{align*} +``` +where the $dW_1(t)$ and $dW_2(t)$ terms described the noise added through the Chemical Langevin Equations. Finally, we can simulate and plot the results. ```julia using StochasticDiffEq -sol = solve(sprob, STrapezoid()) +sol = solve(sprob, EM(); dt = 0.05) plot(sol; xguide = "Time (au)", lw = 2) ``` ![Elaborate SDE simulation](docs/src/assets/readme_elaborate_sde_plot.svg) Some features we used here: -- The SDE was [simulated using StochasticDiffEq.jl]. We also [scaled the SDE noise terms](@ref ref). -- The cell volume was [modeled as a differential equation, which was coupled to the reaction network model](@ref ref). -- The cell divisions were created by [incorporating events into the model](@ref ref). -- The model equations were [displayed using Latexify.jl](@ref ref), and the simulation [plotted using Plots.jl](@ref ref). +- The cell volume was [modeled as a differential equation, which was coupled to the reaction network model](https://docs.sciml.ai/Catalyst/dev/model_creation/constraint_equations/#constraint_equations_coupling_constraints). +- The cell divisions were created by [incorporating events into the model](https://docs.sciml.ai/Catalyst/dev/model_creation/constraint_equations/#constraint_equations_events). +- We designated a specific numeric [solver and corresponding solver options](https://docs.sciml.ai/Catalyst/dev/model_simulation/simulation_introduction/#simulation_intro_solver_options). +- The model simulation was [plotted using Plots.jl](https://docs.sciml.ai/Catalyst/dev/model_simulation/simulation_plotting/). ## Getting help or getting involved Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/), From 5e210de2420c204c81e8aaa15850129d275a438f Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 28 Jun 2024 19:44:57 -0400 Subject: [PATCH 361/446] link to stable docs --- README.md | 63 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 022788958b..125bc1ce5e 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/) [![API Stable](https://img.shields.io/badge/API-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/api/catalyst_api/) -[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://docs.sciml.ai/Catalyst/dev/) -[![API Dev](https://img.shields.io/badge/API-dev-blue.svg)](https://docs.sciml.ai/Catalyst/dev/api/catalyst_api/) +[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://docs.sciml.ai/Catalyst/stable/) +[![API Dev](https://img.shields.io/badge/API-dev-blue.svg)](https://docs.sciml.ai/Catalyst/stable/api/catalyst_api/) [![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) [![Build Status](https://github.com/SciML/Catalyst.jl/workflows/CI/badge.svg)](https://github.com/SciML/Catalyst.jl/actions?query=workflow%3ACI) @@ -23,7 +23,7 @@ specified using Catalyst's domain-specific language (DSL). Leveraging [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl), Catalyst enables large-scale simulations through auto-vectorization and parallelism. Symbolic `ReactionSystem`s can be used to generate ModelingToolkit-based models, allowing -the easy simulation and parameter estimation of mass action ODE models, chemical +the easy simulation and parameter estimation of mass action ODE models, Chemical Langevin SDE models, stochastic chemical kinetics jump process models, and more. Generated models can be used with solvers throughout the broader [SciML](https://sciml.ai) ecosystem, including higher-level SciML packages (e.g. @@ -42,7 +42,7 @@ This also includes a special migration guide for version 14. The latest tutorials and information on using Catalyst are available in the [stable documentation](https://docs.sciml.ai/Catalyst/stable/). The [in-development -documentation](https://docs.sciml.ai/Catalyst/dev/) describes unreleased features in +documentation](https://docs.sciml.ai/Catalyst/stable/) describes unreleased features in the current master branch. An overview of the package, its features, and comparative benchmarking (as of version 13) can also @@ -51,7 +51,7 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model ## Features #### Features of Catalyst -- [The Catalyst DSL](https://docs.sciml.ai/Catalyst/dev/model_creation/dsl_basics/) provides a simple and readable format for manually specifying reaction +- [The Catalyst DSL](https://docs.sciml.ai/Catalyst/stable/model_creation/dsl_basics/) provides a simple and readable format for manually specifying reaction network models using chemical reaction notation. - Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, built on [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and @@ -61,60 +61,60 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model multiple networks together. - Leveraging ModelingToolkit, generated models can be converted to symbolic reaction rate equation ODE models, symbolic Chemical Langevin Equation models, and symbolic stochastic chemical kinetics (jump process) models. These can be simulated using any [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) - [ODE/SDE/jump solver](https://docs.sciml.ai/Catalyst/dev/model_simulation/simulation_introduction/), and can be used within `EnsembleProblem`s for carrying - out [parallelized parameter sweeps and statistical sampling](https://docs.sciml.ai/Catalyst/dev/model_simulation/ensemble_simulations/). Plot recipes - are available for [visualization of all solutions](https://docs.sciml.ai/Catalyst/dev/model_simulation/simulation_plotting/). -- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](https://docs.sciml.ai/Catalyst/dev/model_creation/dsl_basics/#dsl_description_stoichiometries_decimal) for generating - ODE models, and symbolic expressions for stoichiometric coefficients [are supported](https://docs.sciml.ai/Catalyst/dev/model_creation/parametric_stoichiometry/) for + [ODE/SDE/jump solver](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_introduction/), and can be used within `EnsembleProblem`s for carrying + out [parallelized parameter sweeps and statistical sampling](https://docs.sciml.ai/Catalyst/stable/model_simulation/ensemble_simulations/). Plot recipes + are available for [visualization of all solutions](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_plotting/). +- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](https://docs.sciml.ai/Catalyst/stable/model_creation/dsl_basics/#dsl_description_stoichiometries_decimal) for generating + ODE models, and symbolic expressions for stoichiometric coefficients [are supported](https://docs.sciml.ai/Catalyst/stable/model_creation/parametric_stoichiometry/) for all system types. -- A [network analysis suite](https://docs.sciml.ai/Catalyst/dev/model_creation/network_analysis/) permits the computation of linkage classes, deficiencies, reversibility, and other network properties. -- [Conservation laws can be detected and utilized](https://docs.sciml.ai/Catalyst/dev/model_creation/network_analysis/#network_analysis_deficiency) to reduce system sizes, and to generate +- A [network analysis suite](https://docs.sciml.ai/Catalyst/stable/model_creation/network_analysis/) permits the computation of linkage classes, deficiencies, reversibility, and other network properties. +- [Conservation laws can be detected and utilized](https://docs.sciml.ai/Catalyst/stable/model_creation/network_analysis/#network_analysis_deficiency) to reduce system sizes, and to generate non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). -- Catalyst reaction network models can be [coupled with differential and algebraic equations](https://docs.sciml.ai/Catalyst/dev/model_creation/constraint_equations/) +- Catalyst reaction network models can be [coupled with differential and algebraic equations](https://docs.sciml.ai/Catalyst/stable/model_creation/constraint_equations/) (which are then incorporated during conversion to ODEs, SDEs, and steady state equations). -- Models can be [coupled with events](https://docs.sciml.ai/Catalyst/dev/model_creation/constraint_equations/#constraint_equations_events) that affect the system and its state during simulations. +- Models can be [coupled with events](https://docs.sciml.ai/Catalyst/stable/model_creation/constraint_equations/#constraint_equations_events) that affect the system and its state during simulations. - By leveraging ModelingToolkit, users have a variety of options for generating optimized system representations to use in solvers. These include construction - of [dense or sparse Jacobians](https://docs.sciml.ai/Catalyst/dev/model_simulation/ode_simulation_performance/#ode_simulation_performance_sparse_jacobian), [multithreading or parallelization of generated - derivative functions](https://docs.sciml.ai/Catalyst/dev/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation), [automatic classification of reactions into optimized + of [dense or sparse Jacobians](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_sparse_jacobian), [multithreading or parallelization of generated + derivative functions](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation), [automatic classification of reactions into optimized jump types for Gillespie type simulations](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#jump_types), [automatic construction of dependency graphs for jump systems](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-Requiring-Dependency-Graphs), and more. - [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl) symbolic expressions and Julia `Expr`s can be obtained for all rate laws and functions determining the deterministic and stochastic terms within resulting ODE, SDE or jump models. -- [Steady states](https://docs.sciml.ai/Catalyst/dev/steady_state_functionality/homotopy_continuation/) (and their [stabilities](https://docs.sciml.ai/Catalyst/dev/steady_state_functionality/steady_state_stability_computation/)) can be computed for model ODE representations. +- [Steady states](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/homotopy_continuation/) (and their [stabilities](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/steady_state_stability_computation/)) can be computed for model ODE representations. #### Features of Catalyst composing with other packages - [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to numerically solver generated reaction rate equation ODE models. - [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) can be used to numerically solve generated Chemical Langevin Equation SDE models. - [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) can be used to numerically sample generated Stochastic Chemical Kinetics Jump Process models. -- Support for [parallelization of all simulations](https://docs.sciml.ai/Catalyst/dev/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation), including parallelization of - [ODE simulations on GPUs](https://docs.sciml.ai/Catalyst/dev/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation_GPU) using +- Support for [parallelization of all simulations](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation), including parallelization of + [ODE simulations on GPUs](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation_GPU) using [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). - [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX - expressions](https://docs.sciml.ai/Catalyst/dev/model_creation/model_visualisation/#visualisation_latex) corresponding to generated mathematical models or the + expressions](https://docs.sciml.ai/Catalyst/stable/model_creation/model_visualisation/#visualisation_latex) corresponding to generated mathematical models or the underlying set of reactions. -- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](https://docs.sciml.ai/Catalyst/dev/model_creation/model_visualisation/#visualisation_graphs) +- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](https://docs.sciml.ai/Catalyst/stable/model_creation/model_visualisation/#visualisation_graphs) (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). - Model steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by numerically solving steady-state nonlinear equations using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). - [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute - bifurcation diagrams](https://docs.sciml.ai/Catalyst/dev/steady_state_functionality/bifurcation_diagrams/) of models' steady states (including finding periodic orbits). + bifurcation diagrams](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/bifurcation_diagrams/) of models' steady states (including finding periodic orbits). - [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute - model [basins of attraction](https://docs.sciml.ai/Catalyst/dev/steady_state_functionality/dynamical_systems/#dynamical_systems_basins_of_attraction) and [Lyapunov spectrums](https://docs.sciml.ai/Catalyst/dev/steady_state_functionality/dynamical_systems/#dynamical_systems_lyapunov_exponents). + model [basins of attraction](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_basins_of_attraction) and [Lyapunov spectrums](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_lyapunov_exponents). - [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used - to [perform structural identifiability analysis](https://docs.sciml.ai/Catalyst/dev/inverse_problems/structural_identifiability/). + to [perform structural identifiability analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/structural_identifiability/). - [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](https://sebapersson.github.io/PEtab.jl/stable/Define_in_julia/). - [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform - [global sensitivity analysis](https://docs.sciml.ai/Catalyst/dev/inverse_problems/global_sensitivity_analysis/) of model behaviors. + [global sensitivity analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/global_sensitivity_analysis/) of model behaviors. - [SciMLSensitivity.jl](https://github.com/SciML/SciMLSensitivity.jl) can be used to compute local sensitivities of functions containing forward model simulations. #### Features of packages built upon Catalyst -- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](https://docs.sciml.ai/Catalyst/dev/model_creation/model_file_loading_and_export/#Loading-SBML-files-using-SBMLImporter.jl-and-SBMLToolkit.jl) via +- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#Loading-SBML-files-using-SBMLImporter.jl-and-SBMLToolkit.jl) via [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), - and [from BioNetGen .net files](https://docs.sciml.ai/Catalyst/dev/model_creation/model_file_loading_and_export/#file_loading_rni_net) and various stoichiometric matrix network representations + and [from BioNetGen .net files](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#file_loading_rni_net) and various stoichiometric matrix network representations using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). - [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic ModelingToolkit `ODESystem`s that represent moment closure approximations to moments of the @@ -163,6 +163,7 @@ The same model can be used as input to other types of simulations. E.g. here we jump simulation ```julia # Create and simulate a jump process (here using Gillespie's direct algorithm). +# Note that integer (not decimal) initial conditions are used. using JumpProcesses u0_integers = [:S => 50, :E => 10, :SE => 0, :P => 0] dprob = DiscreteProblem(model, u0_integers, tspan, ps) @@ -219,10 +220,10 @@ plot(sol; xguide = "Time (au)", lw = 2) ![Elaborate SDE simulation](docs/src/assets/readme_elaborate_sde_plot.svg) Some features we used here: -- The cell volume was [modeled as a differential equation, which was coupled to the reaction network model](https://docs.sciml.ai/Catalyst/dev/model_creation/constraint_equations/#constraint_equations_coupling_constraints). -- The cell divisions were created by [incorporating events into the model](https://docs.sciml.ai/Catalyst/dev/model_creation/constraint_equations/#constraint_equations_events). -- We designated a specific numeric [solver and corresponding solver options](https://docs.sciml.ai/Catalyst/dev/model_simulation/simulation_introduction/#simulation_intro_solver_options). -- The model simulation was [plotted using Plots.jl](https://docs.sciml.ai/Catalyst/dev/model_simulation/simulation_plotting/). +- The cell volume was [modeled as a differential equation, which was coupled to the reaction network model](https://docs.sciml.ai/Catalyst/stable/model_creation/constraint_equations/#constraint_equations_coupling_constraints). +- The cell divisions were created by [incorporating events into the model](https://docs.sciml.ai/Catalyst/stable/model_creation/constraint_equations/#constraint_equations_events). +- We designated a specific numeric [solver and corresponding solver options](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_introduction/#simulation_intro_solver_options). +- The model simulation was [plotted using Plots.jl](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_plotting/). ## Getting help or getting involved Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/), From 2e57c2465f32d4db7602e455a9306daa1786dcf3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 28 Jun 2024 20:17:59 -0400 Subject: [PATCH 362/446] up --- docs/src/home.md | 127 +++++++++--------- .../steady_state_stability_computation.md | 2 +- 2 files changed, 61 insertions(+), 68 deletions(-) diff --git a/docs/src/home.md b/docs/src/home.md index 5200de003f..c283918dbd 100644 --- a/docs/src/home.md +++ b/docs/src/home.md @@ -2,14 +2,14 @@ Catalyst.jl is a symbolic modeling package for analysis and high-performance simulation of chemical reaction networks. Catalyst defines symbolic -[`ReactionSystem`](https://docs.sciml.ai/Catalyst/stable/catalyst_functionality/programmatic_CRN_construction/)s, +[`ReactionSystem`](@ref)s, which can be created programmatically or easily specified using Catalyst's domain-specific language (DSL). Leveraging [ModelingToolkit.jl](https://github.com/SciML/ModelingToolkit.jl) and [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl), Catalyst enables large-scale simulations through auto-vectorization and parallelism. Symbolic `ReactionSystem`s can be used to generate ModelingToolkit-based models, allowing -the easy simulation and parameter estimation of mass action ODE models, chemical +the easy simulation and parameter estimation of mass action ODE models, Chemical Langevin SDE models, stochastic chemical kinetics jump process models, and more. Generated models can be used with solvers throughout the broader [SciML](https://sciml.ai) ecosystem, including higher-level SciML packages (e.g. @@ -19,74 +19,70 @@ etc). ## [Features](@id doc_home_features) #### [Features of Catalyst](@id doc_home_features_catalyst) -- [The Catalyst DSL](@ref ref) provides a simple and readable format for manually specifying reaction +- [The Catalyst DSL](@ref dsl_description) provides a simple and readable format for manually specifying reaction network models using chemical reaction notation. - Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, built on [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and [Symbolics.jl](https://docs.sciml.ai/Symbolics/stable/). -- The [Catalyst.jl API](http://docs.sciml.ai/Catalyst/stable/api/catalyst_api) provides functionality +- The [Catalyst.jl API](@ref api) provides functionality for extending networks, building networks programmatically, and for composing multiple networks together. -- Generated models can be simulated using any +- Leveraging ModelingToolkit, generated models can be converted to symbolic reaction rate equation ODE models, symbolic Chemical Langevin Equation models, and symbolic stochastic chemical kinetics (jump process) models. These can be simulated using any [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) - [ODE/SDE/jump solver](@ref ref), and can be used within `EnsembleProblem`s for carrying - out [parallelized parameter sweeps and statistical sampling](@ref ref). Plot recipes - are available for [visualization of all solutions](@ref ref). -- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref ref) for generating - ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref ref) for + [ODE/SDE/jump solver](@ref simulation_intro), and can be used within `EnsembleProblem`s for carrying + out [parallelized parameter sweeps and statistical sampling](@ref ensemble_simulations). Plot recipes + are available for [visualization of all solutions](@ref simulation_plotting). +- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref dsl_description_stoichiometries_decimal) for generating + ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref parametric_stoichiometry) for all system types. -- A [network analysis suite](@ref ref) permits the computation of linkage classes, deficiencies, and - reversibilities. -- [Conservation laws can be detected and utilized](@ref ref) to reduce system sizes, and to generate +- A [network analysis suite](@ref network_analysis) permits the computation of linkage classes, deficiencies, reversibility, and other network properties. +- [Conservation laws can be detected and utilized](@ref network_analysis_deficiency) to reduce system sizes, and to generate non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). -- Catalyst reaction network models can be [coupled with differential and algebraic equations](@ref ref) +- Catalyst reaction network models can be [coupled with differential and algebraic equations](@ref constraint_equations_coupling_constraints) (which are then incorporated during conversion to ODEs, SDEs, and steady state equations). -- Models can be [coupled with events](@ref ref) that affect the system and its state during simulations. +- Models can be [coupled with events](@ref constraint_equations_events) that affect the system and its state during simulations. - By leveraging ModelingToolkit, users have a variety of options for generating optimized system representations to use in solvers. These include construction - of [dense or sparse Jacobians](@ref ref), [multithreading or parallelization of generated - derivative functions](@ref ref), [automatic classification of reactions into optimized - jump types for Gillespie type simulations](@ref ref), [automatic construction of - dependency graphs for jump systems](@ref ref), and more. + of [dense or sparse Jacobians](@ref ode_simulation_performance_sparse_jacobian), [multithreading or parallelization of generated + derivative functions](@ref ode_simulation_performance_parallelisation), [automatic classification of reactions into optimized + jump types for Gillespie type simulations](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#jump_types), [automatic construction of + dependency graphs for jump systems](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-Requiring-Dependency-Graphs), and more. - [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl) symbolic expressions and Julia `Expr`s can be obtained for all rate laws and functions determining the deterministic and stochastic terms within resulting ODE, SDE or jump models. -- [Steady states](@ref ref) (and their [stabilities](@ref ref)) can be computed for model ODE representations. +- [Steady states](@ref homotopy_continuation) (and their [stabilities](@ref steady_state_stability)) can be computed for model ODE representations. #### [Features of Catalyst composing with other packages](@id doc_home_features_composed) -- [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to [perform model ODE - simulations](@ref ref). -- [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) Can be used to [perform model - SDE simulations](@ref ref). -- [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) Can be used to [model jump - simulations](@ref ref). -- Support for [parallelization of all simulations](@ref ref), including parallelization of - [ODE simulations on GPUs](@ref ref) using +- [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to numerically solver generated reaction rate equation ODE models. +- [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) can be used to numerically solve generated Chemical Langevin Equation SDE models. +- [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) can be used to numerically sample generated Stochastic Chemical Kinetics Jump Process models. +- Support for [parallelization of all simulations](@ref ode_simulation_performance_parallelisation), including parallelization of + [ODE simulations on GPUs](@ref ode_simulation_performance_parallelisation_GPU) using [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). - [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX - expressions](@ref ref) corresponding to generated mathematical models or the + expressions](@ref visualisation_latex) corresponding to generated mathematical models or the underlying set of reactions. -- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](@ref ref) - (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/).) -- Model steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) - (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using - [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by nonlinear systems - solving using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). +- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](@ref visualisation_graphs) + (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). +- Model steady states can be [computed through homotopy continuation](@ref homotopy_continuation) using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) + (which can find *all* steady states of systems with multiple ones), by [forward ODE simulations](@ref steady_state_solving_simulation) using + [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by [numerically solving steady-state nonlinear equations](@ref steady_state_solving_nonlinear) using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). - [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute - bifurcation diagrams](@ref ref) of models' steady states (including finding periodic orbits). + bifurcation diagrams](@ref bifurcation_diagrams) of models' steady states (including finding periodic orbits). - [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute - model [basins of attraction](@ref ref) and [Lyapunov spectrums](@ref ref). + model [basins of attraction](@ref dynamical_systems_basins_of_attraction) and [Lyapunov spectrums](@ref dynamical_systems_lyapunov_exponents). - [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used - to [perform structural identifiability analysis](@ref ref). + to [perform structural identifiability analysis](@ref structural_identifiability). - [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), - and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](@ref ref). + and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](https://sebapersson.github.io/PEtab.jl/stable/Define_in_julia/). - [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform - [global sensitivity analysis](@ref ref) of model behaviors. + [global sensitivity analysis](@ref global_sensitivity_analysis) of model behaviors. +- [SciMLSensitivity.jl](https://github.com/SciML/SciMLSensitivity.jl) can be used to compute local sensitivities of functions containing forward model simulations. #### [Features of packages built upon Catalyst](@id doc_home_features_other_packages) -- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref ref) via +- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#Loading-SBML-files-using-SBMLImporter.jl-and-SBMLToolkit.jl) via [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), - and [from BioNetGen .net files](@ref ref) and various stoichiometric matrix network representations + and [from BioNetGen .net files](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#file_loading_rni_net) and various stoichiometric matrix network representations using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). - [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic ModelingToolkit `ODESystem`s that represent moment closure approximations to moments of the @@ -99,14 +95,11 @@ etc). resulting stochastic chemical kinetics with delays models. - [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl), a package for constructing and analyzing bond graphs models, which can take Catalyst models as input. -- [PEtab.jl](https://github.com/sebapersson/PEtab.jl), a package that implements the PEtab format for - fitting reaction network ODEs to data. Input can be provided either as SBML files or as Catalyst - `ReactionSystem`s. ## [How to read this documentation](@id doc_home_documentation) The Catalyst documentation is separated into sections describing Catalyst's various features. Where appropriate, some sections will also give advice on best practices for various modeling workflows, and provide links with further reading. Each section also contains a set of relevant example workflows. Finally, the [API](@ref api) section contains a list of all functions exported by Catalyst (as well as descriptions of them and their inputs and outputs). -New users are recommended to start with either the [Introduction to Catalyst and Julia for New Julia users](@ref catalyst_for_new_julia_users) or [Introduction to Catalyst](@ref introduction_to_catalyst) sections (depending on whether they are familiar with Julia programming or not). This should be enough to carry out many basic Catalyst workflows. Next, [The Catalyst DSL](@ref ref) section gives a more throughout introduction to model creation, while the [Introduction to model simulation](@ref ref) section more through describes how simulations are carried out. Once you have gotten started using Catalyst, you can read whichever sections are relevant to your work (they should all be clearly labelled). +New users are recommended to start with either the [Introduction to Catalyst and Julia for New Julia users](@ref catalyst_for_new_julia_users) or [Introduction to Catalyst](@ref introduction_to_catalyst) sections (depending on whether they are familiar with Julia programming or not). This should be enough to carry out many basic Catalyst workflows. This documentation contains code which is dynamically run whenever it is built. If you copy the code and run it in your Julia environment it should work. The exact Julia environment that is used in this documentation can be found [here](@ref doc_home_reproducibility). @@ -167,7 +160,7 @@ model = @reaction_network begin end # Create an ODE that can be simulated. -u0 = [:S => 50, :E => 10, :SE => 0, :P => 0] +u0 = [:S => 50.0, :E => 10.0, :SE => 0.0, :P => 0.0] tspan = (0., 200.) ps = (:kB => 0.01, :kD => 0.1, :kP => 0.1) ode = ODEProblem(model, u0, tspan, ps) @@ -182,8 +175,10 @@ The same model can be used as input to other types of simulations. E.g. here we jump simulation ```@example home_simple_example # Create and simulate a jump process (here using Gillespie's direct algorithm). +# Note that integer (not decimal) initial conditions are used. using JumpProcesses -dprob = DiscreteProblem(model, u0, tspan, ps) +u0_integers = [:S => 50, :E => 10, :SE => 0, :P => 0] +dprob = DiscreteProblem(model, u0_integers, tspan, ps) jprob = JumpProblem(model, dprob, Direct()) jump_sol = solve(jprob, SSAStepper()) jump_sol = solve(jprob, SSAStepper(); seed = 1234) # hide @@ -196,40 +191,42 @@ instead show how various Catalyst features can compose to create a much more adv model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ ($G \to Gᴾ$) is promoted by sunlight (modeled as the cyclic sinusoid $kₐ*(sin(t)+1)$), which -phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($V$) -it undergoes through cell division. First, we declare our model: +phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($Vₘ$) +it undergoes cell division. First, we declare our model: ```@example home_elaborate_example using Catalyst cell_model = @reaction_network begin - @parameters Vₘₐₓ g Ω - @default_noise_scaling Ω + @parameters Vₘ g @equations begin D(V) ~ g*Gᴾ end @continuous_events begin - [V ~ Vₘₐₓ] => [V ~ V/2] + [V ~ Vₘ] => [V ~ V/2] end kₚ*(sin(t)+1)/V, G --> Gᴾ kᵢ/V, Gᴾ --> G end ``` -Next, we can use [Latexify.jl](https://korsbo.github.io/Latexify.jl/stable/) to show the ordinary differential equations associated with this model: -```@example home_elaborate_example -using Latexify -latexify(cell_model; form = :ode) -``` In this case, we would instead like to perform stochastic simulations, so we transform our model to an SDE: ```@example home_elaborate_example -u0 = [:V => 0.5, :G => 1.0, :Gᴾ => 0.0] +u0 = [:V => 25.0, :G => 50.0, :Gᴾ => 0.0] tspan = (0.0, 20.0) -ps = [:Vₘₐₓ => 1.0, :g => 0.2, :kₚ => 5.0, :kᵢ => 2.0, :Ω => 0.1] +ps = [:Vₘ => 50.0, :g => 0.2, :kₚ => 100.0, :kᵢ => 60.0] sprob = SDEProblem(cell_model, u0, tspan, ps) ``` -Finally, we simulate it and plot the result. +This produces the following equations: +```math +\begin{align*} +dG(t) &= - \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) + \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt - \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) + \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) & +dGᴾ(t) &= \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) - \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt + \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) - \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) & +dV(t) &= \left(g \cdot Gᴾ(t)\right) dt +\end{align*} +``` +where the $dW_1(t)$ and $dW_2(t)$ terms described the noise added through the Chemical Langevin Equations. Finally, we can simulate and plot the results. ```@example home_elaborate_example using StochasticDiffEq -sol = solve(sprob, STrapezoid()) -sol = solve(sprob, STrapezoid(); seed = 1234) # hide +sol = solve(sprob, EM(); dt = 0.05) +sol = solve(sprob, EM(); dt = 0.05, seed = 1234) # hide plot(sol; xguide = "Time (au)", lw = 2) ``` @@ -260,10 +257,6 @@ could cite our work: } ``` -We also maintain a user survey, asking basic questions about how users utilise the package. The survey -is available [here](ref), and only takes about 5 minutes to fill out. We are grateful to those who -fill out the survey, as this helps us further develop the package. - ## [Reproducibility](@id doc_home_reproducibility) ```@raw html
The documentation of this SciML package was built using these direct dependencies, diff --git a/docs/src/steady_state_functionality/steady_state_stability_computation.md b/docs/src/steady_state_functionality/steady_state_stability_computation.md index 0cc60ebf94..7f144dd953 100644 --- a/docs/src/steady_state_functionality/steady_state_stability_computation.md +++ b/docs/src/steady_state_functionality/steady_state_stability_computation.md @@ -1,4 +1,4 @@ -# Steady state stability computation +# [Steady state stability computation](@id steady_state_stability) After system steady states have been found using [HomotopyContinuation.jl](@ref homotopy_continuation), [NonlinearSolve.jl](@ref nonlinear_solve), or other means, their stability can be computed using Catalyst's `steady_state_stability` function. Systems with conservation laws will automatically have these removed, permitting stability computation on systems with singular Jacobian. !!! warn From dd1268630edfa114f9964fea65de607a75ac83b0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 28 Jun 2024 21:13:59 -0400 Subject: [PATCH 363/446] update file import/export cross references --- docs/src/index.md | 4 ++-- .../model_file_loading_and_export.md | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index c283918dbd..dbea44a064 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -80,9 +80,9 @@ etc). - [SciMLSensitivity.jl](https://github.com/SciML/SciMLSensitivity.jl) can be used to compute local sensitivities of functions containing forward model simulations. #### [Features of packages built upon Catalyst](@id doc_home_features_other_packages) -- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#Loading-SBML-files-using-SBMLImporter.jl-and-SBMLToolkit.jl) via +- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@id model_file_import_export_sbml) via [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), - and [from BioNetGen .net files](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#file_loading_rni_net) and various stoichiometric matrix network representations + and [from BioNetGen .net files](@ref model_file_import_export_sbml_rni_net) and various stoichiometric matrix network representations using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). - [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic ModelingToolkit `ODESystem`s that represent moment closure approximations to moments of the diff --git a/docs/src/model_creation/model_file_loading_and_export.md b/docs/src/model_creation/model_file_loading_and_export.md index 813ae5f401..ae9380b3a3 100644 --- a/docs/src/model_creation/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -1,7 +1,7 @@ -# Loading Chemical Reaction Network Models from Files +# [Loading Chemical Reaction Network Models from Files](@id model_file_import_export) Catalyst stores chemical reaction network (CRN) models in `ReactionSystem` structures. This tutorial describes how to load such `ReactionSystem`s from, and save them to, files. This can be used to save models between Julia sessions, or transfer them from one session to another. Furthermore, to facilitate the computation modelling of CRNs, several standardised file formats have been created to represent CRN models (e.g. [SBML](https://sbml.org/)). This enables CRN models to be shared between different softwares and programming languages. While Catalyst itself does not have the functionality for loading such files, we will here (briefly) introduce a few packages that can load different file types to Catalyst `ReactionSystem`s. -## Saving Catalyst models to, and loading them from, Julia files +## [Saving Catalyst models to, and loading them from, Julia files](@id model_file_import_export_crn_serialization) Catalyst provides a `save_reactionsystem` function, enabling the user to save a `ReactionSystem` to a file. Here we demonstrate this by first creating a [simple cross-coupling model](@ref basic_CRN_library_cc): ```@example file_handling_1 using Catalyst @@ -56,7 +56,7 @@ end In addition to transferring models between Julia sessions, the `save_reactionsystem` function can also be used or print a model to a text file where you can easily inspect its components. -## Loading and Saving arbitrary Julia variables using Serialization.jl +## [Loading and Saving arbitrary Julia variables using Serialization.jl](@id model_file_import_export_julia_serialisation) Julia provides a general and lightweight interface for loading and saving Julia structures to and from files that it can be good to be aware of. It is called [Serialization.jl](https://docs.julialang.org/en/v1/stdlib/Serialization/) and provides two functions, `serialize` and `deserialize`. The first allows us to write a Julia structure to a file. E.g. if we wish to save a parameter set associated with our model, we can use ```@example file_handling_2 using Serialization @@ -70,7 +70,7 @@ rm("saved_parameters.jls") # hide loaded_sol # hide ``` -## [Loading .net files using ReactionNetworkImporters.jl](@id file_loading_rni_net) +## [Loading .net files using ReactionNetworkImporters.jl](@id model_file_import_export_sbml_rni_net) A general-purpose format for storing CRN models is so-called .net files. These can be generated by e.g. [BioNetGen](https://bionetgen.org/). The [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl) package enables the loading of such files to Catalyst `ReactionSystem`. Here we load a [Repressilator](@ref basic_CRN_library_repressilator) model stored in the "repressilator.net" file: ```julia using ReactionNetworkImporters @@ -96,7 +96,7 @@ Note that, as all initial conditions and parameters have default values, we can A more detailed description of ReactionNetworkImporter's features can be found in its [documentation](https://docs.sciml.ai/ReactionNetworkImporters/stable/). -## Loading SBML files using SBMLImporter.jl and SBMLToolkit.jl +## [Loading SBML files using SBMLImporter.jl and SBMLToolkit.jl](@id model_file_import_export_sbml) The Systems Biology Markup Language (SBML) is the most widespread format for representing CRN models. Currently, there exist two different Julia packages, [SBMLImporter.jl](https://github.com/sebapersson/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), that are able to load SBML files to Catalyst `ReactionSystem` structures. SBML is able to represent a *very* wide range of model features, with both packages supporting most features. However, there exist SBML files (typically containing obscure model features such as events with time delays) that currently cannot be loaded into Catalyst models. SBMLImporter's `load_SBML` function can be used to load SBML files. Here, we load a [Brusselator](@ref basic_CRN_library_brusselator) model stored in the "brusselator.xml" file: @@ -104,7 +104,7 @@ SBMLImporter's `load_SBML` function can be used to load SBML files. Here, we loa using SBMLImporter prn, cbs = load_SBML("brusselator.xml", massaction = true) ``` -Here, while [ReactionNetworkImporters generates a `ParsedReactionSystem` only](@ref file_loading_rni_net), SBMLImporter generates a `ParsedReactionSystem` (here stored in `prn`) and a [so-called `CallbackSet`](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/#CallbackSet) (here stored in `cbs`). While `prn` can be used to create various problems, when we simulate them, we must also supply `cbs`. E.g. to simulate our brusselator we use: +Here, while [ReactionNetworkImporters generates a `ParsedReactionSystem` only](@ref model_file_import_export_sbml_rni_net), SBMLImporter generates a `ParsedReactionSystem` (here stored in `prn`) and a [so-called `CallbackSet`](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/#CallbackSet) (here stored in `cbs`). While `prn` can be used to create various problems, when we simulate them, we must also supply `cbs`. E.g. to simulate our brusselator we use: ```julia using Catalyst, OrdinaryDiffEq, Plots tspan = (0.0, 50.0) @@ -121,10 +121,10 @@ A more detailed description of SBMLImporter's features can be found in its [docu !!! note The `massaction = true` option informs the importer that the target model follows mass-action principles. When given, this enables SBMLImporter to make appropriate modifications to the model (which are important for e.g. jump simulation performance). -### SBMLImporter and SBMLToolkit +### [SBMLImporter and SBMLToolkit](@id model_file_import_export_package_alts) Above, we described how to use SBMLImporter to import SBML files. Alternatively, SBMLToolkit can be used instead. It has a slightly different syntax, which is described in its [documentation](https://github.com/SciML/SBMLToolkit.jl), and does not support as wide a range of SBML features as SBMLImporter. A short comparison of the two packages can be found [here](https://github.com/sebapersson/SBMLImporter.jl?tab=readme-ov-file#differences-compared-to-sbmltoolkit). Generally, while they both perform well, we note that for *jump simulations* SBMLImporter is preferable (its way for internally representing reaction event enables more performant jump simulations). -## Loading models from matrix representation using ReactionNetworkImporters.jl +## [Loading models from matrix representation using ReactionNetworkImporters.jl](@id model_file_import_export_matrix_representations) While CRN models can be represented through various file formats, they can also be represented in various matrix forms. E.g. a CRN with $m$ species and $n$ reactions (and with constant rates) can be represented with either - An $mxn$ substrate matrix (with each species's substrate stoichiometry in each reaction) and an $nxm$ product matrix (with each species's product stoichiometry in each reaction). From c0df3808256535cafcbbe1f4a4dcbcd269bdbb93 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 28 Jun 2024 21:17:52 -0400 Subject: [PATCH 364/446] update citation cross references --- docs/src/index.md | 28 +++++++++---------- .../global_sensitivity_analysis.md | 2 +- .../model_file_loading_and_export.md | 2 +- .../dynamical_systems.md | 2 +- .../nonlinear_solve.md | 2 +- docs/unpublished/petab_ode_param_fitting.md | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index dbea44a064..544a3bbad5 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,4 +1,4 @@ -# [Catalyst.jl for Reaction Network Modeling](@id doc_home) +# [Catalyst.jl for Reaction Network Modeling](@id doc_index) Catalyst.jl is a symbolic modeling package for analysis and high-performance simulation of chemical reaction networks. Catalyst defines symbolic @@ -16,9 +16,9 @@ Generated models can be used with solvers throughout the broader for sensitivity analysis, parameter estimation, machine learning applications, etc). -## [Features](@id doc_home_features) +## [Features](@id doc_index_features) -#### [Features of Catalyst](@id doc_home_features_catalyst) +#### [Features of Catalyst](@id doc_index_features_catalyst) - [The Catalyst DSL](@ref dsl_description) provides a simple and readable format for manually specifying reaction network models using chemical reaction notation. - Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, @@ -52,7 +52,7 @@ etc). deterministic and stochastic terms within resulting ODE, SDE or jump models. - [Steady states](@ref homotopy_continuation) (and their [stabilities](@ref steady_state_stability)) can be computed for model ODE representations. -#### [Features of Catalyst composing with other packages](@id doc_home_features_composed) +#### [Features of Catalyst composing with other packages](@id doc_index_features_composed) - [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to numerically solver generated reaction rate equation ODE models. - [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) can be used to numerically solve generated Chemical Langevin Equation SDE models. - [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) can be used to numerically sample generated Stochastic Chemical Kinetics Jump Process models. @@ -79,7 +79,7 @@ etc). [global sensitivity analysis](@ref global_sensitivity_analysis) of model behaviors. - [SciMLSensitivity.jl](https://github.com/SciML/SciMLSensitivity.jl) can be used to compute local sensitivities of functions containing forward model simulations. -#### [Features of packages built upon Catalyst](@id doc_home_features_other_packages) +#### [Features of packages built upon Catalyst](@id doc_index_features_other_packages) - Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@id model_file_import_export_sbml) via [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), and [from BioNetGen .net files](@ref model_file_import_export_sbml_rni_net) and various stoichiometric matrix network representations @@ -96,7 +96,7 @@ etc). - [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl), a package for constructing and analyzing bond graphs models, which can take Catalyst models as input. -## [How to read this documentation](@id doc_home_documentation) +## [How to read this documentation](@id doc_index_documentation) The Catalyst documentation is separated into sections describing Catalyst's various features. Where appropriate, some sections will also give advice on best practices for various modeling workflows, and provide links with further reading. Each section also contains a set of relevant example workflows. Finally, the [API](@ref api) section contains a list of all functions exported by Catalyst (as well as descriptions of them and their inputs and outputs). New users are recommended to start with either the [Introduction to Catalyst and Julia for New Julia users](@ref catalyst_for_new_julia_users) or [Introduction to Catalyst](@ref introduction_to_catalyst) sections (depending on whether they are familiar with Julia programming or not). This should be enough to carry out many basic Catalyst workflows. @@ -126,7 +126,7 @@ end nothing # hide ``` -## [Installation](@id doc_home_installation) +## [Installation](@id doc_index_installation) Catalyst is an officially registered Julia package, which can be installed through the Julia package manager: ```julia using Pkg @@ -142,9 +142,9 @@ is also needed. A more throughout guide for setting up Catalyst and installing Julia packages can be found [here](@ref catalyst_for_new_julia_users_packages). -## [Illustrative example](@id doc_home_example) +## [Illustrative example](@id doc_index_example) -#### [Deterministic ODE simulation of Michaelis-Menten enzyme kinetics](@id doc_home_example_ode) +#### [Deterministic ODE simulation of Michaelis-Menten enzyme kinetics](@id doc_index_example_ode) Here we show a simple example where a model is created using the Catalyst DSL, and then simulated as an ordinary differential equation. @@ -170,7 +170,7 @@ sol = solve(ode) plot(sol; lw = 5) ``` -#### [Stochastic jump simulations](@id doc_home_example_jump) +#### [Stochastic jump simulations](@id doc_index_example_jump) The same model can be used as input to other types of simulations. E.g. here we instead perform a jump simulation ```@example home_simple_example @@ -185,7 +185,7 @@ jump_sol = solve(jprob, SSAStepper(); seed = 1234) # hide plot(jump_sol; lw = 2) ``` -## [Elaborate example](@id doc_home_elaborate_example) +## [Elaborate example](@id doc_index_elaborate_example) In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth @@ -230,13 +230,13 @@ sol = solve(sprob, EM(); dt = 0.05, seed = 1234) # hide plot(sol; xguide = "Time (au)", lw = 2) ``` -## [Getting Help](@id doc_home_help) +## [Getting Help](@id doc_index_help) Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/), the [Julia Slack](https://julialang.slack.com) channels \#sciml-bridged and \#sciml-sysbio, and the [Julia Zulip sciml-bridged channel](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged). For bugs or feature requests, [open an issue](https://github.com/SciML/Catalyst.jl/issues). -## [Supporting and Citing Catalyst.jl](@id doc_home_citation) +## [Supporting and Citing Catalyst.jl](@id doc_index_citation) The software in this ecosystem was developed as part of academic research. If you would like to help support it, please star the repository as such metrics may help us secure funding in the future. If you use Catalyst as part of your research, teaching, or other activities, we would be grateful if you @@ -257,7 +257,7 @@ could cite our work: } ``` -## [Reproducibility](@id doc_home_reproducibility) +## [Reproducibility](@id doc_index_reproducibility) ```@raw html
The documentation of this SciML package was built using these direct dependencies, ``` diff --git a/docs/src/inverse_problems/global_sensitivity_analysis.md b/docs/src/inverse_problems/global_sensitivity_analysis.md index cd65631c2f..e2a10759f9 100644 --- a/docs/src/inverse_problems/global_sensitivity_analysis.md +++ b/docs/src/inverse_problems/global_sensitivity_analysis.md @@ -142,7 +142,7 @@ Here, the function's sensitivity is evaluated with respect to each output indepe --- ## [Citations](@id global_sensitivity_analysis_citations) -If you use this functionality in your research, [in addition to Catalyst](@ref catalyst_citation), please cite the following paper to support the authors of the GlobalSensitivity.jl package: +If you use this functionality in your research, [in addition to Catalyst](@ref doc_index_citation), please cite the following paper to support the authors of the GlobalSensitivity.jl package: ``` @article{dixit2022globalsensitivity, title={GlobalSensitivity. jl: Performant and Parallel Global Sensitivity Analysis with Julia}, diff --git a/docs/src/model_creation/model_file_loading_and_export.md b/docs/src/model_creation/model_file_loading_and_export.md index ae9380b3a3..43b6d16f22 100644 --- a/docs/src/model_creation/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -136,7 +136,7 @@ The advantage of these forms is that they offer a compact and very general way t --- ## [Citations](@id petab_citations) -If you use any of this functionality in your research, [in addition to Catalyst](@ref catalyst_citation), please cite the paper(s) corresponding to whichever package(s) you used: +If you use any of this functionality in your research, [in addition to Catalyst](@ref doc_index_citation), please cite the paper(s) corresponding to whichever package(s) you used: ``` @software{2022ReactionNetworkImporters, author = {Isaacson, Samuel}, diff --git a/docs/src/steady_state_functionality/dynamical_systems.md b/docs/src/steady_state_functionality/dynamical_systems.md index 5b844e065d..ab48f99bac 100644 --- a/docs/src/steady_state_functionality/dynamical_systems.md +++ b/docs/src/steady_state_functionality/dynamical_systems.md @@ -124,7 +124,7 @@ More details on how to compute Lyapunov exponents using DynamicalSystems.jl can --- ## [Citations](@id dynamical_systems_citations) -If you use this functionality in your research, [in addition to Catalyst](@ref catalyst_citation), please cite the following paper to support the author of the DynamicalSystems.jl package: +If you use this functionality in your research, [in addition to Catalyst](@ref doc_index_citation), please cite the following paper to support the author of the DynamicalSystems.jl package: ``` @article{DynamicalSystems.jl-2018, doi = {10.21105/joss.00598}, diff --git a/docs/src/steady_state_functionality/nonlinear_solve.md b/docs/src/steady_state_functionality/nonlinear_solve.md index 0630da11fa..d5e438d89c 100644 --- a/docs/src/steady_state_functionality/nonlinear_solve.md +++ b/docs/src/steady_state_functionality/nonlinear_solve.md @@ -133,7 +133,7 @@ However, especially when the forward ODE simulation approach is used, it is reco --- ## [Citations](@id nonlinear_solve_citation) -If you use this functionality in your research, [in addition to Catalyst](@ref catalyst_citation), please cite the following paper to support the authors of the NonlinearSolve.jl package: +If you use this functionality in your research, [in addition to Catalyst](@ref doc_index_citation), please cite the following paper to support the authors of the NonlinearSolve.jl package: ``` @article{pal2024nonlinearsolve, title={NonlinearSolve. jl: High-Performance and Robust Solvers for Systems of Nonlinear Equations in Julia}, diff --git a/docs/unpublished/petab_ode_param_fitting.md b/docs/unpublished/petab_ode_param_fitting.md index 9e503d7411..64b61df5bf 100644 --- a/docs/unpublished/petab_ode_param_fitting.md +++ b/docs/unpublished/petab_ode_param_fitting.md @@ -523,7 +523,7 @@ There exist several types of plots for both types of calibration results. More d --- ## [Citations](@id petab_citations) -If you use this functionality in your research, [in addition to Catalyst](@ref catalyst_citation), please cite the following papers to support the authors of the PEtab.jl package (currently there is no article associated with this package) and the PEtab standard: +If you use this functionality in your research, [in addition to Catalyst](@ref doc_index_citation), please cite the following papers to support the authors of the PEtab.jl package (currently there is no article associated with this package) and the PEtab standard: ``` @misc{2023Petabljl, author = {Ognissanti, Damiano AND Arutjunjan, Rafael AND Persson, Sebastian AND Hasselgren, Viktor}, From efd370be7ee99280aa6957c86d94d99f81c98c36 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 28 Jun 2024 21:22:36 -0400 Subject: [PATCH 365/446] up --- .../steady_state_stability_computation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/steady_state_functionality/steady_state_stability_computation.md b/docs/src/steady_state_functionality/steady_state_stability_computation.md index 66a78058a0..08e6fa69b2 100644 --- a/docs/src/steady_state_functionality/steady_state_stability_computation.md +++ b/docs/src/steady_state_functionality/steady_state_stability_computation.md @@ -4,7 +4,7 @@ After system steady states have been found using [HomotopyContinuation.jl](@ref !!! warn Catalyst currently computes steady state stabilities using the naive approach of checking whether a system's largest eigenvalue real part is negative. While more advanced stability computation methods exist (and would be a welcome addition to Catalyst), there is no direct plans to implement these. Furthermore, Catalyst uses a tolerance `tol = 10*sqrt(eps())` to determine whether a computed eigenvalue is far away enough from 0 to be reliably used. This threshold can be changed through the `tol` keyword argument. -## Basic examples +## [Basic examples](@id steady_state_stability_basics) Let us consider the following basic example: ```@example stability_1 using Catalyst @@ -42,7 +42,7 @@ Finally, as described above, Catalyst uses an optional argument, `tol`, to deter nothing# hide ``` -## Pre-computing the Jacobian to increase performance when computing stability for many steady states +## [Pre-computing the Jacobian to increase performance when computing stability for many steady states](@id steady_state_stability_jacobian) Catalyst uses the system Jacobian to compute steady state stability, and the Jacobian is computed once for each call to `steady_state_stability`. If you repeatedly compute stability for steady states of the same system, pre-computing the Jacobian and supplying it to the `steady_state_stability` function can improve performance. In this example we use the self-activation loop from previously, pre-computes its Jacobian, and uses it to multiple `steady_state_stability` calls: From ea51a88e7b613294291faeafbf8d48cc2f476ef8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 1 Jul 2024 11:40:40 -0400 Subject: [PATCH 366/446] doc code fix --- docs/src/v14_migration_guide.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 8e1e9737c5..176ec89dd1 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -139,7 +139,7 @@ Previously, it was possible to directly index problems to query them for their p ```@example v14_migration_4 using Catalyst rn = @reaction_network begin - (p,d), 0 <--> X + (p,d), 0 <--> X end u0 = [:X => 1.0] ps = [:p => 1.0, :d => 0.2] @@ -166,18 +166,18 @@ While it is possible to update e.g. `ODEProblem`s using the [`remake`](@ref simu ```@example v14_migration_5 using Catalyst, OrdinaryDiffEq rn = @reaction_network begin - (k1,k2), X1 <--> X2 + (k1,k2), X1 <--> X2 end u0 = [:X1 => 1.0, :X2 => 2.0] ps = [:k1 => 0.5, :k2 => 3.0] oprob = ODEProblem(rn, u0, (0.0, 10.0), ps; remove_conserved = true) -sol(oprob) +solve(oprob) # hide ``` is perfectly fine, attempting to then modify any initial conditions or the value of the conservation constant in `oprob` will silently fail: ```@example v14_migration_5 oprob_remade = remake(oprob; u0 = [:X1 => 5.0]) # NEVER do this. -sol(oprob) +solve(oprob_remade) # hide ``` This might generate a silent error, where the remade problem is different from the intended one (the value of the conserved constant will not be updated correctly). From 5dc4654b9601dc20ab434a61d5923acdf04e0424 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 1 Jul 2024 19:45:02 -0400 Subject: [PATCH 367/446] update tests --- test/upstream/mtk_problem_inputs.jl | 8 ++++---- test/upstream/mtk_structure_indexing.jl | 27 ++++++++++++++----------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index c1f86ca900..8712c6b504 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -218,10 +218,10 @@ let ] # Loops through all potential parameter sets, checking their inputs yield errors. - for ps in [ps_valid; ps_invalid], u0 in [u0_valid; u0s_invalid] + for ps in [[ps_valid]; ps_invalid], u0 in [[u0_valid]; u0s_invalid] # Handles problems with/without tspan separately. Special check ensuring that valid inputs passes. for XProblem in [ODEProblem, SDEProblem, DiscreteProblem] - if (ps == ps_valid) && (u0 == u0_valid) + if isequal(ps, ps_valid) && isequal(u0, u0_valid) XProblem(rn, u0, (0.0, 1.0), ps); @test true; else # Several of these cases do not throw errors (https://github.com/SciML/ModelingToolkit.jl/issues/2624). @@ -231,7 +231,7 @@ let end end for XProblem in [NonlinearProblem, SteadyStateProblem] - if (ps == ps_valid) && (u0 == u0_valid) + if isequal(ps, ps_valid) && isequal(u0, u0_valid) XProblem(rn, u0, ps); @test true; else @test_broken false @@ -240,4 +240,4 @@ let end end end -end \ No newline at end of file +end diff --git a/test/upstream/mtk_structure_indexing.jl b/test/upstream/mtk_structure_indexing.jl index 78b70eb279..e45c8ba413 100644 --- a/test/upstream/mtk_structure_indexing.jl +++ b/test/upstream/mtk_structure_indexing.jl @@ -46,11 +46,11 @@ begin eproblems = [eoprob, esprob, edprob, ejprob, enprob, essprob] # Creates integrators. - oint = init(oprob, Tsit5(); save_everystep=false) - sint = init(sprob, ImplicitEM(); save_everystep=false) + oint = init(oprob, Tsit5(); save_everystep = false) + sint = init(sprob, ImplicitEM(); save_everystep = false) jint = init(jprob, SSAStepper()) - nint = init(nprob, NewtonRaphson(); save_everystep=false) - @test_broken ssint = init(ssprob, DynamicSS(Tsit5()); save_everystep=false) # https://github.com/SciML/SciMLBase.jl/issues/660 + nint = init(nprob, NewtonRaphson(); save_everystep = false) + @test_broken ssint = init(ssprob, DynamicSS(Tsit5()); save_everystep = false) # https://github.com/SciML/SciMLBase.jl/issues/660 integrators = [oint, sint, jint, nint] # Creates solutions. @@ -64,14 +64,17 @@ end # Tests problem indexing and updating. let + @test_broken false # A few cases fails for SteadyStateProblem: https://github.com/SciML/SciMLBase.jl/issues/660 @test_broken false # Most cases broken for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 - for prob in deepcopy(problems[1:end-1]) + @test_broken false # Cannot run deepcopy of SteadyStateProblem and Nonlinear Problems. + @test_broken false # A few cases fails for JumpProblems. + for prob in deepcopy([oprob, sprob, dprob]) # Get u values (including observables). @test prob[X] == prob[model.X] == prob[:X] == 4 @test prob[XY] == prob[model.XY] == prob[:XY] == 9 @test prob[[XY,Y]] == prob[[model.XY,model.Y]] == prob[[:XY,:Y]] == [9, 5] - @test_broken prob[(XY,Y)] == prob[(model.XY,model.Y)] == prob[(:XY,:Y)] == (9, 5) + @test prob[(XY,Y)] == prob[(model.XY,model.Y)] == prob[(:XY,:Y)] == (9, 5) @test getu(prob, X)(prob) == getu(prob, model.X)(prob) == getu(prob, :X)(prob) == 4 @test getu(prob, XY)(prob) == getu(prob, model.XY)(prob) == getu(prob, :XY)(prob) == 9 @test getu(prob, [XY,Y])(prob) == getu(prob, [model.XY,model.Y])(prob) == getu(prob, [:XY,:Y])(prob) == [9, 5] @@ -117,8 +120,10 @@ end # Test remake function. let + @test_broken false # A few cases fails for JumpProblems. + @test_broken false # Cannot run deepcopy of SteadyStateProblem and Nonlinear Problems. @test_broken false # Currently cannot be run for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 (as indexing cannot be used to check values). - for prob in deepcopy(problems) + for prob in deepcopy([oprob, sprob, dprob]) # Remake for all u0s. rp = remake(prob; u0 = [X => 1, Y => 2]) @test rp[[X, Y]] == [1, 2] @@ -155,10 +160,8 @@ end # Test integrator indexing. let - @test_broken false # NOTE: Multiple problems for `nint` (https://github.com/SciML/SciMLBase.jl/issues/662). - @test_broken false # NOTE: Multiple problems for `jint` (https://github.com/SciML/SciMLBase.jl/issues/654). @test_broken false # NOTE: Cannot even create a `ssint` (https://github.com/SciML/SciMLBase.jl/issues/660). - for int in deepcopy([oint, sint]) + for int in deepcopy([oint, sint, jint, nint]) # Get u values. @test int[X] == int[model.X] == int[:X] == 4 @test int[XY] == int[model.XY] == int[:XY] == 9 @@ -262,7 +265,7 @@ let @test sol[X] == sol[model.X] == sol[:X] @test sol[XY] == sol[model.XY][1] == sol[:XY] @test sol[[XY,Y]] == sol[[model.XY,model.Y]] == sol[[:XY,:Y]] - @test_broken sol[(XY,Y)] == sol[(model.XY,model.Y)] == sol[(:XY,:Y)] + @test sol[(XY,Y)] == sol[(model.XY,model.Y)] == sol[(:XY,:Y)] @test getu(sol, X)(sol) == getu(sol, model.X)(sol)[1] == getu(sol, :X)(sol) @test getu(sol, XY)(sol) == getu(sol, model.XY)(sol)[1] == getu(sol, :XY)(sol) @test getu(sol, [XY,Y])(sol) == getu(sol, [model.XY,model.Y])(sol) == getu(sol, [:XY,:Y])(sol) @@ -282,7 +285,7 @@ end # Tests plotting. let @test_broken false # Currently broken for `ssol` (https://github.com/SciML/SciMLBase.jl/issues/580) - for sol in deepcopy([osol, jsol]) + for sol in deepcopy([osol, ssol, jsol]) # Single variable. @test length(plot(sol; idxs = X).series_list) == 1 @test length(plot(sol; idxs = XY).series_list) == 1 From 74277db0ea5e5ac4a3560670b335898bdc6c1e55 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 1 Jul 2024 20:40:38 -0400 Subject: [PATCH 368/446] test updates --- test/upstream/mtk_problem_inputs.jl | 713 +++++++++++++--------------- 1 file changed, 340 insertions(+), 373 deletions(-) diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index 788645b009..61a09f0b9d 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -1,9 +1,8 @@ -#! format: off - ### Prepares Tests ### # Fetch packages -using Catalyst, JumpProcesses, NonlinearSolve, OrdinaryDiffEq, SteadyStateDiffEq, StochasticDiffEq, Test +using Catalyst, JumpProcesses, NonlinearSolve, OrdinaryDiffEq, Plots, SteadyStateDiffEq, StochasticDiffEq, Test +import ModelingToolkit: getp, getu, setp, setu # Sets rnd number. using StableRNGs @@ -13,411 +12,379 @@ seed = rand(rng, 1:100) ### Basic Tests ### -# Prepares a models and initial conditions/parameters (of different forms) to be used as problem inputs. -begin +# Prepares a model and its problems, integrators, and solutions. +begin + # Creates the model and unpacks its context. model = @reaction_network begin - @species Z(t) = Z0 - @parameters k2=0.5 Z0 + @observables XY ~ X + Y (kp,kd), 0 <--> X (k1,k2), X <--> Y - (k1,k2), Y <--> Z end - @unpack X, Y, Z, kp, kd, k1, k2, Z0 = model - - u0_alts = [ - # Vectors not providing default values. - [X => 4, Y => 5], - [model.X => 4, model.Y => 5], - [:X => 4, :Y => 5], - # Vectors providing default values. - [X => 4, Y => 5, Z => 10], - [model.X => 4, model.Y => 5, model.Z => 10], - [:X => 4, :Y => 5, :Z => 10], - # Dicts not providing default values. - Dict([X => 4, Y => 5]), - Dict([model.X => 4, model.Y => 5]), - Dict([:X => 4, :Y => 5]), - # Dicts providing default values. - Dict([X => 4, Y => 5, Z => 10]), - Dict([model.X => 4, model.Y => 5, model.Z => 10]), - Dict([:X => 4, :Y => 5, :Z => 10]), - # Tuples not providing default values. - (X => 4, Y => 5), - (model.X => 4, model.Y => 5), - (:X => 4, :Y => 5), - # Tuples providing default values. - (X => 4, Y => 5, Z => 10), - (model.X => 4, model.Y => 5, model.Z => 10), - (:X => 4, :Y => 5, :Z => 10) - ] + @unpack XY, X, Y, kp, kd, k1, k2 = model + + # Sets problem inputs (to be used for all problem creations). + u0_vals = [X => 4, Y => 5] tspan = (0.0, 10.0) - p_alts = [ - # Vectors not providing default values. - [kp => 1.0, kd => 0.1, k1 => 0.25, Z0 => 10], - [model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.Z0 => 10], - [:kp => 1.0, :kd => 0.1, :k1 => 0.25, :Z0 => 10], - # Vectors providing default values. - [kp => 1.0, kd => 0.1, k1 => 0.25, k2 => 0.5, Z0 => 10], - [model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.k2 => 0.5, model.Z0 => 10], - [:kp => 1.0, :kd => 0.1, :k1 => 0.25, :k2 => 0.5, :Z0 => 10], - # Dicts not providing default values. - Dict([kp => 1.0, kd => 0.1, k1 => 0.25, Z0 => 10]), - Dict([model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.Z0 => 10]), - Dict([:kp => 1.0, :kd => 0.1, :k1 => 0.25, :Z0 => 10]), - # Dicts providing default values. - Dict([kp => 1.0, kd => 0.1, k1 => 0.25, k2 => 0.5, Z0 => 10]), - Dict([model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.k2 => 0.5, model.Z0 => 10]), - Dict([:kp => 1.0, :kd => 0.1, :k1 => 0.25, :k2 => 0.5, :Z0 => 10]), - # Tuples not providing default values. - (kp => 1.0, kd => 0.1, k1 => 0.25, Z0 => 10), - (model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.Z0 => 10), - (:kp => 1.0, :kd => 0.1, :k1 => 0.25, :Z0 => 10), - # Tuples providing default values. - (kp => 1.0, kd => 0.1, k1 => 0.25, k2 => 0.5, Z0 => 10), - (model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.k2 => 0.5, model.Z0 => 10), - (:kp => 1.0, :kd => 0.1, :k1 => 0.25, :k2 => 0.5, :Z0 => 10), - ] + p_vals = [kp => 1.0, kd => 0.1, k1 => 0.25, k2 => 0.5] + + # Creates problems. + oprob = ODEProblem(model, u0_vals, tspan, p_vals) + sprob = SDEProblem(model,u0_vals, tspan, p_vals) + dprob = DiscreteProblem(model, u0_vals, tspan, p_vals) + jprob = JumpProblem(model, deepcopy(dprob), Direct(); rng) + nprob = NonlinearProblem(model, u0_vals, p_vals) + ssprob = SteadyStateProblem(model, u0_vals, p_vals) + problems = [oprob, sprob, dprob, jprob, nprob, ssprob] + + # Creates an `EnsembleProblem` for each problem. + eoprob = EnsembleProblem(oprob) + esprob = EnsembleProblem(sprob) + edprob = EnsembleProblem(dprob) + ejprob = EnsembleProblem(jprob) + enprob = EnsembleProblem(nprob) + essprob = EnsembleProblem(ssprob) + eproblems = [eoprob, esprob, edprob, ejprob, enprob, essprob] + + # Creates integrators. + oint = init(oprob, Tsit5(); save_everystep = false) + sint = init(sprob, ImplicitEM(); save_everystep = false) + jint = init(jprob, SSAStepper()) + nint = init(nprob, NewtonRaphson(); save_everystep = false) + @test_broken ssint = init(ssprob, DynamicSS(Tsit5()); save_everystep = false) # https://github.com/SciML/SciMLBase.jl/issues/660 + integrators = [oint, sint, jint, nint] + + # Creates solutions. + osol = solve(oprob, Tsit5()) + ssol = solve(sprob, ImplicitEM(); seed) + jsol = solve(jprob, SSAStepper(); seed) + nsol = solve(nprob, NewtonRaphson()) + sssol = solve(nprob, DynamicSS(Tsit5())) + sols = [osol, ssol, jsol, nsol, sssol] end -# Perform ODE simulations (singular and ensemble). +# Tests problem indexing and updating. let - # Creates normal and ensemble problems. - base_oprob = ODEProblem(model, u0_alts[1], tspan, p_alts[1]) - base_sol = solve(base_oprob, Tsit5(); saveat = 1.0) - base_eprob = EnsembleProblem(base_oprob) - base_esol = solve(base_eprob, Tsit5(); trajectories = 2, saveat = 1.0) - - # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts, p in p_alts - oprob = remake(base_oprob; u0, p) - @test base_sol == solve(oprob, Tsit5(); saveat = 1.0) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, Tsit5(); trajectories = 2, saveat = 1.0) + @test_broken false # A few cases fails for JumpProblem: https://github.com/SciML/ModelingToolkit.jl/issues/2838 + @test_broken false # A few cases fails for SteadyStateProblem: https://github.com/SciML/SciMLBase.jl/issues/660 + @test_broken false # Most cases broken for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 + for prob in deepcopy([oprob, sprob, dprob, nprob]) + # Get u values (including observables). + @test prob[X] == prob[model.X] == prob[:X] == 4 + @test prob[XY] == prob[model.XY] == prob[:XY] == 9 + @test prob[[XY,Y]] == prob[[model.XY,model.Y]] == prob[[:XY,:Y]] == [9, 5] + @test prob[(XY,Y)] == prob[(model.XY,model.Y)] == prob[(:XY,:Y)] == (9, 5) + @test getu(prob, X)(prob) == getu(prob, model.X)(prob) == getu(prob, :X)(prob) == 4 + @test getu(prob, XY)(prob) == getu(prob, model.XY)(prob) == getu(prob, :XY)(prob) == 9 + @test getu(prob, [XY,Y])(prob) == getu(prob, [model.XY,model.Y])(prob) == getu(prob, [:XY,:Y])(prob) == [9, 5] + @test getu(prob, (XY,Y))(prob) == getu(prob, (model.XY,model.Y))(prob) == getu(prob, (:XY,:Y))(prob) == (9, 5) + + # Set u values. + prob[X] = 20 + @test prob[X] == 20 + prob[model.X] = 30 + @test prob[X] == 30 + prob[:X] = 40 + @test prob[X] == 40 + setu(prob, X)(prob, 50) + @test prob[X] == 50 + setu(prob, model.X)(prob, 60) + @test prob[X] == 60 + setu(prob, :X)(prob, 70) + @test prob[X] == 70 + + # Get p values. + @test prob.ps[kp] == prob.ps[model.kp] == prob.ps[:kp] == 1.0 + @test prob.ps[[k1,k2]] == prob.ps[[model.k1,model.k2]] == prob.ps[[:k1,:k2]] == [0.25, 0.5] + @test prob.ps[(k1,k2)] == prob.ps[(model.k1,model.k2)] == prob.ps[(:k1,:k2)] == (0.25, 0.5) + @test getp(prob, kp)(prob) == getp(prob, model.kp)(prob) == getp(prob, :kp)(prob) == 1.0 + @test getp(prob, [k1,k2])(prob) == getp(prob, [model.k1,model.k2])(prob) == getp(prob, [:k1,:k2])(prob) == [0.25, 0.5] + @test getp(prob, (k1,k2))(prob) == getp(prob, (model.k1,model.k2))(prob) == getp(prob, (:k1,:k2))(prob) == (0.25, 0.5) + + # Set p values. + prob.ps[kp] = 2.0 + @test prob.ps[kp] == 2.0 + prob.ps[model.kp] = 3.0 + @test prob.ps[kp] == 3.0 + prob.ps[:kp] = 4.0 + @test prob.ps[kp] == 4.0 + setp(prob, kp)(prob, 5.0) + @test prob.ps[kp] == 5.0 + setp(prob, model.kp)(prob, 6.0) + @test prob.ps[kp] == 6.0 + setp(prob, :kp)(prob, 7.0) + @test prob.ps[kp] == 7.0 end end -# Perform SDE simulations (singular and ensemble). +# Test remake function. let - # Creates normal and ensemble problems. - base_sprob = SDEProblem(model, u0_alts[1], tspan, p_alts[1]) - base_sol = solve(base_sprob, ImplicitEM(); seed, saveat = 1.0) - base_eprob = EnsembleProblem(base_sprob) - base_esol = solve(base_eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) - - # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts, p in p_alts - sprob = remake(base_sprob; u0, p) - @test base_sol == solve(sprob, ImplicitEM(); seed, saveat = 1.0) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) + @test_broken false # Cannot check result for JumpProblem: https://github.com/SciML/ModelingToolkit.jl/issues/2838 + @test_broken false # Cannot deepcopy SteadyStateProblem :https://github.com/SciML/ModelingToolkit.jl/issues/2837 + @test_broken false # Currently cannot be run for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 (as indexing cannot be used to check values). + for prob in deepcopy([oprob, sprob, dprob, nprob]) + # Remake for all u0s. + rp = remake(prob; u0 = [X => 1, Y => 2]) + @test rp[[X, Y]] == [1, 2] + rp = remake(prob; u0 = [model.X => 3, model.Y => 4]) + @test rp[[X, Y]] == [3, 4] + rp = remake(prob; u0 = [:X => 5, :Y => 6]) + @test rp[[X, Y]] == [5, 6] + + # Remake for a single u0. + rp = remake(prob; u0 = [Y => 7]) + @test rp[[X, Y]] == [4, 7] + rp = remake(prob; u0 = [model.Y => 8]) + @test rp[[X, Y]] == [4, 8] + rp = remake(prob; u0 = [:Y => 9]) + @test rp[[X, Y]] == [4, 9] + + # Remake for all ps. + rp = remake(prob; p = [kp => 1.0, kd => 2.0, k1 => 3.0, k2 => 4.0]) + @test rp.ps[[kp, kd, k1, k2]] == [1.0, 2.0, 3.0, 4.0] + rp = remake(prob; p = [model.kp => 5.0, model.kd => 6.0, model.k1 => 7.0, model.k2 => 8.0]) + @test rp.ps[[kp, kd, k1, k2]] == [5.0, 6.0, 7.0, 8.0] + rp = remake(prob; p = [:kp => 9.0, :kd => 10.0, :k1 => 11.0, :k2 => 12.0]) + @test rp.ps[[kp, kd, k1, k2]] == [9.0, 10.0, 11.0, 12.0] + + # Remake for a single p. + rp = remake(prob; p = [k2 => 13.0]) + @test rp.ps[[kp, kd, k1, k2]] == [1.0, 0.1, 0.25, 13.0] + rp = remake(prob; p = [model.k2 => 14.0]) + @test rp.ps[[kp, kd, k1, k2]] == [1.0, 0.1, 0.25, 14.0] + rp = remake(prob; p = [:k2 => 15.0]) + @test rp.ps[[kp, kd, k1, k2]] == [1.0, 0.1, 0.25, 15.0] end end -# Perform jump simulations (singular and ensemble). +# Test integrator indexing. let - # Creates normal and ensemble problems. - base_dprob = DiscreteProblem(model, u0_alts[1], tspan, p_alts[1]) - base_jprob = JumpProblem(model, base_dprob, Direct(); rng) - base_sol = solve(base_jprob, SSAStepper(); seed, saveat = 1.0) - base_eprob = EnsembleProblem(base_jprob) - base_esol = solve(base_eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) - - # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts, p in p_alts - jprob = remake(base_jprob; u0, p) - @test base_sol == solve(base_jprob, SSAStepper(); seed, saveat = 1.0) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) + @test_broken false # NOTE: Cannot even create a `ssint` (https://github.com/SciML/SciMLBase.jl/issues/660). + for int in deepcopy([oint, sint, jint, nint]) + # Get u values. + @test int[X] == int[model.X] == int[:X] == 4 + @test int[XY] == int[model.XY] == int[:XY] == 9 + @test int[[XY,Y]] == int[[model.XY,model.Y]] == int[[:XY,:Y]] == [9, 5] + @test int[(XY,Y)] == int[(model.XY,model.Y)] == int[(:XY,:Y)] == (9, 5) + @test getu(int, X)(int) == getu(int, model.X)(int) == getu(int, :X)(int) == 4 + @test getu(int, XY)(int) == getu(int, model.XY)(int) == getu(int, :XY)(int) == 9 + @test getu(int, [XY,Y])(int) == getu(int, [model.XY,model.Y])(int) == getu(int, [:XY,:Y])(int) == [9, 5] + @test getu(int, (XY,Y))(int) == getu(int, (model.XY,model.Y))(int) == getu(int, (:XY,:Y))(int) == (9, 5) + + # Set u values. + int[X] = 20 + @test int[X] == 20 + int[model.X] = 30 + @test int[X] == 30 + int[:X] = 40 + @test int[X] == 40 + setu(int, X)(int, 50) + @test int[X] == 50 + setu(int, model.X)(int, 60) + @test int[X] == 60 + setu(int, :X)(int, 70) + @test int[X] == 70 + + # Get p values. + @test int.ps[kp] == int.ps[model.kp] == int.ps[:kp] == 1.0 + @test int.ps[[k1,k2]] == int.ps[[model.k1,model.k2]] == int.ps[[:k1,:k2]] == [0.25, 0.5] + @test int.ps[(k1,k2)] == int.ps[(model.k1,model.k2)] == int.ps[(:k1,:k2)] == (0.25, 0.5) + @test getp(int, kp)(int) == getp(int, model.kp)(int) == getp(int, :kp)(int) == 1.0 + @test getp(int, [k1,k2])(int) == getp(int, [model.k1,model.k2])(int) == getp(int, [:k1,:k2])(int) == [0.25, 0.5] + @test getp(int, (k1,k2))(int) == getp(int, (model.k1,model.k2))(int) == getp(int, (:k1,:k2))(int) == (0.25, 0.5) + + # Set p values. + int.ps[kp] = 2.0 + @test int.ps[kp] == 2.0 + int.ps[model.kp] = 3.0 + @test int.ps[kp] == 3.0 + int.ps[:kp] = 4.0 + @test int.ps[kp] == 4.0 + setp(int, kp)(int, 5.0) + @test int.ps[kp] == 5.0 + setp(int, model.kp)(int, 6.0) + @test int.ps[kp] == 6.0 + setp(int, :kp)(int, 7.0) + @test int.ps[kp] == 7.0 end end -# Solves a nonlinear problem (EnsembleProblems are not possible for these). -let - base_nlprob = NonlinearProblem(model, u0_alts[1], p_alts[1]) - base_sol = solve(base_nlprob, NewtonRaphson()) - for u0 in u0_alts, p in p_alts - nlprob = remake(base_nlprob; u0, p) - @test base_sol == solve(nlprob, NewtonRaphson()) +# Test solve's save_idxs argument. +# Currently, `save_idxs` is broken with symbolic stuff (https://github.com/SciML/ModelingToolkit.jl/issues/1761). +let + for (prob, solver) in zip(deepcopy([oprob, sprob, jprob]), [Tsit5(), ImplicitEM(), SSAStepper()]) + # Save single variable + @test_broken solve(prob, solver; seed, save_idxs=X)[X][1] == 4 + @test_broken solve(prob, solver; seed, save_idxs=model.X)[X][1] == 4 + @test_broken solve(prob, solver; seed, save_idxs=:X)[X][1] == 4 + + # Save observable. + @test_broken solve(prob, solver; seed, save_idxs=XY)[XY][1] == 9 + @test_broken solve(prob, solver; seed, save_idxs=model.XY)[XY][1] == 9 + @test_broken solve(prob, solver; seed, save_idxs=:XY)[XY][1] == 9 + + # Save vector of stuff. + @test_broken solve(prob, solver; seed, save_idxs=[XY,Y])[[XY,Y]][1] == [9, 5] + @test_broken solve(prob, solver; seed, save_idxs=[model.XY,model.Y])[[model.XY,model.Y]][1] == [9, 5] + @test_broken solve(prob, solver; seed, save_idxs=[:XY,:Y])[[:XY,:Y]][1] == [9, 5] end end -# Perform steady state simulations (singular and ensemble). +# Tests solution indexing. let - # Creates normal and ensemble problems. - base_ssprob = SteadyStateProblem(model, u0_alts[1], p_alts[1]) - base_sol = solve(base_ssprob, DynamicSS(Tsit5())) - base_eprob = EnsembleProblem(base_ssprob) - base_esol = solve(base_eprob, DynamicSS(Tsit5()); trajectories = 2) - - # Simulates problems for all input types, checking that identical solutions are found. - for u0 in u0_alts, p in p_alts - ssprob = remake(base_ssprob; u0, p) - @test base_sol == solve(ssprob, DynamicSS(Tsit5())) - eprob = remake(base_eprob; u0, p) - @test base_esol == solve(eprob, DynamicSS(Tsit5()); trajectories = 2) + for sol in deepcopy([osol, ssol, jsol]) + # Get u values. + @test sol[X][1] == sol[model.X][1] == sol[:X][1] == 4 + @test sol[XY][1] == sol[model.XY][1] == sol[:XY][1] == 9 + @test sol[[XY,Y]][1] == sol[[model.XY,model.Y]][1] == sol[[:XY,:Y]][1] == [9, 5] + @test sol[(XY,Y)][1] == sol[(model.XY,model.Y)][1] == sol[(:XY,:Y)][1] == (9, 5) + @test getu(sol, X)(sol)[1] == getu(sol, model.X)(sol)[1] == getu(sol, :X)(sol)[1] == 4 + @test getu(sol, XY)(sol)[1] == getu(sol, model.XY)(sol)[1] == getu(sol, :XY)(sol)[1] == 9 + @test getu(sol, [XY,Y])(sol)[1] == getu(sol, [model.XY,model.Y])(sol)[1] == getu(sol, [:XY,:Y])(sol)[1] == [9, 5] + @test getu(sol, (XY,Y))(sol)[1] == getu(sol, (model.XY,model.Y))(sol)[1] == getu(sol, (:XY,:Y))(sol)[1] == (9, 5) + + # Get u values via idxs and functional call. + @test osol(0.0; idxs=X) == osol(0.0; idxs=model.X) == osol(0.0; idxs=:X) == 4 + @test osol(0.0; idxs=XY) == osol(0.0; idxs=model.XY) == osol(0.0; idxs=:XY) == 9 + @test osol(0.0; idxs = [XY,Y]) == osol(0.0; idxs = [model.XY,model.Y]) == osol(0.0; idxs = [:XY,:Y]) == [9, 5] + @test_broken osol(0.0; idxs = (XY,Y)) == osol(0.0; idxs = (model.XY,model.Y)) == osol(0.0; idxs = (:XY,:Y)) == (9, 5) # https://github.com/SciML/SciMLBase.jl/issues/711 + + # Get p values. + @test sol.ps[kp] == sol.ps[model.kp] == sol.ps[:kp] == 1.0 + @test sol.ps[[k1,k2]] == sol.ps[[model.k1,model.k2]] == sol.ps[[:k1,:k2]] == [0.25, 0.5] + @test sol.ps[(k1,k2)] == sol.ps[(model.k1,model.k2)] == sol.ps[(:k1,:k2)] == (0.25, 0.5) + @test getp(sol, kp)(sol) == getp(sol, model.kp)(sol) == getp(sol, :kp)(sol) == 1.0 + @test getp(sol, [k1,k2])(sol) == getp(sol, [model.k1,model.k2])(sol) == getp(sol, [:k1,:k2])(sol) == [0.25, 0.5] + @test getp(sol, (k1,k2))(sol) == getp(sol, (model.k1,model.k2))(sol) == getp(sol, (:k1,:k2))(sol) == (0.25, 0.5) end -end -### Vector Species/Parameters Tests ### - -begin - # Declares the model (with vector species/parameters, with/without default values, and observables). - t = default_t() - @species X(t)[1:2] Y(t)[1:2] = [10.0, 20.0] XY(t)[1:2] - @parameters p[1:2] d[1:2] = [0.2, 0.5] - rxs = [ - Reaction(p[1], [], [X[1]]), - Reaction(p[2], [], [X[2]]), - Reaction(d[1], [X[1]], []), - Reaction(d[2], [X[2]], []), - Reaction(p[1], [], [Y[1]]), - Reaction(p[2], [], [Y[2]]), - Reaction(d[1], [Y[1]], []), - Reaction(d[2], [Y[2]], []) - ] - observed = [XY[1] ~ X[1] + Y[1], XY[2] ~ X[2] + Y[2]] - @named model_vec = ReactionSystem(rxs, t; observed) - model_vec = complete(model_vec) - - # Declares various u0 versions (scalarised and vector forms). - u0_alts_vec = [ - # Vectors not providing default values. - [X => [1.0, 2.0]], - [X[1] => 1.0, X[2] => 2.0], - [model_vec.X => [1.0, 2.0]], - [model_vec.X[1] => 1.0, model_vec.X[2] => 2.0], - [:X => [1.0, 2.0]], - # Vectors providing default values. - [X => [1.0, 2.0], Y => [10.0, 20.0]], - [X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0], - [model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]], - [model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0], - [:X => [1.0, 2.0], :Y => [10.0, 20.0]], - # Dicts not providing default values. - Dict([X => [1.0, 2.0]]), - Dict([X[1] => 1.0, X[2] => 2.0]), - Dict([model_vec.X => [1.0, 2.0]]), - Dict([model_vec.X[1] => 1.0, model_vec.X[2] => 2.0]), - Dict([:X => [1.0, 2.0]]), - # Dicts providing default values. - Dict([X => [1.0, 2.0], Y => [10.0, 20.0]]), - Dict([X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0]), - Dict([model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]]), - Dict([model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0]), - Dict([:X => [1.0, 2.0], :Y => [10.0, 20.0]]), - # Tuples not providing default values. - (X => [1.0, 2.0]), - (X[1] => 1.0, X[2] => 2.0), - (model_vec.X => [1.0, 2.0]), - (model_vec.X[1] => 1.0, model_vec.X[2] => 2.0), - (:X => [1.0, 2.0]), - # Tuples providing default values. - (X => [1.0, 2.0], Y => [10.0, 20.0]), - (X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0), - (model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]), - (model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0), - (:X => [1.0, 2.0], :Y => [10.0, 20.0]), - ] - - # Declares various ps versions (vector forms only). - p_alts_vec = [ - # Vectors not providing default values. - [p => [1.0, 2.0]], - [model_vec.p => [1.0, 2.0]], - [:p => [1.0, 2.0]], - # Vectors providing default values. - [p => [4.0, 5.0], d => [0.2, 0.5]], - [model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]], - [:p => [4.0, 5.0], :d => [0.2, 0.5]], - # Dicts not providing default values. - Dict([p => [1.0, 2.0]]), - Dict([model_vec.p => [1.0, 2.0]]), - Dict([:p => [1.0, 2.0]]), - # Dicts providing default values. - Dict([p => [4.0, 5.0], d => [0.2, 0.5]]), - Dict([model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]]), - Dict([:p => [4.0, 5.0], :d => [0.2, 0.5]]), - # Tuples not providing default values. - (p => [1.0, 2.0]), - (model_vec.p => [1.0, 2.0]), - (:p => [1.0, 2.0]), - # Tuples providing default values. - (p => [4.0, 5.0], d => [0.2, 0.5]), - (model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]), - (:p => [4.0, 5.0], :d => [0.2, 0.5]), - ] - - # Declares a timespan. - tspan = (0.0, 10.0) -end - -# Perform ODE simulations (singular and ensemble). -let - # Creates normal and ensemble problems. - base_oprob = ODEProblem(model_vec, u0_alts_vec[1], tspan, p_alts_vec[1]) - base_sol = solve(base_oprob, Tsit5(); saveat = 1.0) - base_eprob = EnsembleProblem(base_oprob) - base_esol = solve(base_eprob, Tsit5(); trajectories = 2, saveat = 1.0) - - # Simulates problems for all input types, checking that identical solutions are found. - @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). - # for u0 in u0_alts_vec, p in p_alts_vec - # oprob = remake(base_oprob; u0, p) - # @test base_sol == solve(oprob, Tsit5(); saveat = 1.0) - # eprob = remake(base_eprob; u0, p) - # @test base_esol == solve(eprob, Tsit5(); trajectories = 2, saveat = 1.0) - # end + # Handles nonlinear and steady state solutions differently. + let + for sol in deepcopy([nsol, sssol]) + # Get u values. + @test sol[X] == sol[model.X] == sol[:X] + @test sol[XY] == sol[model.XY][1] == sol[:XY] + @test sol[[XY,Y]] == sol[[model.XY,model.Y]] == sol[[:XY,:Y]] + @test sol[(XY,Y)] == sol[(model.XY,model.Y)] == sol[(:XY,:Y)] + @test getu(sol, X)(sol) == getu(sol, model.X)(sol)[1] == getu(sol, :X)(sol) + @test getu(sol, XY)(sol) == getu(sol, model.XY)(sol)[1] == getu(sol, :XY)(sol) + @test getu(sol, [XY,Y])(sol) == getu(sol, [model.XY,model.Y])(sol) == getu(sol, [:XY,:Y])(sol) + @test_broken getu(sol, (XY,Y))(sol) == getu(sol, (model.XY,model.Y))(sol) == getu(sol, (:XY,:Y))(sol)[1] # https://github.com/SciML/SciMLBase.jl/issues/710 + + # Get p values. + @test sol.ps[kp] == sol.ps[model.kp] == sol.ps[:kp] + @test sol.ps[[k1,k2]] == sol.ps[[model.k1,model.k2]] == sol.ps[[:k1,:k2]] + @test sol.ps[(k1,k2)] == sol.ps[(model.k1,model.k2)] == sol.ps[(:k1,:k2)] + @test getp(sol, kp)(sol) == getp(sol, model.kp)(sol) == getp(sol, :kp)(sol) + @test getp(sol, [k1,k2])(sol) == getp(sol, [model.k1,model.k2])(sol) == getp(sol, [:k1,:k2])(sol) + @test getp(sol, (k1,k2))(sol) == getp(sol, (model.k1,model.k2))(sol) == getp(sol, (:k1,:k2))(sol) + end + end end -# Perform SDE simulations (singular and ensemble). +# Tests plotting. let - # Creates normal and ensemble problems. - base_sprob = SDEProblem(model_vec, u0_alts_vec[1], tspan, p_alts_vec[1]) - base_sol = solve(base_sprob, ImplicitEM(); seed, saveat = 1.0) - base_eprob = EnsembleProblem(base_sprob) - base_esol = solve(base_eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) - - # Simulates problems for all input types, checking that identical solutions are found. - @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). - # for u0 in u0_alts_vec, p in p_alts_vec - # sprob = remake(base_sprob; u0, p) - # @test base_sol == solve(sprob, ImplicitEM(); seed, saveat = 1.0) - # eprob = remake(base_eprob; u0, p) - # @test base_esol == solve(eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) - # end + for sol in deepcopy([osol, jsol, ssol]) + # Single variable. + @test length(plot(sol; idxs = X).series_list) == 1 + @test length(plot(sol; idxs = XY).series_list) == 1 + @test length(plot(sol; idxs = model.X).series_list) == 1 + @test length(plot(sol; idxs = model.XY).series_list) == 1 + @test length(plot(sol; idxs = :X).series_list) == 1 + @test length(plot(sol; idxs = :XY).series_list) == 1 + + # As vector. + @test length(plot(sol; idxs = [X,Y]).series_list) == 2 + @test length(plot(sol; idxs = [XY,Y]).series_list) == 2 + @test length(plot(sol; idxs = [model.X,model.Y]).series_list) == 2 + @test length(plot(sol; idxs = [model.XY,model.Y]).series_list) == 2 + @test length(plot(sol; idxs = [:X,:Y]).series_list) == 2 + @test length(plot(sol; idxs = [:XY,:Y]).series_list) == 2 + + # As tuple. + @test length(plot(sol; idxs = (X, Y)).series_list) == 1 + @test length(plot(sol; idxs = (XY, Y)).series_list) == 1 + @test length(plot(sol; idxs = (model.X, model.Y)).series_list) == 1 + @test length(plot(sol; idxs = (model.XY, model.Y)).series_list) == 1 + @test length(plot(sol; idxs = (:X, :Y)).series_list) == 1 + @test length(plot(sol; idxs = (:XY, :Y)).series_list) == 1 + end end -# Perform jump simulations (singular and ensemble). -let - # Creates normal and ensemble problems. - base_dprob = DiscreteProblem(model_vec, u0_alts_vec[1], tspan, p_alts_vec[1]) - base_jprob = JumpProblem(model_vec, base_dprob, Direct(); rng) - base_sol = solve(base_jprob, SSAStepper(); seed, saveat = 1.0) - base_eprob = EnsembleProblem(base_jprob) - base_esol = solve(base_eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) - - # Simulates problems for all input types, checking that identical solutions are found. - @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). - # for u0 in u0_alts_vec, p in p_alts_vec - # jprob = remake(base_jprob; u0, p) - # @test base_sol == solve(base_jprob, SSAStepper(); seed, saveat = 1.0) - # eprob = remake(base_eprob; u0, p) - # @test base_esol == solve(eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) - # end -end -# Solves a nonlinear problem (EnsembleProblems are not possible for these). -let - base_nlprob = NonlinearProblem(model_vec, u0_alts_vec[1], p_alts_vec[1]) - base_sol = solve(base_nlprob, NewtonRaphson()) - @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). - # for u0 in u0_alts_vec, p in p_alts_vec - # nlprob = remake(base_nlprob; u0, p) - # @test base_sol == solve(nlprob, NewtonRaphson()) - # end -end +### Tests For Hierarchical System ### -# Perform steady state simulations (singular and ensemble). -let - # Creates normal and ensemble problems. - base_ssprob = SteadyStateProblem(model_vec, u0_alts_vec[1], p_alts_vec[1]) - base_sol = solve(base_ssprob, DynamicSS(Tsit5())) - base_eprob = EnsembleProblem(base_ssprob) - base_esol = solve(base_eprob, DynamicSS(Tsit5()); trajectories = 2) - - # Simulates problems for all input types, checking that identical solutions are found. - @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). - # for u0 in u0_alts_vec, p in p_alts_vec - # ssprob = remake(base_ssprob; u0, p) - # @test base_sol == solve(ssprob, DynamicSS(Tsit5())) - # eprob = remake(base_eprob; u0, p) - # @test base_esol == solve(eprob, DynamicSS(Tsit5()); trajectories = 2) - # end -end +# TODO -### Checks Errors On Faulty Inputs ### +### Mass Action Jump Rate Updating Correctness ### -# Checks various erroneous problem inputs, ensuring that these throw errors. +# Checks that the rates of mass action jumps are correctly updated after parameter values are changed. let - # Declares the model. + # Creates the model. rn = @reaction_network begin - (k1,k2), X1 <--> X2 - end - @unpack k1, k2, X1, X2 = rn - t = default_t() - @species X3(t) - @parameters k3 - - # Creates systems (so these are not recreated in each problem call). - osys = convert(ODESystem, rn) - ssys = convert(SDESystem, rn) - nsys = convert(NonlinearSystem, rn) - - # Declares valid initial conditions and parameter values - u0_valid = [X1 => 1, X2 => 2] - ps_valid = [k1 => 0.5, k2 => 0.1] - - # Declares invalid initial conditions and parameters. This includes both cases where values are - # missing, or additional ones are given. Includes vector/Tuple/Dict forms. - u0s_invalid = [ - # Missing a value. - [X1 => 1], - [rn.X1 => 1], - [:X1 => 1], - Dict([X1 => 1]), - Dict([rn.X1 => 1]), - Dict([:X1 => 1]), - (X1 => 1), - (rn.X1 => 1), - (:X1 => 1), - # Contain an additional value. - [X1 => 1, X2 => 2, X3 => 3], - [:X1 => 1, :X2 => 2, :X3 => 3], - Dict([X1 => 1, X2 => 2, X3 => 3]), - Dict([:X1 => 1, :X2 => 2, :X3 => 3]), - (X1 => 1, X2 => 2, X3 => 3), - (:X1 => 1, :X2 => 2, :X3 => 3) - ] - ps_invalid = [ - # Missing a value. - [k1 => 1.0], - [rn.k1 => 1.0], - [:k1 => 1.0], - Dict([k1 => 1.0]), - Dict([rn.k1 => 1.0]), - Dict([:k1 => 1.0]), - (k1 => 1.0), - (rn.k1 => 1.0), - (:k1 => 1.0), - # Contain an additional value. - [k1 => 1.0, k2 => 2.0, k3 => 3.0], - [:k1 => 1.0, :k2 => 2.0, :k3 => 3.0], - Dict([k1 => 1.0, k2 => 2.0, k3 => 3.0]), - Dict([:k1 => 1.0, :k2 => 2.0, :k3 => 3.0]), - (k1 => 1.0, k2 => 2.0, k3 => 3.0), - (:k1 => 1.0, :k2 => 2.0, :k3 => 3.0) - ] - - # Loops through all potential parameter sets, checking their inputs yield errors. - for ps in [[ps_valid]; ps_invalid], u0 in [[u0_valid]; u0s_invalid] - # Handles problems with/without tspan separately. Special check ensuring that valid inputs passes. - for XProblem in [ODEProblem, SDEProblem, DiscreteProblem] - if isequal(ps, ps_valid) && isequal(u0, u0_valid) - XProblem(rn, u0, (0.0, 1.0), ps); @test true; - else - @test_broken false - continue - @test_throws Exception XProblem(xsys, u0, (0.0, 1.0), ps) - end - end - for XProblem in [NonlinearProblem, SteadyStateProblem] - if isequal(ps, ps_valid) && isequal(u0, u0_valid) - XProblem(rn, u0, ps); @test true; - else - @test_broken false - continue - @test_throws Exception XProblem(xsys, u0, ps) - end - end + p1*p2, A + B --> C end + @unpack p1, p2 = rn + + # Creates a JumpProblem and integrator. Checks that the initial mass action rate is correct. + u0 = [:A => 1, :B => 2, :C => 3] + ps = [:p1 => 3.0, :p2 => 2.0] + dprob = DiscreteProblem(rn, u0, (0.0, 1.0), ps) + jprob = JumpProblem(rn, dprob, Direct()) + jint = init(jprob, SSAStepper()) + @test jprob.massaction_jump.scaled_rates[1] == 6.0 + + # Checks that the mass action rate is correctly updated after normal indexing. + jprob.ps[p1] = 4.0 + @test jprob.massaction_jump.scaled_rates[1] == 8.0 + jprob.ps[rn.p1] = 5.0 + @test jprob.massaction_jump.scaled_rates[1] == 10.0 + jprob.ps[:p1] = 6.0 + @test jprob.massaction_jump.scaled_rates[1] == 12.0 + setp(jprob, p1)(jprob, 7.0) + @test jprob.massaction_jump.scaled_rates[1] == 14.0 + setp(jprob, rn.p1)(jprob, 8.0) + @test jprob.massaction_jump.scaled_rates[1] == 16.0 + setp(jprob, :p1)(jprob, 3.0) + @test jprob.massaction_jump.scaled_rates[1] == 6.0 + + # Check that the mass action rate is correctly updated when `remake` is used. + # Checks both when partial and full parameter vectors are provided to `remake`. + @test remake(jprob; p = [p1 => 4.0]).massaction_jump.scaled_rates[1] == 8.0 + @test remake(jprob; p = [rn.p1 => 5.0]).massaction_jump.scaled_rates[1] == 10.0 + @test remake(jprob; p = [:p1 => 6.0]).massaction_jump.scaled_rates[1] == 12.0 + @test remake(jprob; p = [p1 => 4.0, p2 => 3.0]).massaction_jump.scaled_rates[1] == 12.0 + @test remake(jprob; p = [rn.p1 => 5.0, rn.p2 => 4.0]).massaction_jump.scaled_rates[1] == 20.0 + @test remake(jprob; p = [:p1 => 6.0, :p2 => 5.0]).massaction_jump.scaled_rates[1] == 30.0 + + # Checks that updating an integrators parameter values does not affect mass action rate until after + # `reset_aggregated_jumps!` have been applied as well (wt which point the correct rate is achieved). + jint.ps[p1] = 4.0 + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 30.0 + reset_aggregated_jumps!(jint) + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 8.0 + + jint.ps[rn.p1] = 5.0 + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 8.0 + reset_aggregated_jumps!(jint) + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 10.0 + + jint.ps[:p1] = 6.0 + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 10.0 + reset_aggregated_jumps!(jint) + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 12.0 + + setp(jint, p1)(jint, 7.0) + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 12.0 + reset_aggregated_jumps!(jint) + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 14.0 + + setp(jint, rn.p1)(jint, 8.0) + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 14.0 + reset_aggregated_jumps!(jint) + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 16.0 + + setp(jint, :p1)(jint, 3.0) + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 16.0 + reset_aggregated_jumps!(jint) + @test jint.cb.condition.ma_jumps.scaled_rates[1] == 6.0 end From d6f1588504f77633e7bab735a975601ea69bb6f5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 1 Jul 2024 23:33:12 -0400 Subject: [PATCH 369/446] fix --- test/upstream/mtk_structure_indexing.jl | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/test/upstream/mtk_structure_indexing.jl b/test/upstream/mtk_structure_indexing.jl index ae65360128..61a09f0b9d 100644 --- a/test/upstream/mtk_structure_indexing.jl +++ b/test/upstream/mtk_structure_indexing.jl @@ -64,14 +64,15 @@ end # Tests problem indexing and updating. let + @test_broken false # A few cases fails for JumpProblem: https://github.com/SciML/ModelingToolkit.jl/issues/2838 @test_broken false # A few cases fails for SteadyStateProblem: https://github.com/SciML/SciMLBase.jl/issues/660 @test_broken false # Most cases broken for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 - for prob in deepcopy(problems[1:end-1]) + for prob in deepcopy([oprob, sprob, dprob, nprob]) # Get u values (including observables). @test prob[X] == prob[model.X] == prob[:X] == 4 @test prob[XY] == prob[model.XY] == prob[:XY] == 9 @test prob[[XY,Y]] == prob[[model.XY,model.Y]] == prob[[:XY,:Y]] == [9, 5] - @test_broken prob[(XY,Y)] == prob[(model.XY,model.Y)] == prob[(:XY,:Y)] == (9, 5) + @test prob[(XY,Y)] == prob[(model.XY,model.Y)] == prob[(:XY,:Y)] == (9, 5) @test getu(prob, X)(prob) == getu(prob, model.X)(prob) == getu(prob, :X)(prob) == 4 @test getu(prob, XY)(prob) == getu(prob, model.XY)(prob) == getu(prob, :XY)(prob) == 9 @test getu(prob, [XY,Y])(prob) == getu(prob, [model.XY,model.Y])(prob) == getu(prob, [:XY,:Y])(prob) == [9, 5] @@ -117,8 +118,10 @@ end # Test remake function. let + @test_broken false # Cannot check result for JumpProblem: https://github.com/SciML/ModelingToolkit.jl/issues/2838 + @test_broken false # Cannot deepcopy SteadyStateProblem :https://github.com/SciML/ModelingToolkit.jl/issues/2837 @test_broken false # Currently cannot be run for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 (as indexing cannot be used to check values). - for prob in deepcopy(problems) + for prob in deepcopy([oprob, sprob, dprob, nprob]) # Remake for all u0s. rp = remake(prob; u0 = [X => 1, Y => 2]) @test rp[[X, Y]] == [1, 2] @@ -155,10 +158,8 @@ end # Test integrator indexing. let - @test_broken false # NOTE: Multiple problems for `nint` (https://github.com/SciML/SciMLBase.jl/issues/662). - @test_broken false # NOTE: Multiple problems for `jint` (https://github.com/SciML/SciMLBase.jl/issues/654). @test_broken false # NOTE: Cannot even create a `ssint` (https://github.com/SciML/SciMLBase.jl/issues/660). - for int in deepcopy([oint, sint]) + for int in deepcopy([oint, sint, jint, nint]) # Get u values. @test int[X] == int[model.X] == int[:X] == 4 @test int[XY] == int[model.XY] == int[:XY] == 9 @@ -258,13 +259,12 @@ let # Handles nonlinear and steady state solutions differently. let - @test_broken false # Currently a problem for nonlinear solutions and steady state solutions (https://github.com/SciML/SciMLBase.jl/issues/720). - for sol in deepcopy([]) + for sol in deepcopy([nsol, sssol]) # Get u values. @test sol[X] == sol[model.X] == sol[:X] @test sol[XY] == sol[model.XY][1] == sol[:XY] @test sol[[XY,Y]] == sol[[model.XY,model.Y]] == sol[[:XY,:Y]] - @test_broken sol[(XY,Y)] == sol[(model.XY,model.Y)] == sol[(:XY,:Y)] + @test sol[(XY,Y)] == sol[(model.XY,model.Y)] == sol[(:XY,:Y)] @test getu(sol, X)(sol) == getu(sol, model.X)(sol)[1] == getu(sol, :X)(sol) @test getu(sol, XY)(sol) == getu(sol, model.XY)(sol)[1] == getu(sol, :XY)(sol) @test getu(sol, [XY,Y])(sol) == getu(sol, [model.XY,model.Y])(sol) == getu(sol, [:XY,:Y])(sol) @@ -283,8 +283,7 @@ end # Tests plotting. let - @test_broken false # Currently broken for `ssol` (https://github.com/SciML/SciMLBase.jl/issues/580) - for sol in deepcopy([osol, jsol]) + for sol in deepcopy([osol, jsol, ssol]) # Single variable. @test length(plot(sol; idxs = X).series_list) == 1 @test length(plot(sol; idxs = XY).series_list) == 1 @@ -389,4 +388,3 @@ let reset_aggregated_jumps!(jint) @test jint.cb.condition.ma_jumps.scaled_rates[1] == 6.0 end - From 043694377d67e874f021f4e2f513628f21df3083 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Tue, 2 Jul 2024 00:42:42 -0400 Subject: [PATCH 370/446] Update docs/src/index.md Co-authored-by: Sam Isaacson --- docs/src/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 544a3bbad5..7f9baa07a6 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -171,8 +171,7 @@ plot(sol; lw = 5) ``` #### [Stochastic jump simulations](@id doc_index_example_jump) -The same model can be used as input to other types of simulations. E.g. here we instead perform a -jump simulation +The same model can be used as input to other types of simulations. E.g. here we instead generate and simulate a stochastic chemical kinetics jump process model. ```@example home_simple_example # Create and simulate a jump process (here using Gillespie's direct algorithm). # Note that integer (not decimal) initial conditions are used. From 56e2b48eaf15784a4a9bb5c4a4d07663d485e2be Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Tue, 2 Jul 2024 00:43:22 -0400 Subject: [PATCH 371/446] Update docs/src/index.md Co-authored-by: Sam Isaacson --- docs/src/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/index.md b/docs/src/index.md index 7f9baa07a6..a4c00d3ae0 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -174,7 +174,7 @@ plot(sol; lw = 5) The same model can be used as input to other types of simulations. E.g. here we instead generate and simulate a stochastic chemical kinetics jump process model. ```@example home_simple_example # Create and simulate a jump process (here using Gillespie's direct algorithm). -# Note that integer (not decimal) initial conditions are used. +# The initial conditions are now integers as we track exact populations for each species. using JumpProcesses u0_integers = [:S => 50, :E => 10, :SE => 0, :P => 0] dprob = DiscreteProblem(model, u0_integers, tspan, ps) From 9b2016fc92e5673143c493a7ab7f632b88b04866 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Tue, 2 Jul 2024 00:43:35 -0400 Subject: [PATCH 372/446] Update docs/src/index.md Co-authored-by: Sam Isaacson --- docs/src/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/index.md b/docs/src/index.md index a4c00d3ae0..aab0af8c8f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -206,7 +206,7 @@ cell_model = @reaction_network begin kᵢ/V, Gᴾ --> G end ``` -In this case, we would instead like to perform stochastic simulations, so we transform our model to an SDE: +We now study the system as a Chemical Langevin Dynamics SDE model, which can be generated as follows ```@example home_elaborate_example u0 = [:V => 25.0, :G => 50.0, :Gᴾ => 0.0] tspan = (0.0, 20.0) From ffaf651e8b051c6db8d27717f5c51b3db4858c74 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Tue, 2 Jul 2024 00:43:48 -0400 Subject: [PATCH 373/446] Update docs/src/index.md Co-authored-by: Sam Isaacson --- docs/src/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/index.md b/docs/src/index.md index aab0af8c8f..5d06c0c11c 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -221,7 +221,7 @@ dGᴾ(t) &= \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) - \frac{kᵢ}{V(t)} Gᴾ(t) dV(t) &= \left(g \cdot Gᴾ(t)\right) dt \end{align*} ``` -where the $dW_1(t)$ and $dW_2(t)$ terms described the noise added through the Chemical Langevin Equations. Finally, we can simulate and plot the results. +where the $dW_1(t)$ and $dW_2(t)$ terms represent independent Brownian Motions, encoding the noise added by the Chemical Langevin Equation. Finally, we can simulate and plot the results. ```@example home_elaborate_example using StochasticDiffEq sol = solve(sprob, EM(); dt = 0.05) From 4380871e3a0c32705dfaa6b5040ecefd5796c406 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Jul 2024 00:50:38 -0400 Subject: [PATCH 374/446] sde example update --- docs/src/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 5d06c0c11c..045e17fb5a 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -210,7 +210,7 @@ We now study the system as a Chemical Langevin Dynamics SDE model, which can be ```@example home_elaborate_example u0 = [:V => 25.0, :G => 50.0, :Gᴾ => 0.0] tspan = (0.0, 20.0) -ps = [:Vₘ => 50.0, :g => 0.2, :kₚ => 100.0, :kᵢ => 60.0] +ps = [:Vₘ => 50.0, :g => 0.3, :kₚ => 100.0, :kᵢ => 60.0] sprob = SDEProblem(cell_model, u0, tspan, ps) ``` This produces the following equations: @@ -221,7 +221,7 @@ dGᴾ(t) &= \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) - \frac{kᵢ}{V(t)} Gᴾ(t) dV(t) &= \left(g \cdot Gᴾ(t)\right) dt \end{align*} ``` -where the $dW_1(t)$ and $dW_2(t)$ terms represent independent Brownian Motions, encoding the noise added by the Chemical Langevin Equation. Finally, we can simulate and plot the results. +where the $dW_1(t)$ and $dW_2(t)$ terms represent independent Brownian Motions, encoding the noise added by the Chemical Langevin Equation. Finally, we can simulate and plot the results. ```@example home_elaborate_example using StochasticDiffEq sol = solve(sprob, EM(); dt = 0.05) From 730fa6a0d73110111235e468ff6ff00fc9644c5e Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Jul 2024 00:53:16 -0400 Subject: [PATCH 375/446] update sde example --- README.md | 2 +- docs/src/assets/readme_elaborate_sde_plot.svg | 74 +++++++++---------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 13693b09cc..91ed58e007 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ In this case, we would instead like to perform stochastic simulations, so we tra ```julia u0 = [:V => 25.0, :G => 50.0, :Gᴾ => 0.0] tspan = (0.0, 20.0) -ps = [:Vₘ => 50.0, :g => 0.2, :kₚ => 100.0, :kᵢ => 60.0] +ps = [:Vₘ => 50.0, :g => 0.3, :kₚ => 100.0, :kᵢ => 60.0] sprob = SDEProblem(cell_model, u0, tspan, ps) ``` This produces the following equations: diff --git a/docs/src/assets/readme_elaborate_sde_plot.svg b/docs/src/assets/readme_elaborate_sde_plot.svg index a906bbbfc9..503e76d2ee 100644 --- a/docs/src/assets/readme_elaborate_sde_plot.svg +++ b/docs/src/assets/readme_elaborate_sde_plot.svg @@ -1,50 +1,50 @@ - + - + - + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 35f0aec30c27d8cfd88f0a16ececaa9c77913641 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Jul 2024 02:13:08 -0400 Subject: [PATCH 376/446] up --- docs/src/api.md | 2 +- docs/src/index.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/src/api.md b/docs/src/api.md index f34cbfa8e3..67842c676e 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -1,4 +1,4 @@ -# Catalyst.jl API +# [Catalyst.jl API](@id api) ```@meta CurrentModule = Catalyst ``` diff --git a/docs/src/index.md b/docs/src/index.md index 045e17fb5a..05bc6f029a 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -104,22 +104,23 @@ New users are recommended to start with either the [Introduction to Catalyst and This documentation contains code which is dynamically run whenever it is built. If you copy the code and run it in your Julia environment it should work. The exact Julia environment that is used in this documentation can be found [here](@ref doc_home_reproducibility). For most code blocks in this documentation, the output of the last line of code is printed at the of the block, e.g. -```@example home1 +```@example home_display 1 + 2 ``` and -```@example home1 +```@example home_display +using Catalyst # hide @reaction_network begin (p,d), 0 <--> X end ``` However, in some situations (e.g. when output is extensive, or irrelevant to what is currently being described) we have disabled this, e.g. like here: -```@example home1 +```@example home_display 1 + 2 nothing # hide ``` and here: -```@example home1 +```@example home_display @reaction_network begin (p,d), 0 <--> X end From 179d251701e09b0b3b89fce3b369cd9d894e27b0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Jul 2024 02:15:24 -0400 Subject: [PATCH 377/446] where the $dW_1(t)$ and $dW_2(t)$ terms described the noise added through the Chemical Langevin Equations. Finally, we can simulate and plot the results.transfer text updates from home pr --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 91ed58e007..a8137cb799 100644 --- a/README.md +++ b/README.md @@ -160,11 +160,10 @@ plot(sol; lw = 5) ![ODE simulation](docs/src/assets/readme_ode_plot.svg) #### Stochastic jump simulations -The same model can be used as input to other types of simulations. E.g. here we instead perform a -jump simulation +The same model can be used as input to other types of simulations. E.g. here we instead generate and simulate a stochastic chemical kinetics jump process model. ```julia # Create and simulate a jump process (here using Gillespie's direct algorithm). -# Note that integer (not decimal) initial conditions are used. +# The initial conditions are now integers as we track exact populations for each species. using JumpProcesses u0_integers = [:S => 50, :E => 10, :SE => 0, :P => 0] dprob = DiscreteProblem(model, u0_integers, tspan, ps) @@ -197,7 +196,7 @@ cell_model = @reaction_network begin kᵢ/V, Gᴾ --> G end ``` -In this case, we would instead like to perform stochastic simulations, so we transform our model to an SDE: +We now study the system as a Chemical Langevin Dynamics SDE model, which can be generated as follows ```julia u0 = [:V => 25.0, :G => 50.0, :Gᴾ => 0.0] tspan = (0.0, 20.0) @@ -212,7 +211,7 @@ dGᴾ(t) &= \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) - \frac{kᵢ}{V(t)} Gᴾ(t) dV(t) &= \left(g \cdot Gᴾ(t)\right) dt \end{align*} ``` -where the $dW_1(t)$ and $dW_2(t)$ terms described the noise added through the Chemical Langevin Equations. Finally, we can simulate and plot the results. +where the $dW_1(t)$ and $dW_2(t)$ terms represent independent Brownian Motions, encoding the noise added by the Chemical Langevin Equation. Finally, we can simulate and plot the results. ```julia using StochasticDiffEq sol = solve(sprob, EM(); dt = 0.05) From 9f64cff9071f00137f4e13d782a97cb80d8b8345 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Jul 2024 02:18:31 -0400 Subject: [PATCH 378/446] minor rreference fixes --- docs/src/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 05bc6f029a..cedc37bd76 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -80,7 +80,7 @@ etc). - [SciMLSensitivity.jl](https://github.com/SciML/SciMLSensitivity.jl) can be used to compute local sensitivities of functions containing forward model simulations. #### [Features of packages built upon Catalyst](@id doc_index_features_other_packages) -- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@id model_file_import_export_sbml) via +- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref model_file_import_export_sbml) via [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), and [from BioNetGen .net files](@ref model_file_import_export_sbml_rni_net) and various stoichiometric matrix network representations using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). @@ -101,7 +101,7 @@ The Catalyst documentation is separated into sections describing Catalyst's vari New users are recommended to start with either the [Introduction to Catalyst and Julia for New Julia users](@ref catalyst_for_new_julia_users) or [Introduction to Catalyst](@ref introduction_to_catalyst) sections (depending on whether they are familiar with Julia programming or not). This should be enough to carry out many basic Catalyst workflows. -This documentation contains code which is dynamically run whenever it is built. If you copy the code and run it in your Julia environment it should work. The exact Julia environment that is used in this documentation can be found [here](@ref doc_home_reproducibility). +This documentation contains code which is dynamically run whenever it is built. If you copy the code and run it in your Julia environment it should work. The exact Julia environment that is used in this documentation can be found [here](@ref doc_index_reproducibility). For most code blocks in this documentation, the output of the last line of code is printed at the of the block, e.g. ```@example home_display @@ -224,7 +224,7 @@ dV(t) &= \left(g \cdot Gᴾ(t)\right) dt ``` where the $dW_1(t)$ and $dW_2(t)$ terms represent independent Brownian Motions, encoding the noise added by the Chemical Langevin Equation. Finally, we can simulate and plot the results. ```@example home_elaborate_example -using StochasticDiffEq +using StochasticDiffEq, Plots sol = solve(sprob, EM(); dt = 0.05) sol = solve(sprob, EM(); dt = 0.05, seed = 1234) # hide plot(sol; xguide = "Time (au)", lw = 2) From 9c203cc3ccdb16dced5d3ad60b07e01fa028b461 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Jul 2024 02:19:14 -0400 Subject: [PATCH 379/446] add using Plots in SDE example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8137cb799..705d689bf8 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,7 @@ dV(t) &= \left(g \cdot Gᴾ(t)\right) dt ``` where the $dW_1(t)$ and $dW_2(t)$ terms represent independent Brownian Motions, encoding the noise added by the Chemical Langevin Equation. Finally, we can simulate and plot the results. ```julia -using StochasticDiffEq +using StochasticDiffEq, Plots sol = solve(sprob, EM(); dt = 0.05) plot(sol; xguide = "Time (au)", lw = 2) ``` From 0bf0c78b7bfedf113b3ba167ed61e0eda656fb24 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Jul 2024 02:39:43 -0400 Subject: [PATCH 380/446] minor fix after re reading --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 705d689bf8..4017eeda1d 100644 --- a/README.md +++ b/README.md @@ -32,18 +32,15 @@ etc). ## Breaking changes and new features -**NOTE:** Version 14 is a breaking release, prompted by the release of ModelingToolkit.jl version 9. -This caused several breaking changes in how Catalyst models are represented and interfaced with. +**NOTE:** Version 14 is a breaking release, prompted by the release of ModelingToolkit.jl version 9. This caused several breaking changes in how Catalyst models are represented and interfaced with. -Breaking changes and new functionality are summarized in the -[HISTORY.md](HISTORY.md) file. Furthermore, a migration guide on how to adapt your workflows to the -the new v14 update can be found [here](https://docs.sciml.ai/Catalyst/stable/v14_migration_guide/). +Breaking changes and new functionality are summarized in the [HISTORY.md](HISTORY.md) file. Furthermore, a migration guide on how to adapt your workflows to the new v14 update can be found [here](https://docs.sciml.ai/Catalyst/stable/v14_migration_guide/). ## Tutorials and documentation The latest tutorials and information on using Catalyst are available in the [stable documentation](https://docs.sciml.ai/Catalyst/stable/). The [in-development -documentation](https://docs.sciml.ai/Catalyst/stable/) describes unreleased features in +documentation](https://docs.sciml.ai/Catalyst/dev/) describes unreleased features in the current master branch. An overview of the package, its features, and comparative benchmarking (as of version 13) can also From 7c0e546453ca86224ae8a02e6444f00aec4582ab Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Jul 2024 12:01:22 -0400 Subject: [PATCH 381/446] cross reference fix --- docs/src/introduction_to_catalyst/introduction_to_catalyst.md | 2 +- docs/src/model_creation/programmatic_CRN_construction.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index 3fe15232c8..64f3819dc8 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -44,7 +44,7 @@ listed chemical species and unknowns. [`@reaction_network`](@ref) returns a [`ReactionSystem`](@ref), which we saved in the `repressilator` variable. It can be converted to a variety of other mathematical models represented as `ModelingToolkit.AbstractSystem`s, or analyzed in various ways using the -[Catalyst.jl API](@ref). For example, to see the chemical species, parameters, +[Catalyst.jl API](@ref api). For example, to see the chemical species, parameters, and reactions we can use ```@example tut1 species(repressilator) diff --git a/docs/src/model_creation/programmatic_CRN_construction.md b/docs/src/model_creation/programmatic_CRN_construction.md index a8e7f15d86..5232db3886 100644 --- a/docs/src/model_creation/programmatic_CRN_construction.md +++ b/docs/src/model_creation/programmatic_CRN_construction.md @@ -180,7 +180,7 @@ This ensured they were properly treated as species and not parameters. See the ## Basic querying of `ReactionSystems` -The [Catalyst.jl API](@ref) provides a large variety of functionality for +The [Catalyst.jl API](@ref api) provides a large variety of functionality for querying properties of a reaction network. Here we go over a few of the most useful basic functions. Given the `repressillator` defined above we have that ```@example ex @@ -247,5 +247,5 @@ rx1.prodstoich rx1.netstoich ``` -See the [Catalyst.jl API](@ref) for much more detail on the various querying and +See the [Catalyst.jl API](@ref api) for much more detail on the various querying and analysis functions provided by Catalyst. From 2eba4214f20131174ee3fd0a40dde4a6ce8b1885 Mon Sep 17 00:00:00 2001 From: spaette Date: Thu, 4 Jul 2024 15:30:07 -0500 Subject: [PATCH 382/446] markdown typos --- HISTORY.md | 4 ++-- docs/old_files/advanced.md | 2 +- docs/src/api.md | 2 +- docs/src/model_creation/model_file_loading_and_export.md | 2 +- docs/src/model_creation/network_analysis.md | 2 +- docs/src/v14_migration_guide.md | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 2ee70fc0b2..753403e7af 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -688,7 +688,7 @@ hc_steady_states(wilhelm_2009_model, ps) field has been changed (only when created through the `@reaction_network` macro). Previously they were ordered according to the order with which they appeared in the macro. Now they are ordered according the to order with which - they appeard after the `end` part. E.g. in + they appeared after the `end` part. E.g. in ```julia rn = @reaction_network begin (p,d), 0 <--> X @@ -793,7 +793,7 @@ which gives ![rn_complexes](https://user-images.githubusercontent.com/9385167/130252763-4418ba5a-164f-47f7-b512-a768e4f73834.png) *2.* Support for units via ModelingToolkit and -[Uniftul.jl](https://github.com/PainterQubits/Unitful.jl) in directly constructed +[Unitful.jl](https://github.com/PainterQubits/Unitful.jl) in directly constructed `ReactionSystem`s: ```julia # ]add Unitful diff --git a/docs/old_files/advanced.md b/docs/old_files/advanced.md index 2888749744..80388d12bb 100644 --- a/docs/old_files/advanced.md +++ b/docs/old_files/advanced.md @@ -25,7 +25,7 @@ end ``` occurs at the rate ``d[X]/dt = -k[X]``, it is possible to ignore this by using any of the following non-filled arrows when declaring the reaction: `<=`, `⇐`, `⟽`, -`⇒`, `⟾`, `=>`, `⇔`, `⟺` (`<=>` currently not possible due to Julia langauge technical reasons). This means that the reaction +`⇒`, `⟾`, `=>`, `⇔`, `⟺` (`<=>` currently not possible due to Julia language technical reasons). This means that the reaction ```julia rn = @reaction_network begin diff --git a/docs/src/api.md b/docs/src/api.md index 67842c676e..8a5e543973 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -127,7 +127,7 @@ can call: * `ModelingToolkit.unknowns(rn)` returns all species *and variables* across the system, *all sub-systems*, and all constraint systems. Species are ordered before non-species variables in `unknowns(rn)`, with the first `numspecies(rn)` - entires in `unknowns(rn)` being the same as `species(rn)`. + entries in `unknowns(rn)` being the same as `species(rn)`. * [`species(rn)`](@ref) is a vector collecting all the chemical species within the system and any sub-systems that are also `ReactionSystems`. * `ModelingToolkit.parameters(rn)` returns all parameters across the diff --git a/docs/src/model_creation/model_file_loading_and_export.md b/docs/src/model_creation/model_file_loading_and_export.md index 43b6d16f22..873fbb13ff 100644 --- a/docs/src/model_creation/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -1,5 +1,5 @@ # [Loading Chemical Reaction Network Models from Files](@id model_file_import_export) -Catalyst stores chemical reaction network (CRN) models in `ReactionSystem` structures. This tutorial describes how to load such `ReactionSystem`s from, and save them to, files. This can be used to save models between Julia sessions, or transfer them from one session to another. Furthermore, to facilitate the computation modelling of CRNs, several standardised file formats have been created to represent CRN models (e.g. [SBML](https://sbml.org/)). This enables CRN models to be shared between different softwares and programming languages. While Catalyst itself does not have the functionality for loading such files, we will here (briefly) introduce a few packages that can load different file types to Catalyst `ReactionSystem`s. +Catalyst stores chemical reaction network (CRN) models in `ReactionSystem` structures. This tutorial describes how to load such `ReactionSystem`s from, and save them to, files. This can be used to save models between Julia sessions, or transfer them from one session to another. Furthermore, to facilitate the computation modelling of CRNs, several standardised file formats have been created to represent CRN models (e.g. [SBML](https://sbml.org/)). This enables CRN models to be shared between different software and programming languages. While Catalyst itself does not have the functionality for loading such files, we will here (briefly) introduce a few packages that can load different file types to Catalyst `ReactionSystem`s. ## [Saving Catalyst models to, and loading them from, Julia files](@id model_file_import_export_crn_serialization) Catalyst provides a `save_reactionsystem` function, enabling the user to save a `ReactionSystem` to a file. Here we demonstrate this by first creating a [simple cross-coupling model](@ref basic_CRN_library_cc): diff --git a/docs/src/model_creation/network_analysis.md b/docs/src/model_creation/network_analysis.md index 792ed5477d..f1cf33efc9 100644 --- a/docs/src/model_creation/network_analysis.md +++ b/docs/src/model_creation/network_analysis.md @@ -364,7 +364,7 @@ complexgraph(rn) It is evident from the preceding graph that the network is not reversible. However, it satisfies a weaker property in that there is a path from each reaction complex back to itself within its associated subgraph. This is known as -*weak reversiblity*. One can test a network for weak reversibility by using +*weak reversibility*. One can test a network for weak reversibility by using the [`isweaklyreversible`](@ref) function: ```@example s1 # need subnetworks from the reaction network first diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index 176ec89dd1..ed47a2a439 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -96,7 +96,7 @@ nonspecies(rs) ``` ## Lost support for most units -As part of its v9 update, ModelingToolkit changed how units were handled. This includes using the package [DynamicQuantities.jl](https://github.com/SymbolicML/DynamicQuantities.jl) to manage units (instead of [Uniful.jl](https://github.com/PainterQubits/Unitful.jl), like previously). +As part of its v9 update, ModelingToolkit changed how units were handled. This includes using the package [DynamicQuantities.jl](https://github.com/SymbolicML/DynamicQuantities.jl) to manage units (instead of [Unitful.jl](https://github.com/PainterQubits/Unitful.jl), like previously). While this should lead to long-term improvements, unfortunately, as part of the process support for most units was removed. Currently, only the main SI units are supported (`s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`). Composite units (e.g. `N = kg/(m^2)`) are no longer supported. Furthermore, prefix units (e.g. `mm = m/1000`) are not supported either. This means that most units relevant to Catalyst (such as `µM`) cannot be used directly. While composite units can still be written out in full and used (e.g. `kg/(m^2)`) this is hardly user-friendly. From b777f52f18c49c48459e720de00905ade23b46a0 Mon Sep 17 00:00:00 2001 From: spaette Date: Fri, 5 Jul 2024 16:46:52 -0500 Subject: [PATCH 383/446] typos --- src/dsl.jl | 34 +++++++++---------- src/expression_utils.jl | 4 +-- src/network_analysis.jl | 2 +- src/reaction.jl | 12 +++---- src/reactionsystem.jl | 34 +++++++++---------- src/reactionsystem_conversions.jl | 10 +++--- .../lattice_jump_systems.jl | 2 +- test/miscellaneous_tests/api.jl | 2 +- test/network_analysis/conservation_laws.jl | 2 +- test/reactionsystem_core/reactionsystem.jl | 2 +- test/test_networks.jl | 2 +- 11 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/dsl.jl b/src/dsl.jl index 2107179dab..633ea7f64b 100644 --- a/src/dsl.jl +++ b/src/dsl.jl @@ -269,7 +269,7 @@ function processmult(op, mult, stoich) end end -# Finds the metadata from a metadata expresion (`[key=val, ...]`) and returns as a Vector{Pair{Symbol,ExprValues}}. +# Finds the metadata from a metadata expression (`[key=val, ...]`) and returns as a Vector{Pair{Symbol,ExprValues}}. function extract_metadata(metadata_line::Expr) metadata = :([]) for arg in metadata_line.args @@ -471,7 +471,7 @@ function push_reactions!(reactions::Vector{ReactionStruct}, sub_line::ExprValues push!(metadata_i.args, :(only_use_rate = $(in(arrow, pure_rate_arrows)))) end - # Checks that metadata fields are unqiue. + # Checks that metadata fields are unique. if !allunique(arg.args[1] for arg in metadata_i.args) error("Some reaction metadata fields where repeated: $(metadata_entries)") end @@ -602,7 +602,7 @@ end # default metadata value to the `default_reaction_metadata` vector. function check_default_noise_scaling!(default_reaction_metadata, options) if haskey(options, :default_noise_scaling) - if (length(options[:default_noise_scaling].args) != 3) # Becasue of how expressions are, 3 is used. + if (length(options[:default_noise_scaling].args) != 3) # Because of how expressions are, 3 is used. error("@default_noise_scaling should only have a single input, this appears not to be the case: \"$(options[:default_noise_scaling])\"") end push!(default_reaction_metadata.args, @@ -612,7 +612,7 @@ end # When compound species are declared using the "@compound begin ... end" option, get a list of the compound species, and also the expression that crates them. function read_compound_options(opts) - # If the compound option is used retrive a list of compound species (need to be added to the reaction system's species), and the option that creates them (used to declare them as compounds at the end). + # If the compound option is used retrieve a list of compound species (need to be added to the reaction system's species), and the option that creates them (used to declare them as compounds at the end). if haskey(opts, :compounds) compound_expr = opts[:compounds] # Find compound species names, and append the independent variable. @@ -625,7 +625,7 @@ function read_compound_options(opts) return compound_expr, compound_species end -# Read the events (continious or discrete) provided as options to the DSL. Returns an expression which evalutes to these. +# Read the events (continuous or discrete) provided as options to the DSL. Returns an expression which evaluates to these. function read_events_option(options, event_type::Symbol) # Prepares the events, if required to, converts them to block form. if event_type ∉ [:continuous_events, :discrete_events] @@ -635,7 +635,7 @@ function read_events_option(options, event_type::Symbol) MacroTools.striplines(:(begin end)) events_input = option_block_form(events_input) - # Goes throgh the events, checks for errors, and adds them to the output vector. + # Goes through the events, checks for errors, and adds them to the output vector. events_expr = :([]) for arg in events_input.args # Formatting error checks. @@ -646,7 +646,7 @@ function read_events_option(options, event_type::Symbol) end if (arg isa Expr) && (arg.args[2] isa Expr) && (arg.args[2].head != :vect) && (event_type == :continuous_events) - error("The condition part of continious events (the left-hand side) must be a vector. This is not the case for: $(arg).") + error("The condition part of continuous events (the left-hand side) must be a vector. This is not the case for: $(arg).") end if (arg isa Expr) && (arg.args[3] isa Expr) && (arg.args[3].head != :vect) error("The affect part of all events (the righ-hand side) must be a vector. This is not the case for: $(arg).") @@ -661,10 +661,10 @@ end # Reads the variables options. Outputs: # `vars_extracted`: A vector with extracted variables (lhs in pure differential equations only). -# `dtexpr`: If a differentialequation is defined, the default derrivative (D ~ Differential(t)) must be defined. +# `dtexpr`: If a differential equation is defined, the default derivative (D ~ Differential(t)) must be defined. # `equations`: a vector with the equations provided. function read_equations_options(options, variables_declared) - # Prepares the equations. First, extracts equations from provided option (converting to block form if requried). + # Prepares the equations. First, extracts equations from provided option (converting to block form if required). # Next, uses MTK's `parse_equations!` function to split input into a vector with the equations. eqs_input = haskey(options, :equations) ? options[:equations].args[3] : :(begin end) eqs_input = option_block_form(eqs_input) @@ -718,12 +718,12 @@ function create_differential_expr(options, add_default_diff, used_syms, tiv) (dexpr.args[1] isa Symbol) || error("Differential left-hand side must be a single symbol, instead \"$(dexpr.args[1])\" was given.") in(dexpr.args[1], used_syms) && - error("Differential name ($(dexpr.args[1])) is also a species, variable, or parameter. This is ambigious and not allowed.") + error("Differential name ($(dexpr.args[1])) is also a species, variable, or parameter. This is ambiguous and not allowed.") in(dexpr.args[1], forbidden_symbols_error) && error("A forbidden symbol ($(dexpr.args[1])) was used as a differential name.") end - # If the default differential D has been used, but not pre-declared using the @differenitals + # If the default differential D has been used, but not pre-declared using the @differentials # options, add this declaration to the list of declared differentials. if add_default_diff && !any(diff_dec.args[1] == :D for diff_dec in diffexpr.args) push!(diffexpr.args, :(D = Differential($(tiv)))) @@ -732,11 +732,11 @@ function create_differential_expr(options, add_default_diff, used_syms, tiv) return diffexpr end -# Reads the observables options. Outputs an expression ofr creating the obervable variables, and a vector of observable equations. +# Reads the observables options. Outputs an expression ofr creating the observable variables, and a vector of observable equations. function read_observed_options(options, species_n_vars_declared, ivs_sorted) if haskey(options, :observables) # Gets list of observable equations and prepares variable declaration expression. - # (`options[:observables]` inlucdes `@observables`, `.args[3]` removes this part) + # (`options[:observables]` includes `@observables`, `.args[3]` removes this part) observed_eqs = make_observed_eqs(options[:observables].args[3]) observed_vars = Expr(:block, :(@variables)) obs_syms = :([]) @@ -753,11 +753,11 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted) # Error checks. if (obs_name in species_n_vars_declared) && is_escaped_expr(obs_eq.args[2]) - error("An interpoalted observable have been used, which has also been explicitly delcared within the system using eitehr @species or @variables. This is not permited.") + error("An interpolated observable have been used, which has also been explicitly declared within the system using either @species or @variables. This is not permitted.") end if ((obs_name in species_n_vars_declared) || is_escaped_expr(obs_eq.args[2])) && !isnothing(metadata) - error("Metadata was provided to observable $obs_name in the `@observables` macro. However, the obervable was also declared separately (using either @species or @variables). When this is done, metadata should instead be provided within the original @species or @variable declaration.") + error("Metadata was provided to observable $obs_name in the `@observables` macro. However, the observable was also declared separately (using either @species or @variables). When this is done, metadata should instead be provided within the original @species or @variable declaration.") end # This bits adds the observables to the @variables vector which is given as output. @@ -789,7 +789,7 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted) # Adds the observable to the list of observable names. # This is required for filtering away so these are not added to the ReactionSystem's species list. - # Again, avoid this check if we have interpoalted the variable. + # Again, avoid this check if we have interpolated the variable. is_escaped_expr(obs_eq.args[2]) || push!(obs_syms.args, obs_name) end @@ -805,7 +805,7 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted) end # From the input to the @observables options, creates a vector containing one equation for each observable. -# Checks separate cases for "@obervables O ~ ..." and "@obervables begin ... end". Other cases errors. +# Checks separate cases for "@observables O ~ ..." and "@observables begin ... end". Other cases errors. function make_observed_eqs(observables_expr) if observables_expr.head == :call return :([$(observables_expr)]) diff --git a/src/expression_utils.jl b/src/expression_utils.jl index e1e9d0fe79..c1faa8ca8c 100644 --- a/src/expression_utils.jl +++ b/src/expression_utils.jl @@ -27,7 +27,7 @@ function forbidden_symbol_check(v) error("The following symbol(s) are used as species or parameters: " * ((map(s -> "'" * string(s) * "', ", intersect(forbidden_symbols_error, v))...)) * - "this is not permited.") + "this is not permitted.") nothing end @@ -37,7 +37,7 @@ function forbidden_variable_check(v) error("The following symbol(s) are used as variables: " * ((map(s -> "'" * string(s) * "', ", intersect(forbidden_variables_error, v))...)) * - "this is not permited.") + "this is not permitted.") end function unique_symbol_check(syms) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index f60bfcbc4a..81208e1bf7 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -709,7 +709,7 @@ conservedquantities(state, cons_laws) = cons_laws * state # If u0s are not given while conservation laws are present, throws an error. # Used in HomotopyContinuation and BifurcationKit extensions. # Currently only checks if any u0s are given -# (not whether these are enough for computing conserved quantitites, this will yield a less informative error). +# (not whether these are enough for computing conserved quantities, this will yield a less informative error). function conservationlaw_errorcheck(rs, pre_varmap) vars_with_vals = Set(p[1] for p in pre_varmap) any(s -> s in vars_with_vals, species(rs)) && return diff --git a/src/reaction.jl b/src/reaction.jl index 592e448613..165bbeff37 100644 --- a/src/reaction.jl +++ b/src/reaction.jl @@ -445,10 +445,10 @@ end """ getmetadata_dict(reaction::Reaction) -Retrives the `ImmutableDict` containing all of the metadata associated with a specific reaction. +Retrieves the `ImmutableDict` containing all of the metadata associated with a specific reaction. Arguments: -- `reaction`: The reaction for which we wish to retrive all metadata. +- `reaction`: The reaction for which we wish to retrieve all metadata. Example: ```julia @@ -482,11 +482,11 @@ end """ getmetadata(reaction::Reaction, md_key::Symbol) -Retrives a certain metadata value from a `Reaction`. If the metadata does not exists, throws an error. +Retrieves a certain metadata value from a `Reaction`. If the metadata does not exist, throws an error. Arguments: -- `reaction`: The reaction for which we wish to retrive a specific metadata value. -- `md_key`: The metadata for which we wish to retrive. +- `reaction`: The reaction for which we wish to retrieve a specific metadata value. +- `md_key`: The metadata for which we wish to retrieve. Example: ```julia @@ -622,7 +622,7 @@ getmisc(reaction) Notes: - The `misc` field can contain any valid Julia structure. This mean that Catalyst cannot check it -for symbolci variables that are added here. This means that symbolic variables (e.g. parameters of +for symbolic variables that are added here. This means that symbolic variables (e.g. parameters of species) that are stored here are not accessible to Catalyst. This can cause troubles when e.g. creating a `ReactionSystem` programmatically (in which case any symbolic variables stored in the `misc` metadata field should also be explicitly provided to the `ReactionSystem` constructor). diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index a84d41c522..b504710eee 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -195,7 +195,7 @@ end function find_event_vars!(ps, us, events::Vector, ivs, vars) foreach(event -> find_event_vars!(ps, us, event, ivs, vars), events) end -# For a single event, adds quantitites from its condition and affect expression(s) to `ps` and `us`. +# For a single event, adds quantities from its condition and affect expression(s) to `ps` and `us`. # Applies `findvars!` to the event's condition (`event[1])` and affec (`event[2]`). function find_event_vars!(ps, us, event, ivs, vars) findvars!(ps, us, event[1], ivs, vars) @@ -406,7 +406,7 @@ function ReactionSystem(eqs, iv, unknowns, ps; error("Catalyst reserves the symbols $forbidden_symbols_error for internal use. Please do not use these symbols as parameters or unknowns/species.") end - # Handles reactions and equations. Sorts so that reactions are before equaions in the equations vector. + # Handles reactions and equations. Sorts so that reactions are before equations in the equations vector. eqs′ = CatalystEqType[eq for eq in eqs] sort!(eqs′; by = eqsortby) rxs = Reaction[rx for rx in eqs if rx isa Reaction] @@ -443,7 +443,7 @@ function ReactionSystem(eqs, iv, unknowns, ps; networkproperties end - # Creates the continious and discrete callbacks. + # Creates the continuous and discrete callbacks. ccallbacks = MT.SymbolicContinuousCallbacks(continuous_events) dcallbacks = MT.SymbolicDiscreteCallbacks(discrete_events) @@ -464,7 +464,7 @@ function ReactionSystem(iv; kwargs...) ReactionSystem(Reaction[], iv, [], []; kwargs...) end -# Called internally (whether DSL-based or programmtic model creation is used). +# Called internally (whether DSL-based or programmatic model creation is used). # Creates a sorted reactions + equations vector, also ensuring reaction is first in this vector. # Extracts potential species, variables, and parameters from the input (if not provided as part of # the model creation) and creates the corresponding vectors. @@ -474,12 +474,12 @@ function make_ReactionSystem_internal(rxs_and_eqs::Vector, iv, us_in, ps_in; spatial_ivs = nothing, continuous_events = [], discrete_events = [], observed = [], kwargs...) - # Filters away any potential obervables from `states` and `spcs`. + # Filters away any potential observables from `states` and `spcs`. obs_vars = [obs_eq.lhs for obs_eq in observed] us_in = filter(u -> !any(isequal(u, obs_var) for obs_var in obs_vars), us_in) # Creates a combined iv vector (iv and sivs). This is used later in the function (so that - # independent variables can be exluded when encountered quantities are added to `us` and `ps`). + # independent variables can be excluded when encountered quantities are added to `us` and `ps`). t = value(iv) ivs = Set([t]) if (spatial_ivs !== nothing) @@ -501,17 +501,17 @@ function make_ReactionSystem_internal(rxs_and_eqs::Vector, iv, us_in, ps_in; # Loops through all reactions, adding encountered quantities to the unknown and parameter vectors. # Starts by looping through substrates + products only (so these are added to the vector first). - # Next, the otehr components of reactions (e.g. rates and stoichiometries) are added. + # Next, the other components of reactions (e.g. rates and stoichiometries) are added. for rx in rxs for reactants in (rx.substrates, rx.products), spec in reactants MT.isparameter(spec) ? push!(ps, spec) : push!(us, spec) end end for rx in rxs - # Adds all quantitites encountered in the reaction's rate. + # Adds all quantities encountered in the reaction's rate. findvars!(ps, us, rx.rate, ivs, vars) - # Extracts all quantitites encountered within stoichiometries. + # Extracts all quantities encountered within stoichiometries. for stoichiometry in (rx.substoich, rx.prodstoich), sym in stoichiometry (sym isa Symbolic) && findvars!(ps, us, sym, ivs, vars) end @@ -531,7 +531,7 @@ function make_ReactionSystem_internal(rxs_and_eqs::Vector, iv, us_in, ps_in; fulleqs = rxs end - # Loops through all events, adding encountered quantities to the unknwon and parameter vectors. + # Loops through all events, adding encountered quantities to the unknown and parameter vectors. find_event_vars!(ps, us, continuous_events, ivs, vars) find_event_vars!(ps, us, discrete_events, ivs, vars) @@ -633,7 +633,7 @@ get_networkproperties(sys::ReactionSystem) = getfield(sys, :networkproperties) Returns true if the default for the system is to rescale ratelaws, see https://docs.sciml.ai/Catalyst/stable/introduction_to_catalyst/introduction_to_catalyst/#Reaction-rate-laws-used-in-simulations -for details. Can be overriden via passing `combinatoric_ratelaws` to `convert` or the +for details. Can be overridden via passing `combinatoric_ratelaws` to `convert` or the `*Problem` functions. """ get_combinatoric_ratelaws(sys::ReactionSystem) = getfield(sys, :combinatoric_ratelaws) @@ -642,7 +642,7 @@ get_combinatoric_ratelaws(sys::ReactionSystem) = getfield(sys, :combinatoric_rat combinatoric_ratelaws(sys::ReactionSystem) Returns the effective (default) `combinatoric_ratelaw` value for a compositional system, -calculated by taking the logical or of each component `ReactionSystem`. Can be overriden +calculated by taking the logical or of each component `ReactionSystem`. Can be overridden during calls to `convert` of problem constructors. """ function combinatoric_ratelaws(sys::ReactionSystem) @@ -805,7 +805,7 @@ end """ nonreactions(network) -Return the non-reaction equations within the network (i.e. algebraic and differnetial equations). +Return the non-reaction equations within the network (i.e. algebraic and differential equations). Notes: - Allocates a new array to store the non-species variables. @@ -833,7 +833,7 @@ isspatial(rn::ReactionSystem) = !isempty(get_sivs(rn)) ### ModelingToolkit Function Dispatches ### -# Retrives events. +# Retrieves events. MT.get_continuous_events(sys::ReactionSystem) = getfield(sys, :continuous_events) # `MT.get_discrete_events(sys::ReactionSystem) = getfield(sys, :get_discrete_events)` should be added here. @@ -1044,10 +1044,10 @@ end # Checks if the `ReactionSystem` structure have been updated without also updating the # `reactionsystem_fields` constant. If this is the case, returns `false`. This is used in # certain functionalities which would break if the `ReactionSystem` structure is updated without -# also updating tehse functionalities. +# also updating these functionalities. function reactionsystem_uptodate_check() if fieldnames(ReactionSystem) != reactionsystem_fields - @warn "The `ReactionSystem` strcuture have been modified without this being taken into account in the functionality you are attempting to use. Please report this at https://github.com/SciML/Catalyst.jl/issues. Proceed with cautioun, as there might be errors in whichever funcionality you are attempting to use." + @warn "The `ReactionSystem` structure have been modified without this being taken into account in the functionality you are attempting to use. Please report this at https://github.com/SciML/Catalyst.jl/issues. Proceed with caution, as there might be errors in whichever functionality you are attempting to use." end end @@ -1230,7 +1230,7 @@ default reaction metadata is currently the only supported feature. Arguments: - `rs::ReactionSystem`: The `ReactionSystem` which you wish to remake. - `default_reaction_metadata::Vector{Pair{Symbol, T}}`: A vector with default `Reaction` metadata values. - Each metadata in each `Reaction` of the updated `ReactionSystem` will have the value desiganted in + Each metadata in each `Reaction` of the updated `ReactionSystem` will have the value designated in `default_reaction_metadata` (however, `Reaction`s that already have that metadata designated will not have their value updated). """ diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 6f7b2255ac..54e43f42dd 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -433,7 +433,7 @@ end ### Utility ### -# Throws an error when attempting to convert a spatial system to an unssuported type. +# Throws an error when attempting to convert a spatial system to an unsupported type. function spatial_convert_err(rs::ReactionSystem, systype) isspatial(rs) && error("Conversion to $systype is not supported for spatial networks.") end @@ -450,7 +450,7 @@ diff_2_zero(expr) = (Symbolics.is_derivative(expr) ? 0 : expr) COMPLETENESS_ERROR = "A ReactionSystem must be complete before it can be converted to other system types. A ReactionSystem can be marked as complete using the `complete` function." -# Used to, when required, display a warning about conservation law removeal and remake. +# Used to, when required, display a warning about conservation law removal and remake. function check_cons_warning(remove_conserved, remove_conserved_warn) (remove_conserved && remove_conserved_warn) || return @warn "You are creating a system or problem while eliminating conserved quantities. Please note, @@ -570,7 +570,7 @@ end # Ideally, when `ReactionSystem`s are converted to `NonlinearSystem`s, any coupled ODEs should be # on the form D(X) ~ ..., where lhs is the time derivative w.r.t. a single variable, and the rhs -# does not contain any differentials. If this is not teh case, we throw a warning to let the user +# does not contain any differentials. If this is not the case, we throw a warning to let the user # know that they should be careful. function nonlinear_convert_differentials_check(rs::ReactionSystem) for eq in filter(is_diff_equation, equations(rs)) @@ -578,7 +578,7 @@ function nonlinear_convert_differentials_check(rs::ReactionSystem) # If there is a differential on the right hand side. # If the lhs is not on the form D(...). # If the lhs upper level function is not a differential w.r.t. time. - # If the contenct of the differential is not a variable (and nothing more). + # If the content of the differential is not a variable (and nothing more). # If either of this is a case, throws the warning. if hasnode(Symbolics.is_derivative, eq.rhs) || !Symbolics.is_derivative(eq.lhs) || @@ -591,7 +591,7 @@ function nonlinear_convert_differentials_check(rs::ReactionSystem) This is generally not permitted. If you still would like to perform this conversions, please use the `all_differentials_permitted = true` option. In this case, all differential will be set to `0`. - However, it is recommended to proceed with caution to ensure that the produced nonlinear equation makes sense for you intended application." + However, it is recommended to proceed with caution to ensure that the produced nonlinear equation makes sense for your intended application." ) end end diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index fae7bb9e2d..f23e56d1b4 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -28,7 +28,7 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, return DiscreteProblem(u0, tspan, [vert_ps, edge_ps], args...; kwargs...) end -# Builds a spatial JumpProblem from a DiscreteProblem containg a Lattice Reaction System. +# Builds a spatial JumpProblem from a DiscreteProblem containing a Lattice Reaction System. function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(lrs.rs), combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), kwargs...) diff --git a/test/miscellaneous_tests/api.jl b/test/miscellaneous_tests/api.jl index 2a54807930..9f2cddbb20 100644 --- a/test/miscellaneous_tests/api.jl +++ b/test/miscellaneous_tests/api.jl @@ -314,7 +314,7 @@ end # Test defaults. # Uses mutating stuff (`setdefaults!`) and order dependent input (`species(rn) .=> u0`). -# If you want to test this here @Sam I can write a new one that simualtes using defaults. +# If you want to test this here @Sam I can write a new one that simulates using defaults. # If so, tell me if you have anything specific you want to check though, or I will just implement # it as I would. let diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index 1d0a85e217..e962252ae0 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -50,7 +50,7 @@ end # Tests conservation law computation on large number of networks where we know which have conservation laws. let - # networks for whch we know there is no conservation laws. + # networks for which we know there is no conservation laws. Cs_standard = map(conservationlaws, reaction_networks_standard) Cs_hill = map(conservationlaws, reaction_networks_hill) @test all(size(C, 1) == 0 for C in Cs_standard) diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index 11edb6891b..514dbc136f 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -810,7 +810,7 @@ let end # Checks that the `reactionsystem_uptodate` function work. If it does not, the ReactionSystem -# strcuture's fields have been updated, without updating the `reactionsystem_fields` costant. If so, +# structure's fields have been updated, without updating the `reactionsystem_fields` constant. If so, # there are several places in the code where the `reactionsystem_uptodate` function is called, here # the code might need adaptation to take the updated reaction system into account. let diff --git a/test/test_networks.jl b/test/test_networks.jl index ab2afad1ad..d38c56daec 100644 --- a/test/test_networks.jl +++ b/test/test_networks.jl @@ -347,7 +347,7 @@ reaction_networks_weird[10] = @reaction_network rnw10 begin d, 5X1 → 4X1 end -### Gathers all netowkrs in a simgle array ### +### Gathers all networks in a single array ### reaction_networks_all = [reaction_networks_standard..., reaction_networks_hill..., reaction_networks_conserved..., From de8e941b7367152e8ed3bd48f5bfae59890c8151 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 09:05:53 -0400 Subject: [PATCH 384/446] init --- src/reactionsystem_conversions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 6f7b2255ac..475d12d719 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -109,7 +109,7 @@ function assemble_diffusion(rs, sts, ispcs; combinatoric_ratelaws = true, num_bcsts = count(isbc, get_unknowns(rs)) # we make a matrix sized by the number of reactions - eqs = Matrix{Any}(undef, length(sts) + num_bcsts, length(get_rxs(rs))) + eqs = Matrix{Num}(undef, length(sts) + num_bcsts, length(get_rxs(rs))) eqs .= 0 species_to_idx = Dict((x => i for (i, x) in enumerate(ispcs))) nps = get_networkproperties(rs) From 5e425289326d335382f3e566a7b967b600c88b95 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 14:44:05 -0400 Subject: [PATCH 385/446] init --- README.md | 100 +++++------------- docs/src/assets/Project.toml | 2 +- docs/src/index.md | 96 +++++------------ .../introduction_to_catalyst.md | 2 +- docs/src/model_creation/dsl_basics.md | 1 + 5 files changed, 59 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index 4017eeda1d..a5cee020a8 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/) [![API Stable](https://img.shields.io/badge/API-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/api/catalyst_api/) -[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://docs.sciml.ai/Catalyst/stable/) -[![API Dev](https://img.shields.io/badge/API-dev-blue.svg)](https://docs.sciml.ai/Catalyst/stable/api/catalyst_api/) +[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://docs.sciml.ai/Catalyst/dev/) +[![API Dev](https://img.shields.io/badge/API-dev-blue.svg)](https://docs.sciml.ai/Catalyst/dev/api/catalyst_api/) [![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) [![Build Status](https://github.com/SciML/Catalyst.jl/workflows/CI/badge.svg)](https://github.com/SciML/Catalyst.jl/actions?query=workflow%3ACI) @@ -49,82 +49,40 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model ## Features #### Features of Catalyst -- [The Catalyst DSL](https://docs.sciml.ai/Catalyst/stable/model_creation/dsl_basics/) provides a simple and readable format for manually specifying reaction - network models using chemical reaction notation. -- Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, - built on [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and - [Symbolics.jl](https://docs.sciml.ai/Symbolics/stable/). -- The [Catalyst.jl API](http://docs.sciml.ai/Catalyst/stable/api/catalyst_api) provides functionality - for extending networks, building networks programmatically, and for composing - multiple networks together. -- Leveraging ModelingToolkit, generated models can be converted to symbolic reaction rate equation ODE models, symbolic Chemical Langevin Equation models, and symbolic stochastic chemical kinetics (jump process) models. These can be simulated using any - [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) - [ODE/SDE/jump solver](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_introduction/), and can be used within `EnsembleProblem`s for carrying - out [parallelized parameter sweeps and statistical sampling](https://docs.sciml.ai/Catalyst/stable/model_simulation/ensemble_simulations/). Plot recipes - are available for [visualization of all solutions](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_plotting/). -- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](https://docs.sciml.ai/Catalyst/stable/model_creation/dsl_basics/#dsl_description_stoichiometries_decimal) for generating - ODE models, and symbolic expressions for stoichiometric coefficients [are supported](https://docs.sciml.ai/Catalyst/stable/model_creation/parametric_stoichiometry/) for - all system types. +- [The Catalyst DSL](https://docs.sciml.ai/Catalyst/stable/model_creation/dsl_basics/) provides a simple and readable format for manually specifying reaction network models using chemical reaction notation. +- Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, built on [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and [Symbolics.jl](https://docs.sciml.ai/Symbolics/stable/). +- The [Catalyst.jl API](http://docs.sciml.ai/Catalyst/stable/api/catalyst_api) provides functionality for building networks programmatically and for composing multiple networks together. +- Leveraging ModelingToolkit, generated models can be converted to symbolic reaction rate equation ODE models, symbolic Chemical Langevin Equation models, and symbolic stochastic chemical kinetics (jump process) models. These can be simulated using any [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) [ODE/SDE/jump solver](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_introduction/), and can be used within `EnsembleProblem`s for carrying out [parallelized parameter sweeps and statistical sampling](https://docs.sciml.ai/Catalyst/stable/model_simulation/ensemble_simulations/). Plot recipes are available for [visualization of all solutions](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_plotting/). +- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](https://docs.sciml.ai/Catalyst/stable/model_creation/dsl_basics/#dsl_description_stoichiometries_decimal) for generating ODE models, and symbolic expressions for stoichiometric coefficients [are supported](https://docs.sciml.ai/Catalyst/stable/model_creation/parametric_stoichiometry/) for all system types. - A [network analysis suite](https://docs.sciml.ai/Catalyst/stable/model_creation/network_analysis/) permits the computation of linkage classes, deficiencies, reversibility, and other network properties. -- [Conservation laws can be detected and utilized](https://docs.sciml.ai/Catalyst/stable/model_creation/network_analysis/#network_analysis_deficiency) to reduce system sizes, and to generate - non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). -- Catalyst reaction network models can be [coupled with differential and algebraic equations](https://docs.sciml.ai/Catalyst/stable/model_creation/constraint_equations/) - (which are then incorporated during conversion to ODEs, SDEs, and steady state equations). +- [Conservation laws can be detected and utilized](https://docs.sciml.ai/Catalyst/stable/model_creation/network_analysis/#network_analysis_deficiency) to reduce system sizes, and to generate non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). +- Catalyst reaction network models can be [coupled with differential and algebraic equations](https://docs.sciml.ai/Catalyst/stable/model_creation/constraint_equations/) (which are then incorporated during conversion to ODEs, SDEs, and steady state equations). - Models can be [coupled with events](https://docs.sciml.ai/Catalyst/stable/model_creation/constraint_equations/#constraint_equations_events) that affect the system and its state during simulations. -- By leveraging ModelingToolkit, users have a variety of options for generating - optimized system representations to use in solvers. These include construction - of [dense or sparse Jacobians](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_sparse_jacobian), [multithreading or parallelization of generated - derivative functions](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation), [automatic classification of reactions into optimized - jump types for Gillespie type simulations](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#jump_types), [automatic construction of - dependency graphs for jump systems](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-Requiring-Dependency-Graphs), and more. -- [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl) symbolic - expressions and Julia `Expr`s can be obtained for all rate laws and functions determining the - deterministic and stochastic terms within resulting ODE, SDE or jump models. +- By leveraging ModelingToolkit, users have a variety of options for generating optimized system representations to use in solvers. These include construction of [dense or sparse Jacobians](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_sparse_jacobian), [multithreading or parallelization of generated derivative functions](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation), [automatic classification of reactions into optimized jump types for Gillespie type simulations](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#jump_types), [automatic construction of dependency graphs for jump systems](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-Requiring-Dependency-Graphs), and more. +- [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl) symbolic expressions and Julia `Expr`s can be obtained for all rate laws and functions determining the deterministic and stochastic terms within resulting ODE, SDE, or jump models. - [Steady states](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/homotopy_continuation/) (and their [stabilities](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/steady_state_stability_computation/)) can be computed for model ODE representations. #### Features of Catalyst composing with other packages - [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to numerically solver generated reaction rate equation ODE models. - [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) can be used to numerically solve generated Chemical Langevin Equation SDE models. - [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) can be used to numerically sample generated Stochastic Chemical Kinetics Jump Process models. -- Support for [parallelization of all simulations](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation), including parallelization of - [ODE simulations on GPUs](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation_GPU) using - [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). -- [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX - expressions](https://docs.sciml.ai/Catalyst/stable/model_creation/model_visualisation/#visualisation_latex) corresponding to generated mathematical models or the - underlying set of reactions. -- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](https://docs.sciml.ai/Catalyst/stable/model_creation/model_visualisation/#visualisation_graphs) - (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). -- Model steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) - (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using - [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by numerically solving steady-state nonlinear equations using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). -- [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute - bifurcation diagrams](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/bifurcation_diagrams/) of models' steady states (including finding periodic orbits). -- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute - model [basins of attraction](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_basins_of_attraction) and [Lyapunov spectrums](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_lyapunov_exponents). -- [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used - to [perform structural identifiability analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/structural_identifiability/). -- [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), - and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](https://sebapersson.github.io/PEtab.jl/stable/Define_in_julia/). -- [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform - [global sensitivity analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/global_sensitivity_analysis/) of model behaviors. +- Support for [parallelization of all simulations](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation), including parallelization of [ODE simulations on GPUs](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation_GPU) using [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). +- [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX expressions](https://docs.sciml.ai/Catalyst/stable/model_creation/model_visualisation/#visualisation_latex) corresponding to generated mathematical models or the underlying set of reactions. +- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](https://docs.sciml.ai/Catalyst/stable/model_creation/model_visualisation/#visualisation_graphs) (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). +- Model steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using [SteadyStateDiffEq.jl](https://github.com/SciML/SteadyStateDiffEq.jl), or by numerically solving steady-state nonlinear equations using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). +- [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute bifurcation diagrams](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/bifurcation_diagrams/) of model steady states (including finding periodic orbits). +- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute model [basins of attraction](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_basins_of_attraction) and [Lyapunov spectrums](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_lyapunov_exponents). +- [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used to [perform structural identifiability analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/structural_identifiability/). +- [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](https://sebapersson.github.io/PEtab.jl/stable/Define_in_julia/). +- [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform [global sensitivity analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/global_sensitivity_analysis/) of model behaviors. - [SciMLSensitivity.jl](https://github.com/SciML/SciMLSensitivity.jl) can be used to compute local sensitivities of functions containing forward model simulations. #### Features of packages built upon Catalyst -- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#Loading-SBML-files-using-SBMLImporter.jl-and-SBMLToolkit.jl) via - [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), - and [from BioNetGen .net files](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#file_loading_rni_net) and various stoichiometric matrix network representations - using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). -- [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic - ModelingToolkit `ODESystem`s that represent moment closure approximations to moments of the - Chemical Master Equation, from reaction networks defined in Catalyst. -- [FiniteStateProjection.jl](https://github.com/kaandocal/FiniteStateProjection.jl) - allows the construction and numerical solution of Chemical Master Equation - models from reaction networks defined in Catalyst. -- [DelaySSAToolkit.jl](https://github.com/palmtree2013/DelaySSAToolkit.jl) can - augment Catalyst reaction network models with delays, and can simulate the - resulting stochastic chemical kinetics with delays models. -- [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl), a package for - constructing and analyzing bond graphs models, which can take Catalyst models as input. +- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#Loading-SBML-files-using-SBMLImporter.jl-and-SBMLToolkit.jl) via [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), and [from BioNetGen .net files](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#file_loading_rni_net) and various stoichiometric matrix network representations using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). +- [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic ModelingToolkit `ODESystem`s that represent moment closure approximations to moments of the Chemical Master Equation, from reaction networks defined in Catalyst. +- [FiniteStateProjection.jl](https://github.com/kaandocal/FiniteStateProjection.jl) allows the construction and numerical solution of Chemical Master Equation models from reaction networks defined in Catalyst. +- [DelaySSAToolkit.jl](https://github.com/palmtree2013/DelaySSAToolkit.jl) can augment Catalyst reaction network models with delays, and can simulate the resulting stochastic chemical kinetics with delays models. +- [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl), a package for constructing and analyzing bond graphs models, which can take Catalyst models as input. ## Illustrative example @@ -147,7 +105,7 @@ end # Create an ODE that can be simulated. u0 = [:S => 50.0, :E => 10.0, :SE => 0.0, :P => 0.0] tspan = (0., 200.) -ps = (:kB => 0.01, :kD => 0.1, :kP => 0.1) +ps = [:kB => 0.01, :kD => 0.1, :kP => 0.1] ode = ODEProblem(model, u0, tspan, ps) # Simulate ODE and plot results. @@ -176,7 +134,7 @@ In the above example, we used basic Catalyst-based workflows to simulate a simpl instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ -($G \to Gᴾ$) is promoted by sunlight (modeled as the cyclic sinusoid $kₐ*(sin(t)+1)$), which +($G \to Gᴾ$) is promoted by sunlight (modeled as the cyclic sinusoid $kₐ (sin(t) + 1)$), which phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($Vₘ$) it undergoes cell division. First, we declare our model: ```julia @@ -203,8 +161,8 @@ sprob = SDEProblem(cell_model, u0, tspan, ps) This produces the following equations: ```math \begin{align*} -dG(t) &= - \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) + \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt - \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) + \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) & -dGᴾ(t) &= \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) - \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt + \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) - \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) & +dG(t) &= - \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) + \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt - \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) + \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) \\ +dGᴾ(t) &= \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) - \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt + \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) - \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) \\ dV(t) &= \left(g \cdot Gᴾ(t)\right) dt \end{align*} ``` diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index b92da6ef5d..7d86a95a90 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -39,7 +39,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" BenchmarkTools = "1.5" BifurcationKit = "0.3.4" CairoMakie = "0.12" -Catalyst = "13" +Catalyst = "14" DataFrames = "1.6" DiffEqParamEstim = "2.2" Distributions = "0.25" diff --git a/docs/src/index.md b/docs/src/index.md index cedc37bd76..dda0e4ccc2 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -19,82 +19,40 @@ etc). ## [Features](@id doc_index_features) #### [Features of Catalyst](@id doc_index_features_catalyst) -- [The Catalyst DSL](@ref dsl_description) provides a simple and readable format for manually specifying reaction - network models using chemical reaction notation. -- Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, - built on [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and - [Symbolics.jl](https://docs.sciml.ai/Symbolics/stable/). -- The [Catalyst.jl API](@ref api) provides functionality - for extending networks, building networks programmatically, and for composing - multiple networks together. -- Leveraging ModelingToolkit, generated models can be converted to symbolic reaction rate equation ODE models, symbolic Chemical Langevin Equation models, and symbolic stochastic chemical kinetics (jump process) models. These can be simulated using any - [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) - [ODE/SDE/jump solver](@ref simulation_intro), and can be used within `EnsembleProblem`s for carrying - out [parallelized parameter sweeps and statistical sampling](@ref ensemble_simulations). Plot recipes - are available for [visualization of all solutions](@ref simulation_plotting). -- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref dsl_description_stoichiometries_decimal) for generating - ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref parametric_stoichiometry) for - all system types. +- [The Catalyst DSL](@ref dsl_description) provides a simple and readable format for manually specifying reaction network models using chemical reaction notation. +- Catalyst `ReactionSystem`s provides a symbolic representation of reaction networks, built on [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) and [Symbolics.jl](https://docs.sciml.ai/Symbolics/stable/). +- The [Catalyst.jl API](@ref api) provides functionality for building networks programmatically and for composing multiple networks together. +- Leveraging ModelingToolkit, generated models can be converted to symbolic reaction rate equation ODE models, symbolic Chemical Langevin Equation models, and symbolic stochastic chemical kinetics (jump process) models. These can be simulated using any [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) [ODE/SDE/jump solver](@ref simulation_intro), and can be used within `EnsembleProblem`s for carrying out [parallelized parameter sweeps and statistical sampling](@ref ensemble_simulations). Plot recipes are available for [visualization of all solutions](@ref simulation_plotting). +- Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref dsl_description_stoichiometries_decimal) for generating ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref parametric_stoichiometry) for all system types. - A [network analysis suite](@ref network_analysis) permits the computation of linkage classes, deficiencies, reversibility, and other network properties. -- [Conservation laws can be detected and utilized](@ref network_analysis_deficiency) to reduce system sizes, and to generate - non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). -- Catalyst reaction network models can be [coupled with differential and algebraic equations](@ref constraint_equations_coupling_constraints) - (which are then incorporated during conversion to ODEs, SDEs, and steady state equations). +- [Conservation laws can be detected and utilized](@ref network_analysis_deficiency) to reduce system sizes, and to generate non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations). +- Catalyst reaction network models can be [coupled with differential and algebraic equations](@ref constraint_equations_coupling_constraints) (which are then incorporated during conversion to ODEs, SDEs, and steady state equations). - Models can be [coupled with events](@ref constraint_equations_events) that affect the system and its state during simulations. -- By leveraging ModelingToolkit, users have a variety of options for generating - optimized system representations to use in solvers. These include construction - of [dense or sparse Jacobians](@ref ode_simulation_performance_sparse_jacobian), [multithreading or parallelization of generated - derivative functions](@ref ode_simulation_performance_parallelisation), [automatic classification of reactions into optimized - jump types for Gillespie type simulations](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#jump_types), [automatic construction of - dependency graphs for jump systems](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-Requiring-Dependency-Graphs), and more. -- [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl) symbolic - expressions and Julia `Expr`s can be obtained for all rate laws and functions determining the - deterministic and stochastic terms within resulting ODE, SDE or jump models. +- By leveraging ModelingToolkit, users have a variety of options for generating optimized system representations to use in solvers. These include construction of [dense or sparse Jacobians](@ref ode_simulation_performance_sparse_jacobian), [multithreading or parallelization of generated derivative functions](@ref ode_simulation_performance_parallelisation), [automatic classification of reactions into optimized jump types for Gillespie type simulations](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#jump_types), [automatic construction of dependency graphs for jump systems](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-Requiring-Dependency-Graphs), and more. +- [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl) symbolic expressions and Julia `Expr`s can be obtained for all rate laws and functions determining the deterministic and stochastic terms within resulting ODE, SDE, or jump models. - [Steady states](@ref homotopy_continuation) (and their [stabilities](@ref steady_state_stability)) can be computed for model ODE representations. #### [Features of Catalyst composing with other packages](@id doc_index_features_composed) - [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) Can be used to numerically solver generated reaction rate equation ODE models. - [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) can be used to numerically solve generated Chemical Langevin Equation SDE models. - [JumpProcesses.jl](https://github.com/SciML/JumpProcesses.jl) can be used to numerically sample generated Stochastic Chemical Kinetics Jump Process models. -- Support for [parallelization of all simulations](@ref ode_simulation_performance_parallelisation), including parallelization of - [ODE simulations on GPUs](@ref ode_simulation_performance_parallelisation_GPU) using - [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). -- [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX - expressions](@ref visualisation_latex) corresponding to generated mathematical models or the - underlying set of reactions. -- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](@ref visualisation_graphs) - (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). -- Model steady states can be [computed through homotopy continuation](@ref homotopy_continuation) using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) - (which can find *all* steady states of systems with multiple ones), by [forward ODE simulations](@ref steady_state_solving_simulation) using - [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by [numerically solving steady-state nonlinear equations](@ref steady_state_solving_nonlinear) using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). -- [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute - bifurcation diagrams](@ref bifurcation_diagrams) of models' steady states (including finding periodic orbits). -- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute - model [basins of attraction](@ref dynamical_systems_basins_of_attraction) and [Lyapunov spectrums](@ref dynamical_systems_lyapunov_exponents). -- [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used - to [perform structural identifiability analysis](@ref structural_identifiability). -- [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), - and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](https://sebapersson.github.io/PEtab.jl/stable/Define_in_julia/). -- [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform - [global sensitivity analysis](@ref global_sensitivity_analysis) of model behaviors. +- Support for [parallelization of all simulations](@ref ode_simulation_performance_parallelisation), including parallelization of [ODE simulations on GPUs](@ref ode_simulation_performance_parallelisation_GPU) using [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). +- [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX expressions](@ref visualisation_latex) corresponding to generated mathematical models or the underlying set of reactions. +- [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](@ref visualisation_graphs) (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). +- Model steady states can be [computed through homotopy continuation](@ref homotopy_continuation) using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by [forward ODE simulations](@ref steady_state_solving_simulation) using [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl, or by [numerically solving steady-state nonlinear equations](@ref steady_state_solving_nonlinear) using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). +- [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute bifurcation diagrams](@ref bifurcation_diagrams) of model steady states (including finding periodic orbits). +- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute model [basins of attraction](@ref dynamical_systems_basins_of_attraction) and [Lyapunov spectrums](@ref dynamical_systems_lyapunov_exponents). +- [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used to [perform structural identifiability analysis](@ref structural_identifiability). +- [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](https://sebapersson.github.io/PEtab.jl/stable/Define_in_julia/). +- [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform [global sensitivity analysis](@ref global_sensitivity_analysis) of model behaviors. - [SciMLSensitivity.jl](https://github.com/SciML/SciMLSensitivity.jl) can be used to compute local sensitivities of functions containing forward model simulations. #### [Features of packages built upon Catalyst](@id doc_index_features_other_packages) -- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref model_file_import_export_sbml) via - [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), - and [from BioNetGen .net files](@ref model_file_import_export_sbml_rni_net) and various stoichiometric matrix network representations - using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). -- [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic - ModelingToolkit `ODESystem`s that represent moment closure approximations to moments of the - Chemical Master Equation, from reaction networks defined in Catalyst. -- [FiniteStateProjection.jl](https://github.com/kaandocal/FiniteStateProjection.jl) - allows the construction and numerical solution of Chemical Master Equation - models from reaction networks defined in Catalyst. -- [DelaySSAToolkit.jl](https://github.com/palmtree2013/DelaySSAToolkit.jl) can - augment Catalyst reaction network models with delays, and can simulate the - resulting stochastic chemical kinetics with delays models. -- [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl), a package for - constructing and analyzing bond graphs models, which can take Catalyst models as input. +- Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref model_file_import_export_sbml) via [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), and [from BioNetGen .net files](@ref model_file_import_export_sbml_rni_net) and various stoichiometric matrix network representations using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). +- [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic ModelingToolkit `ODESystem`s that represent moment closure approximations to moments of the Chemical Master Equation, from reaction networks defined in Catalyst. +- [FiniteStateProjection.jl](https://github.com/kaandocal/FiniteStateProjection.jl) allows the construction and numerical solution of Chemical Master Equation models from reaction networks defined in Catalyst. +- [DelaySSAToolkit.jl](https://github.com/palmtree2013/DelaySSAToolkit.jl) can augment Catalyst reaction network models with delays, and can simulate the resulting stochastic chemical kinetics with delays models. +- [BondGraphs.jl](https://github.com/jedforrest/BondGraphs.jl), a package for constructing and analyzing bond graphs models, which can take Catalyst models as input. ## [How to read this documentation](@id doc_index_documentation) The Catalyst documentation is separated into sections describing Catalyst's various features. Where appropriate, some sections will also give advice on best practices for various modeling workflows, and provide links with further reading. Each section also contains a set of relevant example workflows. Finally, the [API](@ref api) section contains a list of all functions exported by Catalyst (as well as descriptions of them and their inputs and outputs). @@ -163,7 +121,7 @@ end # Create an ODE that can be simulated. u0 = [:S => 50.0, :E => 10.0, :SE => 0.0, :P => 0.0] tspan = (0., 200.) -ps = (:kB => 0.01, :kD => 0.1, :kP => 0.1) +ps = [:kB => 0.01, :kD => 0.1, :kP => 0.1] ode = ODEProblem(model, u0, tspan, ps) # Simulate ODE and plot results. @@ -190,7 +148,7 @@ In the above example, we used basic Catalyst-based workflows to simulate a simpl instead show how various Catalyst features can compose to create a much more advanced model. Our model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ -($G \to Gᴾ$) is promoted by sunlight (modeled as the cyclic sinusoid $kₐ*(sin(t)+1)$), which +($G \to Gᴾ$) is promoted by sunlight (modeled as the cyclic sinusoid $kₐ (sin(t) + 1)$), which phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($Vₘ$) it undergoes cell division. First, we declare our model: ```@example home_elaborate_example @@ -217,8 +175,8 @@ sprob = SDEProblem(cell_model, u0, tspan, ps) This produces the following equations: ```math \begin{align*} -dG(t) &= - \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) + \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt - \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) + \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) & -dGᴾ(t) &= \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) - \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt + \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) - \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) & +dG(t) &= - \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) + \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt - \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) + \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) \\ +dGᴾ(t) &= \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) - \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt + \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) - \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) \\ dV(t) &= \left(g \cdot Gᴾ(t)\right) dt \end{align*} ``` diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index 64f3819dc8..848fac71a9 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -155,7 +155,7 @@ underlying problem. !!! note - Above we have used `repressilator = complete(repressilator)` and `odesys = complete(odesys)` to mark these systems as *complete*, indicating to Catalyst and ModelingToolkit that these models are finalized. This must be done before any system is given as input to a `convert` call or some problem type. `ReactionSystem` models created through the @reaction_network` DSL (which is introduced elsewhere, and primarily used throughout these documentation) are always marked as complete when generated. Hence `complete` does not need to be called on them. Symbolically generated `ReactionSystem`s, `ReactionSystem`s generated via the `@network_component` macro, and any ModelingToolkit system generated by `convert` always needs to be manually marked as `complete` as we do for `odesys` above. An expanded description on *completeness* can be found [here](@ref completeness_note). + Above we have used `repressilator = complete(repressilator)` and `odesys = complete(odesys)` to mark these systems as *complete*, indicating to Catalyst and ModelingToolkit that these models are finalized. This must be done before any system is given as input to a `convert` call or some problem type. `ReactionSystem` models created through the `@reaction_network` DSL (which is introduced elsewhere, and primarily used throughout these documentation) are always marked as complete when generated. Hence `complete` does not need to be called on them. Symbolically generated `ReactionSystem`s, `ReactionSystem`s generated via the `@network_component` macro, and any ModelingToolkit system generated by `convert` always needs to be manually marked as `complete` as we do for `odesys` above. An expanded description on *completeness* can be found [here](@ref completeness_note). At this point we are all set to solve the ODEs. We can now use any ODE solver from within the diff --git a/docs/src/model_creation/dsl_basics.md b/docs/src/model_creation/dsl_basics.md index a091f49453..83c6d59172 100644 --- a/docs/src/model_creation/dsl_basics.md +++ b/docs/src/model_creation/dsl_basics.md @@ -352,6 +352,7 @@ An example of how this can be used to create a neat-looking model can be found i (kB,kD), A + σᵛ ↔ Aσᵛ L, Aσᵛ → σᵛ end +nothing # hide ``` This functionality can also be used to create less serious models: From 4baf343d92b8a6d6b573316395ed6cc8ed838df2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 16:08:15 -0400 Subject: [PATCH 386/446] add GPU simulation tutorial --- .../sde_simulation_performance.md | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/docs/src/model_simulation/sde_simulation_performance.md b/docs/src/model_simulation/sde_simulation_performance.md index 94a7584235..9c37364ed5 100644 --- a/docs/src/model_simulation/sde_simulation_performance.md +++ b/docs/src/model_simulation/sde_simulation_performance.md @@ -12,9 +12,47 @@ automatic SDE solver selection enabled (just like is the case for ODEs by defaul In the section on ODE simulation performance, we describe various [options for computing the system Jacobian](@ref ode_simulation_performance_jacobian), and how these could be used to improve performance for [implicit solvers](@ref ode_simulation_performance_stiffness). These can be used in tandem with implicit SDE solvers (such as `STrapezoid`). However, due to additional considerations during SDE simulations, it is much less certain whether these will actually have any impact on performance. So while these options might be worth reading about and trialling, there is no guarantee that they will be beneficial. ## [Parallelisation on CPUs and GPUs](@id sde_simulation_performance_parallelisation) -We have previously described how simulation parallelisation can be used to [improve performance when multiple ODE simulations are carried out](@ref ode_simulation_performance_parallelisation). The same approaches can be used for SDE simulations. Indeed, it is often more relevant for SDEs, as these are often re-simulated using identical simulation conditions (to investigate their typical behaviour across many samples). +We have previously described how simulation parallelisation can be used to [improve performance when multiple ODE simulations are carried out](@ref ode_simulation_performance_parallelisation). The same approaches can be used for SDE simulations. Indeed, it is often more relevant for SDEs, as these are often re-simulated using identical simulation conditions (to investigate their typical behaviour across many samples). CPU parallelisation of SDE simulations uses the [same approach as ODEs](@ref ode_simulation_performance_parallelisation_CPU). GPU parallelisation requires some additional considerations, which are described below. -CPU parallelisation of SDE simulations uses the [same approach as ODEs](@ref ode_simulation_performance_parallelisation_CPU). GPU parallelisation is carried out very similarly to [GPU parallelisation of ODE simulations](@ref ode_simulation_performance_parallelisation_GPU). The only difference is that a GPU-compatible SDE solver is required, with the only alternative currently available being `GPUEM`. +### [GPU parallelisation of SDE simulations](@id sde_simulation_performance_parallelisation_GPU) +GPU parallelisation of SDE simulations uses a similar approach as that for [ODE simulations](@ref ode_simulation_performance_parallelisation_GPU). The main differences are that SDE parallelisation requires a GPU SDE solver (like `GPUEM`) and fixed time stepping. + +We will assume that we are using the CUDA GPU hardware, so we will first load the [CUDA.jl](https://github.com/JuliaGPU/CUDA.jl) backend package, as well as DiffEqGPU: +```julia +using CUDA, DiffEqGPU +``` +Which backend package you should use depends on your available hardware, with the alternatives being listed [here](https://docs.sciml.ai/DiffEqGPU/stable/manual/backends/). + +Next, we create the `SDEProblem` which we wish to simulate. Like for ODEs, we ensure that all vectors are [static vectors](https://github.com/JuliaArrays/StaticArrays.jl) and that all values are `Float32`s. Here we prepare the parallel simulations of a simple [birth-death process](@ref basic_CRN_library_bd). +```@example sde_simulation_performance_gpu +using Catalyst +bd_model = @reaction_network begin + (p,d), 0 <--> X +end +@unpack X, p, d = bd_model + +u0 = @SVector [X => 20.0f0] +tspan = (0.0f0, 10.0f0) +ps = @SVector [p => 10.0f0, d => 1.0f0] +sprob = SDEProblem(bd_model, u0, tspan, ps) +nothing # hide +``` +The `SDEProblem` is then used to [create an `EnsembleProblem`](@ref ensemble_simulations_monte_carlo). +```@example sde_simulation_performance_gpu +eprob = EnsembleProblem(sprob) +nothing # hide +``` +Finally, we can solve our `EnsembleProblem` while: +- Using a valid GPU SDE solver (either [`GPUEM`](https://docs.sciml.ai/DiffEqGPU/stable/manual/ensemblegpukernel/#DiffEqGPU.GPUEM) or [`GPUSIEA`](https://docs.sciml.ai/DiffEqGPU/stable/manual/ensemblegpukernel/#DiffEqGPU.GPUSIEA)). +- Designating the GPU ensemble method, `EnsembleGPUKernel` (with the correct GPU backend as input). +- Designating the number of trajectories we wish to simulate. +- Designating a fixed time step size. + +```julia +esol = solve(eprob, GPUEM(), EnsembleGPUKernel(CUDA.CUDABackend()); trajectories = 10000, dt = 0.01) +``` + +Above we parallelise GPU simulations with identical initial conditions and parameter values. However, [varying these](@ref ensemble_simulations_varying_conditions) is also possible. ### [Multilevel Monte Carlo](@id sde_simulation_performance_parallelisation_mlmc) An approach for speeding up parallel stochastic simulations is so-called [*multilevel Monte Carlo approaches*](https://en.wikipedia.org/wiki/Multilevel_Monte_Carlo_method) (MLMC). These are used when a stochastic process is simulated repeatedly using identical simulation conditions. Here, instead of performing all simulations using identical [tolerance](@ref ode_simulation_performance_error), the ensemble is simulated using a range of tolerances (primarily lower ones, which yields faster simulations). Currently, [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) do not have a native implementation for performing MLMC simulations (this will hopefully be added in the future). However, if high performance of parallel SDE simulations is required, these approaches may be worth investigating. \ No newline at end of file From 109900948cf8485241a98c5fce286e06d8458b13 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 16:11:53 -0400 Subject: [PATCH 387/446] merge fixes --- docs/pages.jl | 3 ++- docs/src/model_simulation/simulation_introduction.md | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/pages.jl b/docs/pages.jl index a8772bd62b..dbb52df4e6 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -27,7 +27,8 @@ pages = Any[ "model_simulation/simulation_plotting.md", "model_simulation/simulation_structure_interfacing.md", "model_simulation/ensemble_simulations.md", - "model_simulation/ode_simulation_performance.md" + "model_simulation/ode_simulation_performance.md", + "model_simulation/sde_simulation_performance.md" ], "Steady state analysis" => Any[ "steady_state_functionality/homotopy_continuation.md", diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index bf5fc7158e..32e01cfcff 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -1,7 +1,7 @@ # [Model Simulation Introduction](@id simulation_intro) Catalyst's core functionality is the creation of *chemical reaction network* (CRN) models that can be simulated using ODE, SDE, and jump simulations. How such simulations are carried out has already been described in [Catalyst's introduction](@ref introduction_to_catalyst). This page provides a deeper introduction, giving some additional background and introducing various simulation-related options. -Here we will focus on the basics, with other sections of the simulation documentation describing various specialised features, or giving advice on performance. Anyone who plans on using Catalyst's simulation functionality extensively is recommended to also read the documentation on [solution plotting](@ref simulation_plotting), and on how to [interact with simulation problems, integrators, and solutions](@ref simulation_structure_interfacing). Anyone with an application for which performance is critical should consider reading the corresponding page on performance advice for [ODEs](@ref ode_simulation_performance). +Here we will focus on the basics, with other sections of the simulation documentation describing various specialised features, or giving advice on performance. Anyone who plans on using Catalyst's simulation functionality extensively is recommended to also read the documentation on [solution plotting](@ref simulation_plotting), and on how to [interact with simulation problems, integrators, and solutions](@ref simulation_structure_interfacing). Anyone with an application for which performance is critical should consider reading the corresponding page on performance advice for [ODEs](@ref ode_simulation_performance) or [SDEs](@ref sde_simulation_performance). ### [Background to CRN simulations](@id simulation_intro_theory) This section provides some brief theory on CRN simulations. For details on how to carry out these simulations in actual code, please skip to the following sections. @@ -169,7 +169,7 @@ nothing # hide ``` ## [Performing SDE simulations](@id simulation_intro_SDEs) -Catalyst uses the [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) package to perform SDE simulations. This section provides a brief introduction, with [StochasticDiffEq's documentation](https://docs.sciml.ai/StochasticDiffEq/stable/) providing a more extensive description. sBy default, Catalyst generates SDEs from CRN models using the chemical Langevin equation. +Catalyst uses the [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) package to perform SDE simulations. This section provides a brief introduction, with [StochasticDiffEq's documentation](https://docs.sciml.ai/StochasticDiffEq/stable/) providing a more extensive description. By default, Catalyst generates SDEs from CRN models using the chemical Langevin equation. A dedicated section giving advice on how to optimise SDE simulation performance can be found [here](@ref sde_simulation_performance). SDE simulations are performed in a similar manner to ODE simulations. The only exception is that an `SDEProblem` is created (rather than an `ODEProblem`). Furthermore, the [StochasticDiffEq.jl](https://github.com/SciML/StochasticDiffEq.jl) package (rather than the OrdinaryDiffEq package) is required for performing simulations. Here we simulate the two-state model for the same parameter set as previously used: ```@example simulation_intro_sde From 98c3e9009428514786d1d46380981ca636f28b7f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 16:23:01 -0400 Subject: [PATCH 388/446] add citations --- .../optimization_ode_param_fitting.md | 2 +- .../simulation_introduction.md | 51 ++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/docs/src/inverse_problems/optimization_ode_param_fitting.md b/docs/src/inverse_problems/optimization_ode_param_fitting.md index ae729d2fc2..5834b96823 100644 --- a/docs/src/inverse_problems/optimization_ode_param_fitting.md +++ b/docs/src/inverse_problems/optimization_ode_param_fitting.md @@ -170,7 +170,7 @@ nothing # hide ``` --- -## [Citation](@id structural_identifiability_citation) +## [Citation](@id optimization_parameter_fitting_citation) If you use this functionality in your research, please cite the following paper to support the authors of the Optimization.jl package: ``` @software{vaibhav_kumar_dixit_2023_7738525, diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index bf5fc7158e..7b43c79034 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -336,4 +336,53 @@ circadian_model = @reaction_network begin d, P --> 0 end ``` -This type of model will generate so called *variable rate jumps*. Simulation of such model is non-trivial (and Catalyst currently lacks a good interface for this). A detailed description of how to carry out jump simulations for models with time-dependant rates can be found [here](https://docs.sciml.ai/JumpProcesses/stable/tutorials/simple_poisson_process/#VariableRateJumps-for-processes-that-are-not-constant-between-jumps). \ No newline at end of file +This type of model will generate so called *variable rate jumps*. Simulation of such model is non-trivial (and Catalyst currently lacks a good interface for this). A detailed description of how to carry out jump simulations for models with time-dependant rates can be found [here](https://docs.sciml.ai/JumpProcesses/stable/tutorials/simple_poisson_process/#VariableRateJumps-for-processes-that-are-not-constant-between-jumps). + +--- +## [Citation](@id simulation_intro_citation) +When you simulate Catalyst models in your research, please cite the corresponding paper(s) to support the package authors. For ODE simulations: +``` +@article{DifferentialEquations.jl-2017, + author = {Rackauckas, Christopher and Nie, Qing}, + doi = {10.5334/jors.151}, + journal = {The Journal of Open Research Software}, + keywords = {Applied Mathematics}, + note = {Exported from https://app.dimensions.ai on 2019/05/05}, + number = {1}, + pages = {}, + title = {DifferentialEquations.jl – A Performant and Feature-Rich Ecosystem for Solving Differential Equations in Julia}, + url = {https://app.dimensions.ai/details/publication/pub.1085583166 and http://openresearchsoftware.metajnl.com/articles/10.5334/jors.151/galley/245/download/}, + volume = {5}, + year = {2017} +} +``` +For SDE simulations: +``` +@article{rackauckas2017adaptive, + title={Adaptive methods for stochastic differential equations via natural embeddings and rejection sampling with memory}, + author={Rackauckas, Christopher and Nie, Qing}, + journal={Discrete and continuous dynamical systems. Series B}, + volume={22}, + number={7}, + pages={2731}, + year={2017}, + publisher={NIH Public Access} +} +``` +For jump simulations: +``` +@misc{2022JumpProcesses, + author = {Isaacson, S. A. and Ilin, V. and Rackauckas, C. V.}, + title = {{JumpProcesses.jl}}, + howpublished = {\url{https://github.com/SciML/JumpProcesses.jl/}}, + year = {2022} +} +@misc{zagatti_extending_2023, + title = {Extending {JumpProcess}.jl for fast point process simulation with time-varying intensities}, + url = {http://arxiv.org/abs/2306.06992}, + doi = {10.48550/arXiv.2306.06992}, + publisher = {arXiv}, + author = {Zagatti, Guilherme Augusto and Isaacson, Samuel A. and Rackauckas, Christopher and Ilin, Vasily and Ng, See-Kiong and Bressan, Stéphane}, + year = {2023}, +} +``` \ No newline at end of file From 6eb9912f990befb34457b9e0e912f97c67fef282 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 16:41:46 -0400 Subject: [PATCH 389/446] add fixed dt SDE simulation section --- .../model_simulation/simulation_introduction.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index 7b43c79034..c28a01ebe5 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -222,6 +222,17 @@ sol = solve(sprob, STrapezoid(); seed = 12345, abstol = 1e-1, reltol = 1e-1) # h plot(sol) ``` +### [SDE simulations with fixed time stepping](@id simulation_intro_SDEs_fixed_dt) +StochasticDiffEq implements SDE solvers with adaptive time stepping. However, when using a non-adaptive solver (or using the `adaptive = false` argument to turn adaptive time stepping off for an adaptive solver) a fixed time step `dt` must be designated. Here we simulate the same `SDEProblem` which we struggled with previously, but using the non-adaptive [`EM`](https://en.wikipedia.org/wiki/Euler%E2%80%93Maruyama_method) solver and a fixed `dt`: +```@example simulation_intro_sde +sol = solve(sprob, EM(); dt = 0.001) +sol = solve(sprob, EM(); dt = 0.001, seed = 1234567) # hide +plot(sol) +``` +We note that this approach also enables us to successfully simulate the SDE we previously struggled with. + +Generally, using a smaller fixed `dt` provides a more exact simulation, but also increases simulation runtime. + ### [Scaling the noise in the chemical Langevin equation](@id simulation_intro_SDEs_noise_saling) When using the CLE to generate SDEs from a CRN, it can sometimes be desirable to scale the magnitude of the noise. This can be done by introducing a *noise scaling term*, with each noise term generated by the CLE being multiplied with this term. A noise scaling term can be set using the `@default_noise_scaling` option: ```@example simulation_intro_sde @@ -338,9 +349,10 @@ end ``` This type of model will generate so called *variable rate jumps*. Simulation of such model is non-trivial (and Catalyst currently lacks a good interface for this). A detailed description of how to carry out jump simulations for models with time-dependant rates can be found [here](https://docs.sciml.ai/JumpProcesses/stable/tutorials/simple_poisson_process/#VariableRateJumps-for-processes-that-are-not-constant-between-jumps). + --- ## [Citation](@id simulation_intro_citation) -When you simulate Catalyst models in your research, please cite the corresponding paper(s) to support the package authors. For ODE simulations: +When you simulate Catalyst models in your research, please cite the corresponding paper(s) to support the simulation package authors. For ODE simulations: ``` @article{DifferentialEquations.jl-2017, author = {Rackauckas, Christopher and Nie, Qing}, From d42214ec2a04f0e0d5d6bd9e7497ec8044123f72 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 17:09:48 -0400 Subject: [PATCH 390/446] up --- docs/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index 7d86a95a90..0fe8c869b8 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -55,7 +55,7 @@ ModelingToolkit = "9.16.0" NonlinearSolve = "3.12" Optim = "1.9" Optimization = "3.25" -OptimizationBBO = "0.2.1" +OptimizationBBO = "0.3" OptimizationNLopt = "0.2.1" OptimizationOptimJL = "0.3.1" OptimizationOptimisers = "0.2.1" From e2bc6eb55649a956af43c17127740973d44c90b8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 17:29:51 -0400 Subject: [PATCH 391/446] up --- docs/src/model_simulation/sde_simulation_performance.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/model_simulation/sde_simulation_performance.md b/docs/src/model_simulation/sde_simulation_performance.md index 9c37364ed5..d32861ae69 100644 --- a/docs/src/model_simulation/sde_simulation_performance.md +++ b/docs/src/model_simulation/sde_simulation_performance.md @@ -1,5 +1,5 @@ # [Advice for performant SDE simulations](@id sde_simulation_performance) -While there exist relatively straightforward approaches to manage performance for [ODE](@ref ode_simulation_performance) and [Jump](@ref ref) simulations, this is generally not the case for SDE simulations. Below, we briefly describe some options. However, as one starts to investigate these, one quickly reaches what is (or could be) active areas of research. +While there exist relatively straightforward approaches to manage performance for [ODE](@ref ode_simulation_performance) and jump simulations, this is generally not the case for SDE simulations. Below, we briefly describe some options. However, as one starts to investigate these, one quickly reaches what is (or could be) active areas of research. ## [SDE solver selection](@id sde_simulation_performance_solvers) We have previously described how [ODE solver selection](@ref ode_simulation_performance_solvers) can impact simulation performance. Again, it can be worthwhile to investigate solver selection's impact on performance for SDE simulations. Throughout this documentation, we generally use the `STrapezoid` solver as the default choice. However, if the `DifferentialEquations` package is loaded @@ -25,7 +25,7 @@ Which backend package you should use depends on your available hardware, with th Next, we create the `SDEProblem` which we wish to simulate. Like for ODEs, we ensure that all vectors are [static vectors](https://github.com/JuliaArrays/StaticArrays.jl) and that all values are `Float32`s. Here we prepare the parallel simulations of a simple [birth-death process](@ref basic_CRN_library_bd). ```@example sde_simulation_performance_gpu -using Catalyst +using Catalyst, StochasticDiffEq, StaticArrays bd_model = @reaction_network begin (p,d), 0 <--> X end From 9db08185a81ed7ecdfb68346c8dfb2d1c7856d5f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 17:36:16 -0400 Subject: [PATCH 392/446] writing fix --- src/reactionsystem_conversions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactionsystem_conversions.jl b/src/reactionsystem_conversions.jl index 9e5b79d692..c46854454f 100644 --- a/src/reactionsystem_conversions.jl +++ b/src/reactionsystem_conversions.jl @@ -590,7 +590,7 @@ function nonlinear_convert_differentials_check(rs::ReactionSystem) (2) The right-hand side does not contain any differentials. This is generally not permitted. - If you still would like to perform this conversions, please use the `all_differentials_permitted = true` option. In this case, all differential will be set to `0`. + If you still would like to perform this conversion, please use the `all_differentials_permitted = true` option. In this case, all differentials will be set to `0`. However, it is recommended to proceed with caution to ensure that the produced nonlinear equation makes sense for your intended application." ) end From 2cf77fbc4951982f01cdbe191d3d86d917fae32b Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 19:48:21 -0400 Subject: [PATCH 393/446] change broken tests --- test/upstream/mtk_structure_indexing.jl | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/test/upstream/mtk_structure_indexing.jl b/test/upstream/mtk_structure_indexing.jl index 61a09f0b9d..1049e93a0e 100644 --- a/test/upstream/mtk_structure_indexing.jl +++ b/test/upstream/mtk_structure_indexing.jl @@ -65,9 +65,7 @@ end # Tests problem indexing and updating. let @test_broken false # A few cases fails for JumpProblem: https://github.com/SciML/ModelingToolkit.jl/issues/2838 - @test_broken false # A few cases fails for SteadyStateProblem: https://github.com/SciML/SciMLBase.jl/issues/660 - @test_broken false # Most cases broken for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 - for prob in deepcopy([oprob, sprob, dprob, nprob]) + for prob in deepcopy([oprob, sprob, dprob, nprob, ssprob, eoprob, esprob, edprob, enprob, essprob]) # Get u values (including observables). @test prob[X] == prob[model.X] == prob[:X] == 4 @test prob[XY] == prob[model.XY] == prob[:XY] == 9 @@ -119,9 +117,7 @@ end # Test remake function. let @test_broken false # Cannot check result for JumpProblem: https://github.com/SciML/ModelingToolkit.jl/issues/2838 - @test_broken false # Cannot deepcopy SteadyStateProblem :https://github.com/SciML/ModelingToolkit.jl/issues/2837 - @test_broken false # Currently cannot be run for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 (as indexing cannot be used to check values). - for prob in deepcopy([oprob, sprob, dprob, nprob]) + for prob in deepcopy([oprob, sprob, dprob, nprob, ssprob, eoprob, esprob, edprob, enprob, essprob]) # Remake for all u0s. rp = remake(prob; u0 = [X => 1, Y => 2]) @test rp[[X, Y]] == [1, 2] @@ -243,10 +239,10 @@ let @test getu(sol, (XY,Y))(sol)[1] == getu(sol, (model.XY,model.Y))(sol)[1] == getu(sol, (:XY,:Y))(sol)[1] == (9, 5) # Get u values via idxs and functional call. - @test osol(0.0; idxs=X) == osol(0.0; idxs=model.X) == osol(0.0; idxs=:X) == 4 - @test osol(0.0; idxs=XY) == osol(0.0; idxs=model.XY) == osol(0.0; idxs=:XY) == 9 - @test osol(0.0; idxs = [XY,Y]) == osol(0.0; idxs = [model.XY,model.Y]) == osol(0.0; idxs = [:XY,:Y]) == [9, 5] - @test_broken osol(0.0; idxs = (XY,Y)) == osol(0.0; idxs = (model.XY,model.Y)) == osol(0.0; idxs = (:XY,:Y)) == (9, 5) # https://github.com/SciML/SciMLBase.jl/issues/711 + @test sol(0.0; idxs=X) == sol(0.0; idxs=model.X) == sol(0.0; idxs=:X) == 4 + @test sol(0.0; idxs=XY) == sol(0.0; idxs=model.XY) == sol(0.0; idxs=:XY) == 9 + @test sol(0.0; idxs = [XY,Y]) == sol(0.0; idxs = [model.XY,model.Y]) == sol(0.0; idxs = [:XY,:Y]) == [9, 5] + @test_broken sol(0.0; idxs = (XY,Y)) == sol(0.0; idxs = (model.XY,model.Y)) == sol(0.0; idxs = (:XY,:Y)) == (9, 5) # https://github.com/SciML/SciMLBase.jl/issues/711 # Get p values. @test sol.ps[kp] == sol.ps[model.kp] == sol.ps[:kp] == 1.0 From d25dd2205aa574ce23abbfd0dd72ffa52772e752 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 19:52:47 -0400 Subject: [PATCH 394/446] fix mtk input test file, add static array input tests --- test/upstream/mtk_problem_inputs.jl | 572 +++++++++++----------------- 1 file changed, 226 insertions(+), 346 deletions(-) diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index 61a09f0b9d..6264fb8f3d 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -1,8 +1,10 @@ +#! format: off + ### Prepares Tests ### # Fetch packages -using Catalyst, JumpProcesses, NonlinearSolve, OrdinaryDiffEq, Plots, SteadyStateDiffEq, StochasticDiffEq, Test -import ModelingToolkit: getp, getu, setp, setu +using Catalyst, JumpProcesses, NonlinearSolve, OrdinaryDiffEq, StaticArrays, SteadyStateDiffEq, + StochasticDiffEq, Test # Sets rnd number. using StableRNGs @@ -12,379 +14,257 @@ seed = rand(rng, 1:100) ### Basic Tests ### -# Prepares a model and its problems, integrators, and solutions. -begin - # Creates the model and unpacks its context. +# Prepares a models and initial conditions/parameters (of different forms) to be used as problem inputs. +begin model = @reaction_network begin - @observables XY ~ X + Y + @species Z(t) = Z0 + @parameters k2=0.5 Z0 (kp,kd), 0 <--> X (k1,k2), X <--> Y + (k1,k2), Y <--> Z end - @unpack XY, X, Y, kp, kd, k1, k2 = model - - # Sets problem inputs (to be used for all problem creations). - u0_vals = [X => 4, Y => 5] + @unpack X, Y, Z, kp, kd, k1, k2, Z0 = model + + u0_alts = [ + # Vectors not providing default values. + [X => 4, Y => 5], + [model.X => 4, model.Y => 5], + [:X => 4, :Y => 5], + # Vectors providing default values. + [X => 4, Y => 5, Z => 10], + [model.X => 4, model.Y => 5, model.Z => 10], + [:X => 4, :Y => 5, :Z => 10], + # Static vectors not providing default values. + SA[X => 4, Y => 5], + SA[model.X => 4, model.Y => 5], + SA[:X => 4, :Y => 5], + # Static vectors providing default values. + SA[X => 4, Y => 5, Z => 10], + SA[model.X => 4, model.Y => 5, model.Z => 10], + SA[:X => 4, :Y => 5, :Z => 10], + # Dicts not providing default values. + Dict([X => 4, Y => 5]), + Dict([model.X => 4, model.Y => 5]), + Dict([:X => 4, :Y => 5]), + # Dicts providing default values. + Dict([X => 4, Y => 5, Z => 10]), + Dict([model.X => 4, model.Y => 5, model.Z => 10]), + Dict([:X => 4, :Y => 5, :Z => 10]), + # Tuples not providing default values. + (X => 4, Y => 5), + (model.X => 4, model.Y => 5), + (:X => 4, :Y => 5), + # Tuples providing default values. + (X => 4, Y => 5, Z => 10), + (model.X => 4, model.Y => 5, model.Z => 10), + (:X => 4, :Y => 5, :Z => 10) + ] tspan = (0.0, 10.0) - p_vals = [kp => 1.0, kd => 0.1, k1 => 0.25, k2 => 0.5] - - # Creates problems. - oprob = ODEProblem(model, u0_vals, tspan, p_vals) - sprob = SDEProblem(model,u0_vals, tspan, p_vals) - dprob = DiscreteProblem(model, u0_vals, tspan, p_vals) - jprob = JumpProblem(model, deepcopy(dprob), Direct(); rng) - nprob = NonlinearProblem(model, u0_vals, p_vals) - ssprob = SteadyStateProblem(model, u0_vals, p_vals) - problems = [oprob, sprob, dprob, jprob, nprob, ssprob] - - # Creates an `EnsembleProblem` for each problem. - eoprob = EnsembleProblem(oprob) - esprob = EnsembleProblem(sprob) - edprob = EnsembleProblem(dprob) - ejprob = EnsembleProblem(jprob) - enprob = EnsembleProblem(nprob) - essprob = EnsembleProblem(ssprob) - eproblems = [eoprob, esprob, edprob, ejprob, enprob, essprob] - - # Creates integrators. - oint = init(oprob, Tsit5(); save_everystep = false) - sint = init(sprob, ImplicitEM(); save_everystep = false) - jint = init(jprob, SSAStepper()) - nint = init(nprob, NewtonRaphson(); save_everystep = false) - @test_broken ssint = init(ssprob, DynamicSS(Tsit5()); save_everystep = false) # https://github.com/SciML/SciMLBase.jl/issues/660 - integrators = [oint, sint, jint, nint] - - # Creates solutions. - osol = solve(oprob, Tsit5()) - ssol = solve(sprob, ImplicitEM(); seed) - jsol = solve(jprob, SSAStepper(); seed) - nsol = solve(nprob, NewtonRaphson()) - sssol = solve(nprob, DynamicSS(Tsit5())) - sols = [osol, ssol, jsol, nsol, sssol] + p_alts = [ + # Vectors not providing default values. + [kp => 1.0, kd => 0.1, k1 => 0.25, Z0 => 10], + [model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.Z0 => 10], + [:kp => 1.0, :kd => 0.1, :k1 => 0.25, :Z0 => 10], + # Vectors providing default values. + [kp => 1.0, kd => 0.1, k1 => 0.25, k2 => 0.5, Z0 => 10], + [model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.k2 => 0.5, model.Z0 => 10], + [:kp => 1.0, :kd => 0.1, :k1 => 0.25, :k2 => 0.5, :Z0 => 10], + # Static vectors not providing default values. + SA[kp => 1.0, kd => 0.1, k1 => 0.25, Z0 => 10], + SA[model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.Z0 => 10], + SA[:kp => 1.0, :kd => 0.1, :k1 => 0.25, :Z0 => 10], + # Static vectors providing default values. + SA[kp => 1.0, kd => 0.1, k1 => 0.25, k2 => 0.5, Z0 => 10], + SA[model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.k2 => 0.5, model.Z0 => 10], + SA[:kp => 1.0, :kd => 0.1, :k1 => 0.25, :k2 => 0.5, :Z0 => 10], + # Dicts not providing default values. + Dict([kp => 1.0, kd => 0.1, k1 => 0.25, Z0 => 10]), + Dict([model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.Z0 => 10]), + Dict([:kp => 1.0, :kd => 0.1, :k1 => 0.25, :Z0 => 10]), + # Dicts providing default values. + Dict([kp => 1.0, kd => 0.1, k1 => 0.25, k2 => 0.5, Z0 => 10]), + Dict([model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.k2 => 0.5, model.Z0 => 10]), + Dict([:kp => 1.0, :kd => 0.1, :k1 => 0.25, :k2 => 0.5, :Z0 => 10]), + # Tuples not providing default values. + (kp => 1.0, kd => 0.1, k1 => 0.25, Z0 => 10), + (model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.Z0 => 10), + (:kp => 1.0, :kd => 0.1, :k1 => 0.25, :Z0 => 10), + # Tuples providing default values. + (kp => 1.0, kd => 0.1, k1 => 0.25, k2 => 0.5, Z0 => 10), + (model.kp => 1.0, model.kd => 0.1, model.k1 => 0.25, model.k2 => 0.5, model.Z0 => 10), + (:kp => 1.0, :kd => 0.1, :k1 => 0.25, :k2 => 0.5, :Z0 => 10), + ] end -# Tests problem indexing and updating. +# Perform ODE simulations (singular and ensemble). let - @test_broken false # A few cases fails for JumpProblem: https://github.com/SciML/ModelingToolkit.jl/issues/2838 - @test_broken false # A few cases fails for SteadyStateProblem: https://github.com/SciML/SciMLBase.jl/issues/660 - @test_broken false # Most cases broken for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 - for prob in deepcopy([oprob, sprob, dprob, nprob]) - # Get u values (including observables). - @test prob[X] == prob[model.X] == prob[:X] == 4 - @test prob[XY] == prob[model.XY] == prob[:XY] == 9 - @test prob[[XY,Y]] == prob[[model.XY,model.Y]] == prob[[:XY,:Y]] == [9, 5] - @test prob[(XY,Y)] == prob[(model.XY,model.Y)] == prob[(:XY,:Y)] == (9, 5) - @test getu(prob, X)(prob) == getu(prob, model.X)(prob) == getu(prob, :X)(prob) == 4 - @test getu(prob, XY)(prob) == getu(prob, model.XY)(prob) == getu(prob, :XY)(prob) == 9 - @test getu(prob, [XY,Y])(prob) == getu(prob, [model.XY,model.Y])(prob) == getu(prob, [:XY,:Y])(prob) == [9, 5] - @test getu(prob, (XY,Y))(prob) == getu(prob, (model.XY,model.Y))(prob) == getu(prob, (:XY,:Y))(prob) == (9, 5) - - # Set u values. - prob[X] = 20 - @test prob[X] == 20 - prob[model.X] = 30 - @test prob[X] == 30 - prob[:X] = 40 - @test prob[X] == 40 - setu(prob, X)(prob, 50) - @test prob[X] == 50 - setu(prob, model.X)(prob, 60) - @test prob[X] == 60 - setu(prob, :X)(prob, 70) - @test prob[X] == 70 - - # Get p values. - @test prob.ps[kp] == prob.ps[model.kp] == prob.ps[:kp] == 1.0 - @test prob.ps[[k1,k2]] == prob.ps[[model.k1,model.k2]] == prob.ps[[:k1,:k2]] == [0.25, 0.5] - @test prob.ps[(k1,k2)] == prob.ps[(model.k1,model.k2)] == prob.ps[(:k1,:k2)] == (0.25, 0.5) - @test getp(prob, kp)(prob) == getp(prob, model.kp)(prob) == getp(prob, :kp)(prob) == 1.0 - @test getp(prob, [k1,k2])(prob) == getp(prob, [model.k1,model.k2])(prob) == getp(prob, [:k1,:k2])(prob) == [0.25, 0.5] - @test getp(prob, (k1,k2))(prob) == getp(prob, (model.k1,model.k2))(prob) == getp(prob, (:k1,:k2))(prob) == (0.25, 0.5) - - # Set p values. - prob.ps[kp] = 2.0 - @test prob.ps[kp] == 2.0 - prob.ps[model.kp] = 3.0 - @test prob.ps[kp] == 3.0 - prob.ps[:kp] = 4.0 - @test prob.ps[kp] == 4.0 - setp(prob, kp)(prob, 5.0) - @test prob.ps[kp] == 5.0 - setp(prob, model.kp)(prob, 6.0) - @test prob.ps[kp] == 6.0 - setp(prob, :kp)(prob, 7.0) - @test prob.ps[kp] == 7.0 + # Creates normal and ensemble problems. + base_oprob = ODEProblem(model, u0_alts[1], tspan, p_alts[1]) + base_sol = solve(base_oprob, Tsit5(); saveat = 1.0) + base_eprob = EnsembleProblem(base_oprob) + base_esol = solve(base_eprob, Tsit5(); trajectories = 2, saveat = 1.0) + + # Simulates problems for all input types, checking that identical solutions are found. + for u0 in u0_alts, p in p_alts + oprob = remake(base_oprob; u0, p) + @test base_sol == solve(oprob, Tsit5(); saveat = 1.0) + eprob = remake(base_eprob; u0, p) + @test base_esol == solve(eprob, Tsit5(); trajectories = 2, saveat = 1.0) end end -# Test remake function. +# Perform SDE simulations (singular and ensemble). let - @test_broken false # Cannot check result for JumpProblem: https://github.com/SciML/ModelingToolkit.jl/issues/2838 - @test_broken false # Cannot deepcopy SteadyStateProblem :https://github.com/SciML/ModelingToolkit.jl/issues/2837 - @test_broken false # Currently cannot be run for Ensemble problems: https://github.com/SciML/SciMLBase.jl/issues/661 (as indexing cannot be used to check values). - for prob in deepcopy([oprob, sprob, dprob, nprob]) - # Remake for all u0s. - rp = remake(prob; u0 = [X => 1, Y => 2]) - @test rp[[X, Y]] == [1, 2] - rp = remake(prob; u0 = [model.X => 3, model.Y => 4]) - @test rp[[X, Y]] == [3, 4] - rp = remake(prob; u0 = [:X => 5, :Y => 6]) - @test rp[[X, Y]] == [5, 6] - - # Remake for a single u0. - rp = remake(prob; u0 = [Y => 7]) - @test rp[[X, Y]] == [4, 7] - rp = remake(prob; u0 = [model.Y => 8]) - @test rp[[X, Y]] == [4, 8] - rp = remake(prob; u0 = [:Y => 9]) - @test rp[[X, Y]] == [4, 9] - - # Remake for all ps. - rp = remake(prob; p = [kp => 1.0, kd => 2.0, k1 => 3.0, k2 => 4.0]) - @test rp.ps[[kp, kd, k1, k2]] == [1.0, 2.0, 3.0, 4.0] - rp = remake(prob; p = [model.kp => 5.0, model.kd => 6.0, model.k1 => 7.0, model.k2 => 8.0]) - @test rp.ps[[kp, kd, k1, k2]] == [5.0, 6.0, 7.0, 8.0] - rp = remake(prob; p = [:kp => 9.0, :kd => 10.0, :k1 => 11.0, :k2 => 12.0]) - @test rp.ps[[kp, kd, k1, k2]] == [9.0, 10.0, 11.0, 12.0] - - # Remake for a single p. - rp = remake(prob; p = [k2 => 13.0]) - @test rp.ps[[kp, kd, k1, k2]] == [1.0, 0.1, 0.25, 13.0] - rp = remake(prob; p = [model.k2 => 14.0]) - @test rp.ps[[kp, kd, k1, k2]] == [1.0, 0.1, 0.25, 14.0] - rp = remake(prob; p = [:k2 => 15.0]) - @test rp.ps[[kp, kd, k1, k2]] == [1.0, 0.1, 0.25, 15.0] + # Creates normal and ensemble problems. + base_sprob = SDEProblem(model, u0_alts[1], tspan, p_alts[1]) + base_sol = solve(base_sprob, ImplicitEM(); seed, saveat = 1.0) + base_eprob = EnsembleProblem(base_sprob) + base_esol = solve(base_eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) + + # Simulates problems for all input types, checking that identical solutions are found. + for u0 in u0_alts, p in p_alts + sprob = remake(base_sprob; u0, p) + @test base_sol == solve(sprob, ImplicitEM(); seed, saveat = 1.0) + eprob = remake(base_eprob; u0, p) + @test base_esol == solve(eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) end end -# Test integrator indexing. +# Perform jump simulations (singular and ensemble). let - @test_broken false # NOTE: Cannot even create a `ssint` (https://github.com/SciML/SciMLBase.jl/issues/660). - for int in deepcopy([oint, sint, jint, nint]) - # Get u values. - @test int[X] == int[model.X] == int[:X] == 4 - @test int[XY] == int[model.XY] == int[:XY] == 9 - @test int[[XY,Y]] == int[[model.XY,model.Y]] == int[[:XY,:Y]] == [9, 5] - @test int[(XY,Y)] == int[(model.XY,model.Y)] == int[(:XY,:Y)] == (9, 5) - @test getu(int, X)(int) == getu(int, model.X)(int) == getu(int, :X)(int) == 4 - @test getu(int, XY)(int) == getu(int, model.XY)(int) == getu(int, :XY)(int) == 9 - @test getu(int, [XY,Y])(int) == getu(int, [model.XY,model.Y])(int) == getu(int, [:XY,:Y])(int) == [9, 5] - @test getu(int, (XY,Y))(int) == getu(int, (model.XY,model.Y))(int) == getu(int, (:XY,:Y))(int) == (9, 5) - - # Set u values. - int[X] = 20 - @test int[X] == 20 - int[model.X] = 30 - @test int[X] == 30 - int[:X] = 40 - @test int[X] == 40 - setu(int, X)(int, 50) - @test int[X] == 50 - setu(int, model.X)(int, 60) - @test int[X] == 60 - setu(int, :X)(int, 70) - @test int[X] == 70 - - # Get p values. - @test int.ps[kp] == int.ps[model.kp] == int.ps[:kp] == 1.0 - @test int.ps[[k1,k2]] == int.ps[[model.k1,model.k2]] == int.ps[[:k1,:k2]] == [0.25, 0.5] - @test int.ps[(k1,k2)] == int.ps[(model.k1,model.k2)] == int.ps[(:k1,:k2)] == (0.25, 0.5) - @test getp(int, kp)(int) == getp(int, model.kp)(int) == getp(int, :kp)(int) == 1.0 - @test getp(int, [k1,k2])(int) == getp(int, [model.k1,model.k2])(int) == getp(int, [:k1,:k2])(int) == [0.25, 0.5] - @test getp(int, (k1,k2))(int) == getp(int, (model.k1,model.k2))(int) == getp(int, (:k1,:k2))(int) == (0.25, 0.5) - - # Set p values. - int.ps[kp] = 2.0 - @test int.ps[kp] == 2.0 - int.ps[model.kp] = 3.0 - @test int.ps[kp] == 3.0 - int.ps[:kp] = 4.0 - @test int.ps[kp] == 4.0 - setp(int, kp)(int, 5.0) - @test int.ps[kp] == 5.0 - setp(int, model.kp)(int, 6.0) - @test int.ps[kp] == 6.0 - setp(int, :kp)(int, 7.0) - @test int.ps[kp] == 7.0 + # Creates normal and ensemble problems. + base_dprob = DiscreteProblem(model, u0_alts[1], tspan, p_alts[1]) + base_jprob = JumpProblem(model, base_dprob, Direct(); rng) + base_sol = solve(base_jprob, SSAStepper(); seed, saveat = 1.0) + base_eprob = EnsembleProblem(base_jprob) + base_esol = solve(base_eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) + + # Simulates problems for all input types, checking that identical solutions are found. + for u0 in u0_alts, p in p_alts + jprob = remake(base_jprob; u0, p) + @test base_sol == solve(base_jprob, SSAStepper(); seed, saveat = 1.0) + eprob = remake(base_eprob; u0, p) + @test base_esol == solve(eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) end end -# Test solve's save_idxs argument. -# Currently, `save_idxs` is broken with symbolic stuff (https://github.com/SciML/ModelingToolkit.jl/issues/1761). -let - for (prob, solver) in zip(deepcopy([oprob, sprob, jprob]), [Tsit5(), ImplicitEM(), SSAStepper()]) - # Save single variable - @test_broken solve(prob, solver; seed, save_idxs=X)[X][1] == 4 - @test_broken solve(prob, solver; seed, save_idxs=model.X)[X][1] == 4 - @test_broken solve(prob, solver; seed, save_idxs=:X)[X][1] == 4 - - # Save observable. - @test_broken solve(prob, solver; seed, save_idxs=XY)[XY][1] == 9 - @test_broken solve(prob, solver; seed, save_idxs=model.XY)[XY][1] == 9 - @test_broken solve(prob, solver; seed, save_idxs=:XY)[XY][1] == 9 - - # Save vector of stuff. - @test_broken solve(prob, solver; seed, save_idxs=[XY,Y])[[XY,Y]][1] == [9, 5] - @test_broken solve(prob, solver; seed, save_idxs=[model.XY,model.Y])[[model.XY,model.Y]][1] == [9, 5] - @test_broken solve(prob, solver; seed, save_idxs=[:XY,:Y])[[:XY,:Y]][1] == [9, 5] +# Solves a nonlinear problem (EnsembleProblems are not possible for these). +let + base_nlprob = NonlinearProblem(model, u0_alts[1], p_alts[1]) + base_sol = solve(base_nlprob, NewtonRaphson()) + for u0 in u0_alts, p in p_alts + nlprob = remake(base_nlprob; u0, p) + @test base_sol == solve(nlprob, NewtonRaphson()) end end -# Tests solution indexing. +# Perform steady state simulations (singular and ensemble). let - for sol in deepcopy([osol, ssol, jsol]) - # Get u values. - @test sol[X][1] == sol[model.X][1] == sol[:X][1] == 4 - @test sol[XY][1] == sol[model.XY][1] == sol[:XY][1] == 9 - @test sol[[XY,Y]][1] == sol[[model.XY,model.Y]][1] == sol[[:XY,:Y]][1] == [9, 5] - @test sol[(XY,Y)][1] == sol[(model.XY,model.Y)][1] == sol[(:XY,:Y)][1] == (9, 5) - @test getu(sol, X)(sol)[1] == getu(sol, model.X)(sol)[1] == getu(sol, :X)(sol)[1] == 4 - @test getu(sol, XY)(sol)[1] == getu(sol, model.XY)(sol)[1] == getu(sol, :XY)(sol)[1] == 9 - @test getu(sol, [XY,Y])(sol)[1] == getu(sol, [model.XY,model.Y])(sol)[1] == getu(sol, [:XY,:Y])(sol)[1] == [9, 5] - @test getu(sol, (XY,Y))(sol)[1] == getu(sol, (model.XY,model.Y))(sol)[1] == getu(sol, (:XY,:Y))(sol)[1] == (9, 5) - - # Get u values via idxs and functional call. - @test osol(0.0; idxs=X) == osol(0.0; idxs=model.X) == osol(0.0; idxs=:X) == 4 - @test osol(0.0; idxs=XY) == osol(0.0; idxs=model.XY) == osol(0.0; idxs=:XY) == 9 - @test osol(0.0; idxs = [XY,Y]) == osol(0.0; idxs = [model.XY,model.Y]) == osol(0.0; idxs = [:XY,:Y]) == [9, 5] - @test_broken osol(0.0; idxs = (XY,Y)) == osol(0.0; idxs = (model.XY,model.Y)) == osol(0.0; idxs = (:XY,:Y)) == (9, 5) # https://github.com/SciML/SciMLBase.jl/issues/711 - - # Get p values. - @test sol.ps[kp] == sol.ps[model.kp] == sol.ps[:kp] == 1.0 - @test sol.ps[[k1,k2]] == sol.ps[[model.k1,model.k2]] == sol.ps[[:k1,:k2]] == [0.25, 0.5] - @test sol.ps[(k1,k2)] == sol.ps[(model.k1,model.k2)] == sol.ps[(:k1,:k2)] == (0.25, 0.5) - @test getp(sol, kp)(sol) == getp(sol, model.kp)(sol) == getp(sol, :kp)(sol) == 1.0 - @test getp(sol, [k1,k2])(sol) == getp(sol, [model.k1,model.k2])(sol) == getp(sol, [:k1,:k2])(sol) == [0.25, 0.5] - @test getp(sol, (k1,k2))(sol) == getp(sol, (model.k1,model.k2))(sol) == getp(sol, (:k1,:k2))(sol) == (0.25, 0.5) + # Creates normal and ensemble problems. + base_ssprob = SteadyStateProblem(model, u0_alts[1], p_alts[1]) + base_sol = solve(base_ssprob, DynamicSS(Tsit5())) + base_eprob = EnsembleProblem(base_ssprob) + base_esol = solve(base_eprob, DynamicSS(Tsit5()); trajectories = 2) + + # Simulates problems for all input types, checking that identical solutions are found. + for u0 in u0_alts, p in p_alts + ssprob = remake(base_ssprob; u0, p) + @test base_sol == solve(ssprob, DynamicSS(Tsit5())) + eprob = remake(base_eprob; u0, p) + @test base_esol == solve(eprob, DynamicSS(Tsit5()); trajectories = 2) end - - # Handles nonlinear and steady state solutions differently. - let - for sol in deepcopy([nsol, sssol]) - # Get u values. - @test sol[X] == sol[model.X] == sol[:X] - @test sol[XY] == sol[model.XY][1] == sol[:XY] - @test sol[[XY,Y]] == sol[[model.XY,model.Y]] == sol[[:XY,:Y]] - @test sol[(XY,Y)] == sol[(model.XY,model.Y)] == sol[(:XY,:Y)] - @test getu(sol, X)(sol) == getu(sol, model.X)(sol)[1] == getu(sol, :X)(sol) - @test getu(sol, XY)(sol) == getu(sol, model.XY)(sol)[1] == getu(sol, :XY)(sol) - @test getu(sol, [XY,Y])(sol) == getu(sol, [model.XY,model.Y])(sol) == getu(sol, [:XY,:Y])(sol) - @test_broken getu(sol, (XY,Y))(sol) == getu(sol, (model.XY,model.Y))(sol) == getu(sol, (:XY,:Y))(sol)[1] # https://github.com/SciML/SciMLBase.jl/issues/710 - - # Get p values. - @test sol.ps[kp] == sol.ps[model.kp] == sol.ps[:kp] - @test sol.ps[[k1,k2]] == sol.ps[[model.k1,model.k2]] == sol.ps[[:k1,:k2]] - @test sol.ps[(k1,k2)] == sol.ps[(model.k1,model.k2)] == sol.ps[(:k1,:k2)] - @test getp(sol, kp)(sol) == getp(sol, model.kp)(sol) == getp(sol, :kp)(sol) - @test getp(sol, [k1,k2])(sol) == getp(sol, [model.k1,model.k2])(sol) == getp(sol, [:k1,:k2])(sol) - @test getp(sol, (k1,k2))(sol) == getp(sol, (model.k1,model.k2))(sol) == getp(sol, (:k1,:k2))(sol) - end - end -end - -# Tests plotting. -let - for sol in deepcopy([osol, jsol, ssol]) - # Single variable. - @test length(plot(sol; idxs = X).series_list) == 1 - @test length(plot(sol; idxs = XY).series_list) == 1 - @test length(plot(sol; idxs = model.X).series_list) == 1 - @test length(plot(sol; idxs = model.XY).series_list) == 1 - @test length(plot(sol; idxs = :X).series_list) == 1 - @test length(plot(sol; idxs = :XY).series_list) == 1 - - # As vector. - @test length(plot(sol; idxs = [X,Y]).series_list) == 2 - @test length(plot(sol; idxs = [XY,Y]).series_list) == 2 - @test length(plot(sol; idxs = [model.X,model.Y]).series_list) == 2 - @test length(plot(sol; idxs = [model.XY,model.Y]).series_list) == 2 - @test length(plot(sol; idxs = [:X,:Y]).series_list) == 2 - @test length(plot(sol; idxs = [:XY,:Y]).series_list) == 2 - - # As tuple. - @test length(plot(sol; idxs = (X, Y)).series_list) == 1 - @test length(plot(sol; idxs = (XY, Y)).series_list) == 1 - @test length(plot(sol; idxs = (model.X, model.Y)).series_list) == 1 - @test length(plot(sol; idxs = (model.XY, model.Y)).series_list) == 1 - @test length(plot(sol; idxs = (:X, :Y)).series_list) == 1 - @test length(plot(sol; idxs = (:XY, :Y)).series_list) == 1 - end end -### Tests For Hierarchical System ### +### Checks Errors On Faulty Inputs ### -# TODO - -### Mass Action Jump Rate Updating Correctness ### - -# Checks that the rates of mass action jumps are correctly updated after parameter values are changed. +# Checks various erroneous problem inputs, ensuring that these throw errors. let - # Creates the model. + # Declares the model. rn = @reaction_network begin - p1*p2, A + B --> C + (k1,k2), X1 <--> X2 + end + @unpack k1, k2, X1, X2 = rn + t = default_t() + @species X3(t) + @parameters k3 + + # Declares valid initial conditions and parameter values + u0_valid = [X1 => 1, X2 => 2] + ps_valid = [k1 => 0.5, k2 => 0.1] + + # Declares invalid initial conditions and parameters. This includes both cases where values are + # missing, or additional ones are given. Includes vector/Tuple/Dict forms. + u0s_invalid = [ + # Missing a value. + [X1 => 1], + [rn.X1 => 1], + [:X1 => 1], + SA[X1 => 1], + SA[rn.X1 => 1], + SA[:X1 => 1], + Dict([X1 => 1]), + Dict([rn.X1 => 1]), + Dict([:X1 => 1]), + (X1 => 1), + (rn.X1 => 1), + (:X1 => 1), + # Contain an additional value. + [X1 => 1, X2 => 2, X3 => 3], + [:X1 => 1, :X2 => 2, :X3 => 3], + SA[X1 => 1, X2 => 2, X3 => 3], + SA[:X1 => 1, :X2 => 2, :X3 => 3], + Dict([X1 => 1, X2 => 2, X3 => 3]), + Dict([:X1 => 1, :X2 => 2, :X3 => 3]), + (X1 => 1, X2 => 2, X3 => 3), + (:X1 => 1, :X2 => 2, :X3 => 3) + ] + ps_invalid = [ + # Missing a value. + [k1 => 1.0], + [rn.k1 => 1.0], + [:k1 => 1.0], + SA[k1 => 1.0], + SA[rn.k1 => 1.0], + SA[:k1 => 1.0], + Dict([k1 => 1.0]), + Dict([rn.k1 => 1.0]), + Dict([:k1 => 1.0]), + (k1 => 1.0), + (rn.k1 => 1.0), + (:k1 => 1.0), + # Contain an additional value. + [k1 => 1.0, k2 => 2.0, k3 => 3.0], + [:k1 => 1.0, :k2 => 2.0, :k3 => 3.0], + SA[k1 => 1.0, k2 => 2.0, k3 => 3.0], + SA[:k1 => 1.0, :k2 => 2.0, :k3 => 3.0], + Dict([k1 => 1.0, k2 => 2.0, k3 => 3.0]), + Dict([:k1 => 1.0, :k2 => 2.0, :k3 => 3.0]), + (k1 => 1.0, k2 => 2.0, k3 => 3.0), + (:k1 => 1.0, :k2 => 2.0, :k3 => 3.0) + ] + + # Loops through all potential parameter sets, checking their inputs yield errors. + for ps in [[ps_valid]; ps_invalid], u0 in [[u0_valid]; u0s_invalid] + # Handles problems with/without tspan separately. Special check ensuring that valid inputs passes. + for XProblem in [ODEProblem, SDEProblem, DiscreteProblem] + if isequal(ps, ps_valid) && isequal(u0, u0_valid) + XProblem(rn, u0, (0.0, 1.0), ps); @test true; + else + # Several of these cases do not throw errors (https://github.com/SciML/ModelingToolkit.jl/issues/2624). + @test_broken false + continue + @test_throws Exception XProblem(rn, u0, (0.0, 1.0), ps) + end + end + for XProblem in [NonlinearProblem, SteadyStateProblem] + if isequal(ps, ps_valid) && isequal(u0, u0_valid) + XProblem(rn, u0, ps); @test true; + else + @test_broken false + continue + @test_throws Exception XProblem(rn, u0, ps) + end + end end - @unpack p1, p2 = rn - - # Creates a JumpProblem and integrator. Checks that the initial mass action rate is correct. - u0 = [:A => 1, :B => 2, :C => 3] - ps = [:p1 => 3.0, :p2 => 2.0] - dprob = DiscreteProblem(rn, u0, (0.0, 1.0), ps) - jprob = JumpProblem(rn, dprob, Direct()) - jint = init(jprob, SSAStepper()) - @test jprob.massaction_jump.scaled_rates[1] == 6.0 - - # Checks that the mass action rate is correctly updated after normal indexing. - jprob.ps[p1] = 4.0 - @test jprob.massaction_jump.scaled_rates[1] == 8.0 - jprob.ps[rn.p1] = 5.0 - @test jprob.massaction_jump.scaled_rates[1] == 10.0 - jprob.ps[:p1] = 6.0 - @test jprob.massaction_jump.scaled_rates[1] == 12.0 - setp(jprob, p1)(jprob, 7.0) - @test jprob.massaction_jump.scaled_rates[1] == 14.0 - setp(jprob, rn.p1)(jprob, 8.0) - @test jprob.massaction_jump.scaled_rates[1] == 16.0 - setp(jprob, :p1)(jprob, 3.0) - @test jprob.massaction_jump.scaled_rates[1] == 6.0 - - # Check that the mass action rate is correctly updated when `remake` is used. - # Checks both when partial and full parameter vectors are provided to `remake`. - @test remake(jprob; p = [p1 => 4.0]).massaction_jump.scaled_rates[1] == 8.0 - @test remake(jprob; p = [rn.p1 => 5.0]).massaction_jump.scaled_rates[1] == 10.0 - @test remake(jprob; p = [:p1 => 6.0]).massaction_jump.scaled_rates[1] == 12.0 - @test remake(jprob; p = [p1 => 4.0, p2 => 3.0]).massaction_jump.scaled_rates[1] == 12.0 - @test remake(jprob; p = [rn.p1 => 5.0, rn.p2 => 4.0]).massaction_jump.scaled_rates[1] == 20.0 - @test remake(jprob; p = [:p1 => 6.0, :p2 => 5.0]).massaction_jump.scaled_rates[1] == 30.0 - - # Checks that updating an integrators parameter values does not affect mass action rate until after - # `reset_aggregated_jumps!` have been applied as well (wt which point the correct rate is achieved). - jint.ps[p1] = 4.0 - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 30.0 - reset_aggregated_jumps!(jint) - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 8.0 - - jint.ps[rn.p1] = 5.0 - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 8.0 - reset_aggregated_jumps!(jint) - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 10.0 - - jint.ps[:p1] = 6.0 - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 10.0 - reset_aggregated_jumps!(jint) - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 12.0 - - setp(jint, p1)(jint, 7.0) - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 12.0 - reset_aggregated_jumps!(jint) - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 14.0 - - setp(jint, rn.p1)(jint, 8.0) - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 14.0 - reset_aggregated_jumps!(jint) - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 16.0 - - setp(jint, :p1)(jint, 3.0) - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 16.0 - reset_aggregated_jumps!(jint) - @test jint.cb.condition.ma_jumps.scaled_rates[1] == 6.0 end From 5c0bd3b1b0b802ef512898838a1de99303ad4d7e Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 19:55:10 -0400 Subject: [PATCH 395/446] up --- test/upstream/mtk_problem_inputs.jl | 196 ++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index 6264fb8f3d..ffe2037519 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -175,6 +175,202 @@ let end end +### Vector Species/Parameters Tests ### + +begin + # Declares the model (with vector species/parameters, with/without default values, and observables). + t = default_t() + @species X(t)[1:2] Y(t)[1:2] = [10.0, 20.0] XY(t)[1:2] + @parameters p[1:2] d[1:2] = [0.2, 0.5] + rxs = [ + Reaction(p[1], [], [X[1]]), + Reaction(p[2], [], [X[2]]), + Reaction(d[1], [X[1]], []), + Reaction(d[2], [X[2]], []), + Reaction(p[1], [], [Y[1]]), + Reaction(p[2], [], [Y[2]]), + Reaction(d[1], [Y[1]], []), + Reaction(d[2], [Y[2]], []) + ] + observed = [XY[1] ~ X[1] + Y[1], XY[2] ~ X[2] + Y[2]] + @named model_vec = ReactionSystem(rxs, t; observed) + model_vec = complete(model_vec) + + # Declares various u0 versions (scalarised and vector forms). + u0_alts_vec = [ + # Vectors not providing default values. + [X => [1.0, 2.0]], + [X[1] => 1.0, X[2] => 2.0], + [model_vec.X => [1.0, 2.0]], + [model_vec.X[1] => 1.0, model_vec.X[2] => 2.0], + [:X => [1.0, 2.0]], + # Vectors providing default values. + [X => [1.0, 2.0], Y => [10.0, 20.0]], + [X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0], + [model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]], + [model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0], + [:X => [1.0, 2.0], :Y => [10.0, 20.0]], + # Static vectors not providing default values. + SA[X => [1.0, 2.0]], + SA[X[1] => 1.0, X[2] => 2.0], + SA[model_vec.X => [1.0, 2.0]], + SA[model_vec.X[1] => 1.0, model_vec.X[2] => 2.0], + SA[:X => [1.0, 2.0]], + # Static vectors providing default values. + SA[X => [1.0, 2.0], Y => [10.0, 20.0]], + SA[X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0], + SA[model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]], + SA[model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0], + SA[:X => [1.0, 2.0], :Y => [10.0, 20.0]], + # Dicts not providing default values. + Dict([X => [1.0, 2.0]]), + Dict([X[1] => 1.0, X[2] => 2.0]), + Dict([model_vec.X => [1.0, 2.0]]), + Dict([model_vec.X[1] => 1.0, model_vec.X[2] => 2.0]), + Dict([:X => [1.0, 2.0]]), + # Dicts providing default values. + Dict([X => [1.0, 2.0], Y => [10.0, 20.0]]), + Dict([X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0]), + Dict([model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]]), + Dict([model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0]), + Dict([:X => [1.0, 2.0], :Y => [10.0, 20.0]]), + # Tuples not providing default values. + (X => [1.0, 2.0]), + (X[1] => 1.0, X[2] => 2.0), + (model_vec.X => [1.0, 2.0]), + (model_vec.X[1] => 1.0, model_vec.X[2] => 2.0), + (:X => [1.0, 2.0]), + # Tuples providing default values. + (X => [1.0, 2.0], Y => [10.0, 20.0]), + (X[1] => 1.0, X[2] => 2.0, Y[1] => 10.0, Y[2] => 20.0), + (model_vec.X => [1.0, 2.0], model_vec.Y => [10.0, 20.0]), + (model_vec.X[1] => 1.0, model_vec.X[2] => 2.0, model_vec.Y[1] => 10.0, model_vec.Y[2] => 20.0), + (:X => [1.0, 2.0], :Y => [10.0, 20.0]), + ] + + # Declares various ps versions (vector forms only). + p_alts_vec = [ + # Vectors not providing default values. + [p => [1.0, 2.0]], + [model_vec.p => [1.0, 2.0]], + [:p => [1.0, 2.0]], + # Vectors providing default values. + [p => [4.0, 5.0], d => [0.2, 0.5]], + [model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]], + [:p => [4.0, 5.0], :d => [0.2, 0.5]], + # Static vectors not providing default values. + SA[p => [1.0, 2.0]], + SA[model_vec.p => [1.0, 2.0]], + SA[:p => [1.0, 2.0]], + # Static vectors providing default values. + SA[p => [4.0, 5.0], d => [0.2, 0.5]], + SA[model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]], + SA[:p => [4.0, 5.0], :d => [0.2, 0.5]], + # Dicts not providing default values. + Dict([p => [1.0, 2.0]]), + Dict([model_vec.p => [1.0, 2.0]]), + Dict([:p => [1.0, 2.0]]), + # Dicts providing default values. + Dict([p => [4.0, 5.0], d => [0.2, 0.5]]), + Dict([model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]]), + Dict([:p => [4.0, 5.0], :d => [0.2, 0.5]]), + # Tuples not providing default values. + (p => [1.0, 2.0]), + (model_vec.p => [1.0, 2.0]), + (:p => [1.0, 2.0]), + # Tuples providing default values. + (p => [4.0, 5.0], d => [0.2, 0.5]), + (model_vec.p => [4.0, 5.0], model_vec.d => [0.2, 0.5]), + (:p => [4.0, 5.0], :d => [0.2, 0.5]), + ] + + # Declares a timespan. + tspan = (0.0, 10.0) +end + +# Perform ODE simulations (singular and ensemble). +let + # Creates normal and ensemble problems. + base_oprob = ODEProblem(model_vec, u0_alts_vec[1], tspan, p_alts_vec[1]) + base_sol = solve(base_oprob, Tsit5(); saveat = 1.0) + base_eprob = EnsembleProblem(base_oprob) + base_esol = solve(base_eprob, Tsit5(); trajectories = 2, saveat = 1.0) + + # Simulates problems for all input types, checking that identical solutions are found. + @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). + # for u0 in u0_alts_vec, p in p_alts_vec + # oprob = remake(base_oprob; u0, p) + # @test base_sol == solve(oprob, Tsit5(); saveat = 1.0) + # eprob = remake(base_eprob; u0, p) + # @test base_esol == solve(eprob, Tsit5(); trajectories = 2, saveat = 1.0) + # end +end + +# Perform SDE simulations (singular and ensemble). +let + # Creates normal and ensemble problems. + base_sprob = SDEProblem(model_vec, u0_alts_vec[1], tspan, p_alts_vec[1]) + base_sol = solve(base_sprob, ImplicitEM(); seed, saveat = 1.0) + base_eprob = EnsembleProblem(base_sprob) + base_esol = solve(base_eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) + + # Simulates problems for all input types, checking that identical solutions are found. + @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). + # for u0 in u0_alts_vec, p in p_alts_vec + # sprob = remake(base_sprob; u0, p) + # @test base_sol == solve(sprob, ImplicitEM(); seed, saveat = 1.0) + # eprob = remake(base_eprob; u0, p) + # @test base_esol == solve(eprob, ImplicitEM(); seed, trajectories = 2, saveat = 1.0) + # end +end + +# Perform jump simulations (singular and ensemble). +let + # Creates normal and ensemble problems. + base_dprob = DiscreteProblem(model_vec, u0_alts_vec[1], tspan, p_alts_vec[1]) + base_jprob = JumpProblem(model_vec, base_dprob, Direct(); rng) + base_sol = solve(base_jprob, SSAStepper(); seed, saveat = 1.0) + base_eprob = EnsembleProblem(base_jprob) + base_esol = solve(base_eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) + + # Simulates problems for all input types, checking that identical solutions are found. + @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). + # for u0 in u0_alts_vec, p in p_alts_vec + # jprob = remake(base_jprob; u0, p) + # @test base_sol == solve(base_jprob, SSAStepper(); seed, saveat = 1.0) + # eprob = remake(base_eprob; u0, p) + # @test base_esol == solve(eprob, SSAStepper(); seed, trajectories = 2, saveat = 1.0) + # end +end + +# Solves a nonlinear problem (EnsembleProblems are not possible for these). +let + base_nlprob = NonlinearProblem(model_vec, u0_alts_vec[1], p_alts_vec[1]) + base_sol = solve(base_nlprob, NewtonRaphson()) + @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). + # for u0 in u0_alts_vec, p in p_alts_vec + # nlprob = remake(base_nlprob; u0, p) + # @test base_sol == solve(nlprob, NewtonRaphson()) + # end +end + +# Perform steady state simulations (singular and ensemble). +let + # Creates normal and ensemble problems. + base_ssprob = SteadyStateProblem(model_vec, u0_alts_vec[1], p_alts_vec[1]) + base_sol = solve(base_ssprob, DynamicSS(Tsit5())) + base_eprob = EnsembleProblem(base_ssprob) + base_esol = solve(base_eprob, DynamicSS(Tsit5()); trajectories = 2) + + # Simulates problems for all input types, checking that identical solutions are found. + @test_broken false # Cannot remake problem (https://github.com/SciML/ModelingToolkit.jl/issues/2804). + # for u0 in u0_alts_vec, p in p_alts_vec + # ssprob = remake(base_ssprob; u0, p) + # @test base_sol == solve(ssprob, DynamicSS(Tsit5())) + # eprob = remake(base_eprob; u0, p) + # @test base_esol == solve(eprob, DynamicSS(Tsit5()); trajectories = 2) + # end +end ### Checks Errors On Faulty Inputs ### From 56479aa07683174c5c385441d905037bc44224d6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 20:31:50 -0400 Subject: [PATCH 396/446] init --- .../reactionsystem_serialisation.jl | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/test/miscellaneous_tests/reactionsystem_serialisation.jl b/test/miscellaneous_tests/reactionsystem_serialisation.jl index 06684fdcf2..79e06c4ab1 100644 --- a/test/miscellaneous_tests/reactionsystem_serialisation.jl +++ b/test/miscellaneous_tests/reactionsystem_serialisation.jl @@ -462,4 +462,67 @@ let rs_incomplete_loaded = include("../serialised_rs_incomplete.jl") @test !ModelingToolkit.iscomplete(rs_incomplete_loaded) rm("serialised_rs_incomplete.jl") -end \ No newline at end of file +end + +# Tests network without species, reactions, and parameters. +let + # Creates model. + rs = @reaction_network begin + @equations D(V) ~ -V + end + + # Checks its serialisation. + save_reactionsystem("test_serialisation.jl", rs; safety_check = false) + isequal(rs, include("../test_serialisation.jl")) + rm("test_serialisation.jl") +end + +# Tests various corner cases (multiple observables, species observables, non-default combinatoric +# rate law, and rate law disabling) +let + # Creates model. + rs = @reaction_network begin + @combinatoric_ratelaws false + @species Xcount(t) + @observables begin + Xtot ~ X + 2X2 + Xcount ~ X + X2 + end + p, 0 --> X + d*X2, X => 0 + (k1,k2), 2X <--> X2 + end + + # Checks its serialisation. + save_reactionsystem("test_serialisation.jl", rs; safety_check = false) + isequal(rs, include("../test_serialisation.jl")) + rm("test_serialisation.jl") +end + +# Tests saving of empty network. +let + rs = @reaction_network + save_reactionsystem("test_serialisation.jl", rs; safety_check = false) + isequal(rs, include("../test_serialisation.jl")) + rm("test_serialisation.jl") +end + +# Test that serialisation of unknown type (here a function) yields an error. +let + rs = @reaction_network begin + d, X --> 0, [misc = x -> 2x] + end + @test_throws Exception save_reactionsystem("test_serialisation.jl", rs) +end + +# Test connection field. +# Not really used for `ReactionSystem`s right now, so tests the direct function and its warning. +let + rs = @reaction_network begin + d, X --> 0 + end + @test (@test_logs (:warn, ) match_mode=:any Catalyst.get_connection_type_string(rs)) == "" + @test Catalyst.get_connection_type_annotation(rs) == "Connection types:: (OBS: Currently not supported, and hence empty)" +end + + From 20cbabe449d7fe9b426f2a1daa2a32c0e39ed2da Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 21:16:19 -0400 Subject: [PATCH 397/446] test changes --- .../serialisation_support.jl | 14 +++++----- .../serialise_fields.jl | 26 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index fd552074e8..1d5624fca3 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -14,11 +14,11 @@ end # E.g `@string_prepend! str1 str_base` becomes `str_base = str1 * str_base`. macro string_prepend!(input, string) rhs = :($input * $string) - return esc(:($string = $rhs)) + esc(:($string = $rhs)) end macro string_prepend!(input1, input2, string) rhs = :($input1 * $input2 * $string) - return esc(:($string = $rhs)) + esc(:($string = $rhs)) end # Gets the character at a specific index. @@ -51,18 +51,18 @@ function push_field(file_text::String, rn::ReactionSystem, # Adds (potential) annotation. Returns the expanded file text, and a Bool that this field was added. annotate && (@string_prepend! "\n\n# " get_comp_annotation(rn) write_string) - return (file_text * write_string, true) + (file_text * write_string, true) end # Generic function for creating an string for a unsupported argument. function get_unsupported_comp_string(component::String) @warn "Writing ReactionSystem models with $(component) is currently not supported. This field is not written to the file." - return "" + "" end # Generic function for creating the annotation string for an unsupported argument. function get_unsupported_comp_annotation(component::String) - return "$(component): (OBS: Currently not supported, and hence empty)" + "$(component): (OBS: Currently not supported, and hence empty)" end ### String Conversion ### @@ -72,14 +72,14 @@ end function expression_2_string(expr; strip_call_dict = make_strip_call_dict(Symbolics.get_variables(expr))) strip_called_expr = substitute(expr, strip_call_dict) - return repr(strip_called_expr) + repr(strip_called_expr) end # Converts a vector of symbolics (e.g. the species or parameter vectors) to a string vector. Strips # any calls (e.g. X(t) becomes X). E.g. a species vector [X, Y, Z] is converted to "[X, Y, Z]". function syms_2_strings(syms) strip_called_syms = [strip_call(Symbolics.unwrap(sym)) for sym in syms] - return get_substring_end("$(convert(Vector{Any}, strip_called_syms))", 4, 0) + get_substring_end("$(convert(Vector{Any}, strip_called_syms))", 4, 0) end # Converts a vector of symbolic variables (e.g. the species or parameter vectors) to a string diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index e4e64aff28..1d63c0dc5a 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -2,18 +2,18 @@ # Checks if the reaction system has any independent variable. True for all valid reaction systems. function seri_has_iv(rn::ReactionSystem) - return true + true end # Extract a string which declares the system's independent variable. function get_iv_string(rn::ReactionSystem) iv_dec = MT.get_iv(rn) - return "@variables $(iv_dec)" + "@variables $(iv_dec)" end # Creates an annotation for the system's independent variable. function get_iv_annotation(rn::ReactionSystem) - return "Independent variable:" + "Independent variable:" end # Combines the 3 independent variable-related functions in a constant tuple. @@ -23,17 +23,17 @@ IV_FS = (seri_has_iv, get_iv_string, get_iv_annotation) # Checks if the reaction system has any spatial independent variables. function seri_has_sivs(rn::ReactionSystem) - return !isempty(get_sivs(rn)) + !isempty(get_sivs(rn)) end # Extract a string which declares the system's spatial independent variables. function get_sivs_string(rn::ReactionSystem) - return "spatial_ivs = @variables$(syms_2_declaration_string(get_sivs(rn)))" + "spatial_ivs = @variables$(syms_2_declaration_string(get_sivs(rn)))" end # Creates an annotation for the system's spatial independent variables. function get_sivs_annotation(rn::ReactionSystem) - return "Spatial independent variables:" + "Spatial independent variables:" end # Combines the 3 independent variables-related functions in a constant tuple. @@ -133,7 +133,7 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, end # Merges the file text with `us_n_ps_string` and returns the final outputs. - return file_text * us_n_ps_string, has_ps, has_sps, has_vars + file_text * us_n_ps_string, has_ps, has_sps, has_vars end ### Handles Parameters ### @@ -142,7 +142,7 @@ end # Checks if the reaction system has any parameters. function seri_has_parameters(rn::ReactionSystem) - return !isempty(get_ps(rn)) + !isempty(get_ps(rn)) end # Extract a string which declares the system's parameters. Uses multiline declaration (a @@ -150,12 +150,12 @@ end # have metadata, default value, or type designation). function get_parameters_string(ps) multiline_format = count(complicated_declaration(p) for p in ps) > 3 - return "@parameters$(syms_2_declaration_string(ps; multiline_format))" + "@parameters$(syms_2_declaration_string(ps; multiline_format))" end # Creates an annotation for the system's parameters. function get_parameters_annotation(rn::ReactionSystem) - return "Parameters:" + "Parameters:" end ### Handles Species ### @@ -164,7 +164,7 @@ end # Checks if the reaction system has any species. function seri_has_species(rn::ReactionSystem) - return !isempty(get_species(rn)) + !isempty(get_species(rn)) end # Extract a string which declares the system's species. Uses multiline declaration (a @@ -172,12 +172,12 @@ end # have metadata, default value, or type designation). function get_species_string(sps) multiline_format = count(complicated_declaration(sp) for sp in sps) > 3 - return "@species$(syms_2_declaration_string(sps; multiline_format))" + "@species$(syms_2_declaration_string(sps; multiline_format))" end # Creates an annotation for the system's species. function get_species_annotation(rn::ReactionSystem) - return "Species:" + "Species:" end ### Handles Variables ### From 283215b437eb720588e48f19efc4e84645f2f224 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 6 Jul 2024 21:47:54 -0400 Subject: [PATCH 398/446] up --- .../serialisation_support.jl | 14 +++++----- .../serialise_fields.jl | 26 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/reactionsystem_serialisation/serialisation_support.jl b/src/reactionsystem_serialisation/serialisation_support.jl index 1d5624fca3..fd552074e8 100644 --- a/src/reactionsystem_serialisation/serialisation_support.jl +++ b/src/reactionsystem_serialisation/serialisation_support.jl @@ -14,11 +14,11 @@ end # E.g `@string_prepend! str1 str_base` becomes `str_base = str1 * str_base`. macro string_prepend!(input, string) rhs = :($input * $string) - esc(:($string = $rhs)) + return esc(:($string = $rhs)) end macro string_prepend!(input1, input2, string) rhs = :($input1 * $input2 * $string) - esc(:($string = $rhs)) + return esc(:($string = $rhs)) end # Gets the character at a specific index. @@ -51,18 +51,18 @@ function push_field(file_text::String, rn::ReactionSystem, # Adds (potential) annotation. Returns the expanded file text, and a Bool that this field was added. annotate && (@string_prepend! "\n\n# " get_comp_annotation(rn) write_string) - (file_text * write_string, true) + return (file_text * write_string, true) end # Generic function for creating an string for a unsupported argument. function get_unsupported_comp_string(component::String) @warn "Writing ReactionSystem models with $(component) is currently not supported. This field is not written to the file." - "" + return "" end # Generic function for creating the annotation string for an unsupported argument. function get_unsupported_comp_annotation(component::String) - "$(component): (OBS: Currently not supported, and hence empty)" + return "$(component): (OBS: Currently not supported, and hence empty)" end ### String Conversion ### @@ -72,14 +72,14 @@ end function expression_2_string(expr; strip_call_dict = make_strip_call_dict(Symbolics.get_variables(expr))) strip_called_expr = substitute(expr, strip_call_dict) - repr(strip_called_expr) + return repr(strip_called_expr) end # Converts a vector of symbolics (e.g. the species or parameter vectors) to a string vector. Strips # any calls (e.g. X(t) becomes X). E.g. a species vector [X, Y, Z] is converted to "[X, Y, Z]". function syms_2_strings(syms) strip_called_syms = [strip_call(Symbolics.unwrap(sym)) for sym in syms] - get_substring_end("$(convert(Vector{Any}, strip_called_syms))", 4, 0) + return get_substring_end("$(convert(Vector{Any}, strip_called_syms))", 4, 0) end # Converts a vector of symbolic variables (e.g. the species or parameter vectors) to a string diff --git a/src/reactionsystem_serialisation/serialise_fields.jl b/src/reactionsystem_serialisation/serialise_fields.jl index 1d63c0dc5a..e4e64aff28 100644 --- a/src/reactionsystem_serialisation/serialise_fields.jl +++ b/src/reactionsystem_serialisation/serialise_fields.jl @@ -2,18 +2,18 @@ # Checks if the reaction system has any independent variable. True for all valid reaction systems. function seri_has_iv(rn::ReactionSystem) - true + return true end # Extract a string which declares the system's independent variable. function get_iv_string(rn::ReactionSystem) iv_dec = MT.get_iv(rn) - "@variables $(iv_dec)" + return "@variables $(iv_dec)" end # Creates an annotation for the system's independent variable. function get_iv_annotation(rn::ReactionSystem) - "Independent variable:" + return "Independent variable:" end # Combines the 3 independent variable-related functions in a constant tuple. @@ -23,17 +23,17 @@ IV_FS = (seri_has_iv, get_iv_string, get_iv_annotation) # Checks if the reaction system has any spatial independent variables. function seri_has_sivs(rn::ReactionSystem) - !isempty(get_sivs(rn)) + return !isempty(get_sivs(rn)) end # Extract a string which declares the system's spatial independent variables. function get_sivs_string(rn::ReactionSystem) - "spatial_ivs = @variables$(syms_2_declaration_string(get_sivs(rn)))" + return "spatial_ivs = @variables$(syms_2_declaration_string(get_sivs(rn)))" end # Creates an annotation for the system's spatial independent variables. function get_sivs_annotation(rn::ReactionSystem) - "Spatial independent variables:" + return "Spatial independent variables:" end # Combines the 3 independent variables-related functions in a constant tuple. @@ -133,7 +133,7 @@ function handle_us_n_ps(file_text::String, rn::ReactionSystem, annotate::Bool, end # Merges the file text with `us_n_ps_string` and returns the final outputs. - file_text * us_n_ps_string, has_ps, has_sps, has_vars + return file_text * us_n_ps_string, has_ps, has_sps, has_vars end ### Handles Parameters ### @@ -142,7 +142,7 @@ end # Checks if the reaction system has any parameters. function seri_has_parameters(rn::ReactionSystem) - !isempty(get_ps(rn)) + return !isempty(get_ps(rn)) end # Extract a string which declares the system's parameters. Uses multiline declaration (a @@ -150,12 +150,12 @@ end # have metadata, default value, or type designation). function get_parameters_string(ps) multiline_format = count(complicated_declaration(p) for p in ps) > 3 - "@parameters$(syms_2_declaration_string(ps; multiline_format))" + return "@parameters$(syms_2_declaration_string(ps; multiline_format))" end # Creates an annotation for the system's parameters. function get_parameters_annotation(rn::ReactionSystem) - "Parameters:" + return "Parameters:" end ### Handles Species ### @@ -164,7 +164,7 @@ end # Checks if the reaction system has any species. function seri_has_species(rn::ReactionSystem) - !isempty(get_species(rn)) + return !isempty(get_species(rn)) end # Extract a string which declares the system's species. Uses multiline declaration (a @@ -172,12 +172,12 @@ end # have metadata, default value, or type designation). function get_species_string(sps) multiline_format = count(complicated_declaration(sp) for sp in sps) > 3 - "@species$(syms_2_declaration_string(sps; multiline_format))" + return "@species$(syms_2_declaration_string(sps; multiline_format))" end # Creates an annotation for the system's species. function get_species_annotation(rn::ReactionSystem) - "Species:" + return "Species:" end ### Handles Variables ### From d4fd35f49e4d54e8ac3ca5c7d09aebec14ebd47e Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 08:00:16 -0400 Subject: [PATCH 399/446] fix warn messages --- test/extensions/structural_identifiability.jl | 14 ++++++++------ test/simulation_and_solving/solve_nonlinear.jl | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/test/extensions/structural_identifiability.jl b/test/extensions/structural_identifiability.jl index 105d990d48..c4becbc5e4 100644 --- a/test/extensions/structural_identifiability.jl +++ b/test/extensions/structural_identifiability.jl @@ -316,6 +316,7 @@ end ### Other Tests ### # Checks that identifiability can be assessed for coupled CRN/DAE systems. +# `remove_conserved = false` is used to remove info print statement from log. let rs = @reaction_network begin @parameters k c1 c2 @@ -329,9 +330,10 @@ let @unpack p, d, k, c1, c2 = rs # Tests identifiability assessment when all unknowns are measured. - gi_1 = assess_identifiability(rs; measured_quantities = [:X, :V, :C], loglevel) - li_1 = assess_local_identifiability(rs; measured_quantities = [:X, :V, :C], loglevel) - ifs_1 = find_identifiable_functions(rs; measured_quantities = [:X, :V, :C], loglevel) + remove_conserved = false + gi_1 = assess_identifiability(rs; measured_quantities = [:X, :V, :C], loglevel, remove_conserved) + li_1 = assess_local_identifiability(rs; measured_quantities = [:X, :V, :C], loglevel, remove_conserved) + ifs_1 = find_identifiable_functions(rs; measured_quantities = [:X, :V, :C], loglevel, remove_conserved) @test sym_dict(gi_1) == Dict([:X => :globally, :C => :globally, :V => :globally, :k => :globally, :c1 => :nonidentifiable, :c2 => :nonidentifiable, :p => :globally, :d => :globally]) @test sym_dict(li_1) == Dict([:X => 1, :C => 1, :V => 1, :k => 1, :c1 => 0, :c2 => 0, :p => 1, :d => 1]) @@ -339,9 +341,9 @@ let # Tests identifiability assessment when only variables are measured. # Checks that a parameter in an equation can be set as known. - gi_2 = assess_identifiability(rs; measured_quantities = [:V, :C], known_p = [:c1], loglevel) - li_2 = assess_local_identifiability(rs; measured_quantities = [:V, :C], known_p = [:c1], loglevel) - ifs_2 = find_identifiable_functions(rs; measured_quantities = [:V, :C], known_p = [:c1], loglevel) + gi_2 = assess_identifiability(rs; measured_quantities = [:V, :C], known_p = [:c1], loglevel, remove_conserved) + li_2 = assess_local_identifiability(rs; measured_quantities = [:V, :C], known_p = [:c1], loglevel, remove_conserved) + ifs_2 = find_identifiable_functions(rs; measured_quantities = [:V, :C], known_p = [:c1], loglevel, remove_conserved) @test sym_dict(gi_2) == Dict([:X => :nonidentifiable, :C => :globally, :V => :globally, :k => :nonidentifiable, :c1 => :globally, :c2 => :nonidentifiable, :p => :nonidentifiable, :d => :globally]) @test sym_dict(li_2) == Dict([:X => 0, :C => 1, :V => 1, :k => 0, :c1 => 1, :c2 => 0, :p => 0, :d => 1]) diff --git a/test/simulation_and_solving/solve_nonlinear.jl b/test/simulation_and_solving/solve_nonlinear.jl index 1dcb8cd5c1..0315a65ac5 100644 --- a/test/simulation_and_solving/solve_nonlinear.jl +++ b/test/simulation_and_solving/solve_nonlinear.jl @@ -90,7 +90,7 @@ let # Creates NonlinearProblem. u0 = [steady_state_network_3.X => rand(), steady_state_network_3.Y => rand() + 1.0, steady_state_network_3.Y2 => rand() + 3.0, steady_state_network_3.XY2 => 0.0] p = [:p => rand()+1.0, :d => 0.5, :k1 => 1.0, :k2 => 2.0, :k3 => 3.0, :k4 => 4.0] - nl_prob_1 = NonlinearProblem(steady_state_network_3, u0, p; remove_conserved = true) + nl_prob_1 = NonlinearProblem(steady_state_network_3, u0, p; remove_conserved = true, remove_conserved_warn = false) nl_prob_2 = NonlinearProblem(steady_state_network_3, u0, p) # Solves it using standard algorithm and simulation based algorithm. From 115b966ea76a023e45c85907de3c953b30fb34f0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 09:24:31 -0400 Subject: [PATCH 400/446] save progress --- .../spatial_ODE_systems.jl | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index bd9fdf8e4e..e53160de0c 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -13,6 +13,10 @@ struct LatticeTransportODEf{S,T} """The indexes of the edge parameters in the parameter vector (`parameters(lrs)`).""" edge_p_idxs::Vector{Int64} """ + Work in progress. + """ + mtk_ps + """ The non-spatial `ReactionSystem` which was used to create the `LatticeReactionSystem` contain a set of parameters (either identical to, or a sub set of, `parameters(lrs)`). This vector contain the indexes of the non-spatial system's parameters in `parameters(lrs)`. These are @@ -72,6 +76,9 @@ struct LatticeTransportODEf{S,T} edge_p_idxs = subset_indexes_of(edge_parameters(lrs), parameters(lrs)) nonspatial_rs_p_idxs = subset_indexes_of(parameters(reactionsystem(lrs)), parameters(lrs)) + # WIP. + mtk_ps = ModelingToolkit.MTKParameters(complete(convert(ODESystem, reactionsystem(lrs))), [e[1] => e[2][1] for e in vert_ps]) + # Computes the indexes of the vertex parameters in the vector of parameters. # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a # vector). This vector is already sorted according to the order of the parameters. Here, we extract @@ -90,7 +97,7 @@ struct LatticeTransportODEf{S,T} # Declares `work_ps` (used as storage during computation) and the edge iterator. work_ps = zeros(length(parameters(lrs))) edge_iterator = Catalyst.edge_iterator(lrs) - new{S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_p_idxs, edge_p_idxs, + new{S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_p_idxs, edge_p_idxs, mtk_ps, nonspatial_rs_p_idxs, vert_ps, work_ps, v_ps_idx_types, transport_rates, t_rate_idx_types, leaving_rates, edge_iterator) end @@ -109,6 +116,10 @@ struct LatticeTransportODEjac{R,S,T} """The indexes of the edge parameters in the parameter vector (`parameters(lrs)`).""" edge_p_idxs::Vector{Int64} """ + Work in progress. + """ + mtk_ps + """ The non-spatial `ReactionSystem` which was used to create the `LatticeReactionSystem` contain a set of parameters (either identical to, or a sub set of, `parameters(lrs)`). This vector contain the indexes of the non-spatial system's parameters in `parameters(lrs)`. These are @@ -145,6 +156,9 @@ struct LatticeTransportODEjac{R,S,T} edge_p_idxs = subset_indexes_of(edge_parameters(lrs), parameters(lrs)) nonspatial_rs_p_idxs = subset_indexes_of(parameters(reactionsystem(lrs)), parameters(lrs)) + # WIP. + mtk_ps = ModelingToolkit.MTKParameters(complete(convert(ODESystem, reactionsystem(lrs))), [e[1] => e[2][1] for e in vert_ps]) + # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a # vector). This vector is already sorted according to the order of the parameters. Here, we extract # its values only and put them into `vert_ps`. @@ -153,7 +167,7 @@ struct LatticeTransportODEjac{R,S,T} work_ps = zeros(length(parameters(lrs))) v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) new{R,S,typeof(jac_transport)}(ofunc, num_verts(lrs), num_species(lrs) , vert_p_idxs, - edge_p_idxs, nonspatial_rs_p_idxs, vert_ps, + edge_p_idxs, mtk_ps, nonspatial_rs_p_idxs, vert_ps, work_ps, v_ps_idx_types, sparse, jac_transport) end end @@ -321,9 +335,6 @@ end # Defines the forcing functor's effect on the (spatial) ODE system. function (f_func::LatticeTransportODEf)(du, u, p, t) - println(du) - println(u) - println(p) # Updates for non-spatial reactions. for vert_i in 1:(f_func.num_verts) # Gets the indices of all the species at vertex i. From 56d5f86dea16a4e0457b4e0227d3ba348551a374 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 09:45:49 -0400 Subject: [PATCH 401/446] save progress --- .../spatial_ODE_systems.jl | 24 ++++++++++++++----- src/spatial_reaction_systems/utility.jl | 8 +++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index e53160de0c..6e713cff23 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -17,6 +17,10 @@ struct LatticeTransportODEf{S,T} """ mtk_ps """ + Work in progress. + """ + p_setters + """ The non-spatial `ReactionSystem` which was used to create the `LatticeReactionSystem` contain a set of parameters (either identical to, or a sub set of, `parameters(lrs)`). This vector contain the indexes of the non-spatial system's parameters in `parameters(lrs)`. These are @@ -77,7 +81,9 @@ struct LatticeTransportODEf{S,T} nonspatial_rs_p_idxs = subset_indexes_of(parameters(reactionsystem(lrs)), parameters(lrs)) # WIP. - mtk_ps = ModelingToolkit.MTKParameters(complete(convert(ODESystem, reactionsystem(lrs))), [e[1] => e[2][1] for e in vert_ps]) + nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) + mtk_ps = MT.MTKParameters(nonspatial_osys, [e[1] => e[2][1] for e in vert_ps]) + p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(nonspatial_osys)] # Computes the indexes of the vertex parameters in the vector of parameters. # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a @@ -97,7 +103,7 @@ struct LatticeTransportODEf{S,T} # Declares `work_ps` (used as storage during computation) and the edge iterator. work_ps = zeros(length(parameters(lrs))) edge_iterator = Catalyst.edge_iterator(lrs) - new{S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_p_idxs, edge_p_idxs, mtk_ps, + new{S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_p_idxs, edge_p_idxs, mtk_ps, p_setters, nonspatial_rs_p_idxs, vert_ps, work_ps, v_ps_idx_types, transport_rates, t_rate_idx_types, leaving_rates, edge_iterator) end @@ -120,6 +126,10 @@ struct LatticeTransportODEjac{R,S,T} """ mtk_ps """ + Work in progress. + """ + p_setters + """ The non-spatial `ReactionSystem` which was used to create the `LatticeReactionSystem` contain a set of parameters (either identical to, or a sub set of, `parameters(lrs)`). This vector contain the indexes of the non-spatial system's parameters in `parameters(lrs)`. These are @@ -157,7 +167,9 @@ struct LatticeTransportODEjac{R,S,T} nonspatial_rs_p_idxs = subset_indexes_of(parameters(reactionsystem(lrs)), parameters(lrs)) # WIP. - mtk_ps = ModelingToolkit.MTKParameters(complete(convert(ODESystem, reactionsystem(lrs))), [e[1] => e[2][1] for e in vert_ps]) + nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) + mtk_ps = MT.MTKParameters(nonspatial_osys, [e[1] => e[2][1] for e in vert_ps]) + p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(nonspatial_osys)] # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a # vector). This vector is already sorted according to the order of the parameters. Here, we extract @@ -167,7 +179,7 @@ struct LatticeTransportODEjac{R,S,T} work_ps = zeros(length(parameters(lrs))) v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) new{R,S,typeof(jac_transport)}(ofunc, num_verts(lrs), num_species(lrs) , vert_p_idxs, - edge_p_idxs, mtk_ps, nonspatial_rs_p_idxs, vert_ps, + edge_p_idxs, mtk_ps, p_setters, nonspatial_rs_p_idxs, vert_ps, work_ps, v_ps_idx_types, sparse, jac_transport) end end @@ -344,7 +356,7 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) update_work_vert_ps!(f_func, p, vert_i) # Evaluate reaction contributions to du at vert_i. - f_func.ofunc((@view du[idxs]), (@view u[idxs]), nonspatial_ps(f_func), t) + f_func.ofunc((@view du[idxs]), (@view u[idxs]), f_func.mtk_ps, t) end # s_idx is the species index among transport species, s is the index among all species. @@ -372,7 +384,7 @@ function (jac_func::LatticeTransportODEjac)(J, u, p, t) for vert_i in 1:(jac_func.num_verts) idxs = get_indexes(vert_i, jac_func.num_species) update_work_vert_ps!(jac_func, p, vert_i) - jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), nonspatial_ps(jac_func), t) + jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), jac_func.mtk_ps, t) end # Updates for the spatial reactions (adds the Jacobian values from the transportation reactions). diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 43c7932185..92000302cc 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -287,6 +287,14 @@ function update_work_vert_ps!(lt_ode_func, all_ps::Vector{T}, vert::Int64) where return update_work_vert_ps!(lt_ode_func.work_ps, lt_ode_func.vert_p_idxs, all_ps, vert, lt_ode_func.v_ps_idx_types) end +# Input is either a LatticeTransportODEf or LatticeTransportODEjac function (which fields we pass on). +function update_work_vert_ps!(lt_ode_func, all_ps::Vector{T}, vert::Int64) where {T} + for (setp, (idx, loc_type)) in zip(lt_ode_func.p_setters, enumerate(lt_ode_func.v_ps_idx_types)) + p_val = (loc_type ? all_ps[lt_ode_func.vert_p_idxs[idx]][1] : all_ps[lt_ode_func.vert_p_idxs[idx]][vert]) + setp(lt_ode_func.mtk_ps, p_val) + end +end + # Fetches the parameter values that currently are in the work parameter vector and which # corresponds to the parameters of the non-spatial `ReactionSystem` stored in the `ReactionSystem`. function nonspatial_ps(lt_ode_func) From 72ccf0752333ba695a78061d2a2f0a9085b4d8c5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 09:54:05 -0400 Subject: [PATCH 402/446] up --- test/upstream/mtk_structure_indexing.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/upstream/mtk_structure_indexing.jl b/test/upstream/mtk_structure_indexing.jl index 1049e93a0e..f8768b8ed0 100644 --- a/test/upstream/mtk_structure_indexing.jl +++ b/test/upstream/mtk_structure_indexing.jl @@ -37,12 +37,12 @@ begin problems = [oprob, sprob, dprob, jprob, nprob, ssprob] # Creates an `EnsembleProblem` for each problem. - eoprob = EnsembleProblem(oprob) - esprob = EnsembleProblem(sprob) - edprob = EnsembleProblem(dprob) - ejprob = EnsembleProblem(jprob) - enprob = EnsembleProblem(nprob) - essprob = EnsembleProblem(ssprob) + eoprob = EnsembleProblem(deepcopy(oprob)) + esprob = EnsembleProblem(deepcopy(sprob)) + edprob = EnsembleProblem(deepcopy(dprob)) + ejprob = EnsembleProblem(deepcopy(jprob)) + enprob = EnsembleProblem(deepcopy(nprob)) + essprob = EnsembleProblem(deepcopy(ssprob)) eproblems = [eoprob, esprob, edprob, ejprob, enprob, essprob] # Creates integrators. From 2409dcca72d284057e3ced7ad49ae9c055d97b34 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 11:00:20 -0400 Subject: [PATCH 403/446] save prog --- .../spatial_ODE_systems.jl | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 6e713cff23..5dede5e1df 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -68,7 +68,7 @@ struct LatticeTransportODEf{S,T} """An iterator over all the edges of the lattice.""" edge_iterator::Vector{Pair{Int64, Int64}} - function LatticeTransportODEf(ofunc::S, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, + function LatticeTransportODEf(ofunc::S, vert_ps, transport_rates::Vector{Pair{Int64, SparseMatrixCSC{T, Int64}}}, lrs::LatticeReactionSystem) where {S,T} # Records which parameters and rates are uniform and which are not. @@ -82,7 +82,8 @@ struct LatticeTransportODEf{S,T} # WIP. nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) - mtk_ps = MT.MTKParameters(nonspatial_osys, [e[1] => e[2][1] for e in vert_ps]) + p_init = [p => Dict(vert_ps)[p][1] for p in parameters(nonspatial_osys)] + mtk_ps = MT.MTKParameters(nonspatial_osys, p_init) p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(nonspatial_osys)] # Computes the indexes of the vertex parameters in the vector of parameters. @@ -157,7 +158,7 @@ struct LatticeTransportODEjac{R,S,T} """The transport rates. This is a dense or sparse matrix (depending on what type of Jacobian is used).""" jac_transport::T - function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{S}}}, + function LatticeTransportODEjac(ofunc::R, vert_ps, jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, lrs::LatticeReactionSystem, sparse::Bool) where {R,S} @@ -250,23 +251,23 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{Basi ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) jac_transport = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) if sparse - f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, lrs) + f = LatticeTransportODEf(ofunc_sparse, [vert_ps; edge_ps], transport_rates, lrs) jac_transport = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) - J = LatticeTransportODEjac(ofunc_dense, vert_ps, jac_transport, lrs, true) + J = LatticeTransportODEjac(ofunc_dense, [vert_ps; edge_ps], jac_transport, lrs, true) jac_prototype = jac_transport else - f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, lrs) - J = LatticeTransportODEjac(ofunc_dense, vert_ps, jac_transport, lrs, false) + f = LatticeTransportODEf(ofunc_dense, [vert_ps; edge_ps], transport_rates, lrs) + J = LatticeTransportODEjac(ofunc_dense, [vert_ps; edge_ps], jac_transport, lrs, false) jac_prototype = nothing end else if sparse ofunc_sparse = ODEFunction(osys; jac = false, sparse = true) - f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, lrs) + f = LatticeTransportODEf(ofunc_sparse, [vert_ps; edge_ps], transport_rates, lrs) jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = false) else ofunc_dense = ODEFunction(osys; jac = false, sparse = false) - f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, lrs) + f = LatticeTransportODEf(ofunc_dense, [vert_ps; edge_ps], transport_rates, lrs) jac_prototype = nothing end J = nothing From e820c2e4965cc2d1ba890de3f81df8bed7168eb2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 11:22:03 -0400 Subject: [PATCH 404/446] add StaticArrays to test env --- Project.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index eb7e1abeb7..0a302b5529 100644 --- a/Project.toml +++ b/Project.toml @@ -76,6 +76,7 @@ SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLNLSolve = "e9a6253c-8580-4d32-9898-8661bb511710" StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f" StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" @@ -84,4 +85,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [targets] -test = ["BifurcationKit", "DiffEqCallbacks", "DomainSets", "Graphviz_jll", "HomotopyContinuation", "Logging", "NonlinearSolve", "OrdinaryDiffEq", "Plots", "Random", "SafeTestsets", "SciMLBase", "SciMLNLSolve", "StableRNGs", "Statistics", "SteadyStateDiffEq", "StochasticDiffEq", "StructuralIdentifiability", "Test", "Unitful"] +test = ["BifurcationKit", "DiffEqCallbacks", "DomainSets", "Graphviz_jll", "HomotopyContinuation", "Logging", "NonlinearSolve", "OrdinaryDiffEq", "Plots", "Random", "SafeTestsets", "SciMLBase", "SciMLNLSolve", "StableRNGs", "StaticArrays", "Statistics", "SteadyStateDiffEq", "StochasticDiffEq", "StructuralIdentifiability", "Test", "Unitful"] From 1caacd5add4e6f23c1abd30a4b030e6a7b5994e7 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 15:33:33 -0400 Subject: [PATCH 405/446] save progress --- .../spatial_ODE_systems.jl | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 5dede5e1df..35b2a3c2a4 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -68,7 +68,7 @@ struct LatticeTransportODEf{S,T} """An iterator over all the edges of the lattice.""" edge_iterator::Vector{Pair{Int64, Int64}} - function LatticeTransportODEf(ofunc::S, vert_ps, + function LatticeTransportODEf(ofunc::S, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, edge_ps, transport_rates::Vector{Pair{Int64, SparseMatrixCSC{T, Int64}}}, lrs::LatticeReactionSystem) where {S,T} # Records which parameters and rates are uniform and which are not. @@ -82,7 +82,7 @@ struct LatticeTransportODEf{S,T} # WIP. nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) - p_init = [p => Dict(vert_ps)[p][1] for p in parameters(nonspatial_osys)] + p_init = [p => Dict([vert_ps; edge_ps])[p][1] for p in parameters(nonspatial_osys)] mtk_ps = MT.MTKParameters(nonspatial_osys, p_init) p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(nonspatial_osys)] @@ -158,7 +158,7 @@ struct LatticeTransportODEjac{R,S,T} """The transport rates. This is a dense or sparse matrix (depending on what type of Jacobian is used).""" jac_transport::T - function LatticeTransportODEjac(ofunc::R, vert_ps, + function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{S}}}, edge_ps, jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, lrs::LatticeReactionSystem, sparse::Bool) where {R,S} @@ -169,7 +169,8 @@ struct LatticeTransportODEjac{R,S,T} # WIP. nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) - mtk_ps = MT.MTKParameters(nonspatial_osys, [e[1] => e[2][1] for e in vert_ps]) + p_init = [p => Dict([vert_ps; edge_ps])[p][1] for p in parameters(nonspatial_osys)] + mtk_ps = MT.MTKParameters(nonspatial_osys, p_init) p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(nonspatial_osys)] # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a @@ -251,23 +252,23 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{Basi ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) jac_transport = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) if sparse - f = LatticeTransportODEf(ofunc_sparse, [vert_ps; edge_ps], transport_rates, lrs) + f = LatticeTransportODEf(ofunc_sparse, vert_ps, edge_ps, transport_rates, lrs) jac_transport = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) - J = LatticeTransportODEjac(ofunc_dense, [vert_ps; edge_ps], jac_transport, lrs, true) + J = LatticeTransportODEjac(ofunc_dense, vert_ps, edge_ps, jac_transport, lrs, true) jac_prototype = jac_transport else - f = LatticeTransportODEf(ofunc_dense, [vert_ps; edge_ps], transport_rates, lrs) - J = LatticeTransportODEjac(ofunc_dense, [vert_ps; edge_ps], jac_transport, lrs, false) + f = LatticeTransportODEf(ofunc_dense, vert_ps, edge_ps, transport_rates, lrs) + J = LatticeTransportODEjac(ofunc_dense, vert_ps, edge_ps, jac_transport, lrs, false) jac_prototype = nothing end else if sparse ofunc_sparse = ODEFunction(osys; jac = false, sparse = true) - f = LatticeTransportODEf(ofunc_sparse, [vert_ps; edge_ps], transport_rates, lrs) + f = LatticeTransportODEf(ofunc_sparse, vert_ps, edge_ps, transport_rates, lrs) jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = false) else ofunc_dense = ODEFunction(osys; jac = false, sparse = false) - f = LatticeTransportODEf(ofunc_dense, [vert_ps; edge_ps], transport_rates, lrs) + f = LatticeTransportODEf(ofunc_dense, vert_ps, edge_ps, transport_rates, lrs) jac_prototype = nothing end J = nothing @@ -347,7 +348,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, end # Defines the forcing functor's effect on the (spatial) ODE system. -function (f_func::LatticeTransportODEf)(du, u, p, t) +function (f_func::LatticeTransportODEf)(du::AbstractVector, u, p, t) # Updates for non-spatial reactions. for vert_i in 1:(f_func.num_verts) # Gets the indices of all the species at vertex i. @@ -378,7 +379,7 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) end # Defines the Jacobian functor's effect on the (spatial) ODE system. -function (jac_func::LatticeTransportODEjac)(J, u, p, t) +function (jac_func::LatticeTransportODEjac)(J::AbstractMatrix, u, p, t) J .= 0.0 # Update the Jacobian from non-spatial reaction terms. From c3d55526cd39ec7aa95942fa09abf849f520bf61 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 15:36:42 -0400 Subject: [PATCH 406/446] up --- test/extensions/homotopy_continuation.jl | 31 ++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/test/extensions/homotopy_continuation.jl b/test/extensions/homotopy_continuation.jl index 7a260e3b59..024c7dca18 100644 --- a/test/extensions/homotopy_continuation.jl +++ b/test/extensions/homotopy_continuation.jl @@ -25,12 +25,12 @@ let u0 = [:X1 => 2.0, :X2 => 2.0, :X3 => 2.0, :X2_2X3 => 2.0] # Computes the single steady state, checks that when given to the ODE rhs, all are evaluated to 0. - hc_ss = hc_steady_states(rs, ps; u0=u0, show_progress=false) + hc_ss = hc_steady_states(rs, ps; u0 = u0, show_progress = false, seed = 1234) hc_ss = Pair.(unknowns(rs), hc_ss[1]) - @test maximum(abs.(f_eval(rs, hc_ss, ps, 0.0))) ≈ 0.0 atol=1e-12 + @test maximum(abs.(f_eval(rs, hc_ss, ps, 0.0))) ≈ 0.0 atol = 1e-12 # Checks that not giving a `u0` argument yields an error for systems with conservation laws. - @test_throws Exception hc_steady_states(rs, ps; show_progress=false) + @test_throws Exception hc_steady_states(rs, ps; show_progress = false) end # Tests for network with multiple steady state. @@ -45,11 +45,11 @@ let end ps = [:k3 => 1.0, :k2 => 2.0, :k4 => 1.5, :k1=>8.0] - hc_ss_1 = hc_steady_states(wilhelm_2009_model, ps; seed=0x000004d1, show_progress=false) - @test sort(hc_ss_1, by=sol->sol[1]) ≈ [[0.0, 0.0], [0.5, 2.0], [4.5, 6.0]] + hc_ss_1 = hc_steady_states(wilhelm_2009_model, ps; seed = 1234, show_progress = false) + @test sort(hc_ss_1, by = sol->sol[1]) ≈ [[0.0, 0.0], [0.5, 2.0], [4.5, 6.0]] - hc_ss_2 = hc_steady_states(wilhelm_2009_model, ps; seed=0x000004d2, show_progress=false) - hc_ss_3 = hc_steady_states(wilhelm_2009_model, ps; seed=0x000004d2, show_progress=false) + hc_ss_2 = hc_steady_states(wilhelm_2009_model, ps; seed = 1234, show_progress = false) + hc_ss_3 = hc_steady_states(wilhelm_2009_model, ps; seed = 1234, show_progress = false) @test hc_ss_1 != hc_ss_2 @test hc_ss_2 == hc_ss_3 end @@ -69,7 +69,7 @@ let ps = (:kY1 => 1.0, :kY2 => 3, :kZ1 => 1.0, :kZ2 => 4.0) u0_1 = (:Y1 => 1.0, :Y2 => 3, :Z1 => 10, :Z2 =>40.0) - ss_1 = sort(hc_steady_states(rs_1, ps; u0=u0_1, show_progress=false), by=sol->sol[1]) + ss_1 = sort(hc_steady_states(rs_1, ps; u0 = u0_1, show_progress = false, seed = 1234), by = sol->sol[1]) @test ss_1 ≈ [[0.2, 0.1, 3.0, 1.0, 40.0, 10.0]] rs_2 = @reaction_network begin @@ -81,7 +81,7 @@ let end u0_2 = [:B2 => 1.0, :B1 => 3.0, :A2 => 10.0, :A1 =>40.0] - ss_2 = sort(hc_steady_states(rs_2, ps; u0=u0_2, show_progress=false), by=sol->sol[1]) + ss_2 = sort(hc_steady_states(rs_2, ps; u0 = u0_2, show_progress = false, seed = 1234), by = sol->sol[1]) @test ss_1 ≈ ss_2 end @@ -96,14 +96,15 @@ let d, X --> 0 end ps = [:v => 5.0, :K => 2.5, :n => 3, :d => 1.0] - sss = hc_steady_states(rs, ps; filter_negative=false, show_progress=false) + sss = hc_steady_states(rs, ps; filter_negative = false, show_progress = false, seed = 1234) @test length(sss) == 4 for ss in sss - @test f_eval(rs,sss[1], last.(ps), 0.0)[1] ≈ 0.0 atol=1e-12 + @test f_eval(rs,sss[1], last.(ps), 0.0)[1] ≈ 0.0 atol = 1e-12 end - @test_throws Exception hc_steady_states(rs, [:v => 5.0, :K => 2.5, :n => 2.7, :d => 1.0]; show_progress=false) + ps = [:v => 5.0, :K => 2.5, :n => 2.7, :d => 1.0] + @test_throws Exception hc_steady_states(rs, ps; show_progress = false, seed = 1234) end @@ -124,7 +125,7 @@ let # Checks that homotopy continuation correctly find the system's single steady state. ps = [:p => 2.0, :d => 1.0, :k => 5.0] - hc_ss = hc_steady_states(rs, ps) + hc_ss = hc_steady_states(rs, ps; show_progress = false, seed = 1234) @test hc_ss ≈ [[2.0, 0.2, 10.0]] end @@ -137,7 +138,7 @@ let p_start = [:p => 1.0, :d => 0.2] # Computes bifurcation diagram. - @test_throws Exception hc_steady_states(incomplete_network, p_start) + @test_throws Exception hc_steady_states(incomplete_network, p_start; show_progress = false, seed = 1234) end # Tests that non-autonomous system throws an error @@ -146,5 +147,5 @@ let (k,t), 0 <--> X end ps = [:k => 1.0] - @test_throws Exception hc_steady_states(rs, ps) + @test_throws Exception hc_steady_states(rs, ps; show_progress = false, seed = 1234) end \ No newline at end of file From ae077c20a0c0587bfb440dcade6aaa42d0f9cffb Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 16:02:15 -0400 Subject: [PATCH 407/446] save progress --- .../spatial_ODE_systems.jl | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 35b2a3c2a4..7d4893bea6 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -1,5 +1,130 @@ ### Spatial ODE Functor Structures ### +# Functor with information for the forcing function of a spatial ODE with spatial movement on a lattice. +struct LatticeTransportODEFunction{R,S,T} + """The ODEFunction of the (non-spatial) ReactionSystem that generated this LatticeTransportODEf instance.""" + ofunc::S + """The number of vertices.""" + num_verts::Int64 + """The number of species.""" + num_species::Int64 + """The indexes of the vertex parameters in the parameter vector (`parameters(lrs)`).""" + vert_p_idxs::Vector{Int64} + """The indexes of the edge parameters in the parameter vector (`parameters(lrs)`).""" + edge_p_idxs::Vector{Int64} + """ + Work in progress. + """ + mtk_ps + """ + Work in progress. + """ + p_setters + """ + The non-spatial `ReactionSystem` which was used to create the `LatticeReactionSystem` contain + a set of parameters (either identical to, or a sub set of, `parameters(lrs)`). This vector + contain the indexes of the non-spatial system's parameters in `parameters(lrs)`. These are + required to manage the non-spatial ODEFunction in the spatial call. + """ + nonspatial_rs_p_idxs::Vector{Int64} + """The values of the parameters that are tied to vertices.""" + vert_ps::Vector{Vector{T}} + """ + Vector for storing temporary values. Repeatedly during simulations, we need to retrieve the + parameter values in a certain vertex. However, since most parameters (likely) are uniform + (and hence only have 1 value stored), we need to create a new vector each time we need to retrieve + the parameter values in a new vertex. To avoid relocating these values repeatedly, we write them + to this vector. + """ + work_ps::Vector{T} + """ + For each parameter in vert_ps, its value is a vector with a length of either num_verts or 1. + To know whenever a parameter's value needs expanding to the work_ps array, its length needs checking. + This check is done once, and the value is stored in this array. True means a uniform value. + """ + v_ps_idx_types::Vector{Bool} + """ + A vector that stores, for each species with transportation, its transportation rate(s). + Each entry is a pair from (the index of) the transported species (in the `species(lrs)` vector) + to its transportation rate (each species only has a single transportation rate, the sum of all + its transportation reactions' rates). If the transportation rate is uniform across all edges, + stores a single value (in a size (1,1) sparse matrix). Otherwise, stores these in a sparse matrix + where value (i,j) is the species transportation rate from vertex i to vertex j. + """ + transport_rates::Vector{Pair{Int64,SparseMatrixCSC{T, Int64}}} + """ + For each transport rate in transport_rates, its value is a (sparse) matrix with a size of either + (num_verts,num_verts) or (1,1). In the second case, the transportation rate is uniform across + all edges. To avoid having to check which case holds for each transportation rate, we store the + corresponding case in this value. `true` means that a species has a uniform transportation rate. + """ + t_rate_idx_types::Vector{Bool} + """ + A matrix, NxM, where N is the number of species with transportation and M is the number of vertices. + Each value is the total rate at which that species leaves that vertex + (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D). + """ + leaving_rates::Matrix{T} + """An iterator over all the edges of the lattice.""" + edge_iterator::Vector{Pair{Int64, Int64}} + """Whether the Jacobian is sparse or not.""" + sparse::Bool + """The transport rates. This is a dense or sparse matrix (depending on what type of Jacobian is used).""" + jac_transport::R + + function LatticeTransportODEFunction(ofunc::S, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, edge_ps, + transport_rates::Vector{Pair{Int64, SparseMatrixCSC{T, Int64}}}, + lrs::LatticeReactionSystem) where {S,T} + # Records which parameters and rates are uniform and which are not. + v_ps_idx_types = map(vp -> length(vp[2]) == 1, vert_ps) + t_rate_idx_types = map(tr -> size(tr[2]) == (1,1), transport_rates) + + # Computes the indexes of various parameters in in the `parameters(lrs)` vector. + vert_p_idxs = subset_indexes_of(vertex_parameters(lrs), parameters(lrs)) + edge_p_idxs = subset_indexes_of(edge_parameters(lrs), parameters(lrs)) + nonspatial_rs_p_idxs = subset_indexes_of(parameters(reactionsystem(lrs)), parameters(lrs)) + + # WIP. + nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) + p_init = [p => Dict([vert_ps; edge_ps])[p][1] for p in parameters(nonspatial_osys)] + mtk_ps = MT.MTKParameters(nonspatial_osys, p_init) + p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(nonspatial_osys)] + + # Computes the indexes of the vertex parameters in the vector of parameters. + # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a + # vector). This vector is already sorted according to the order of the parameters. Here, we extract + # its values only and put them into `vert_ps`. + vert_ps = [vp[2] for vp in vert_ps] + + # Computes the leaving rate matrix. + leaving_rates = zeros(length(transport_rates), num_verts(lrs)) + for (s_idx, tr_pair) in enumerate(transport_rates) + for e in Catalyst.edge_iterator(lrs) + # Updates the exit rate for species s_idx from vertex e.src. + leaving_rates[s_idx, e[1]] += get_transport_rate(tr_pair[2], e, t_rate_idx_types[s_idx]) + end + end + + # Declares `work_ps` (used as storage during computation) and the edge iterator. + work_ps = zeros(length(parameters(lrs))) + edge_iterator = Catalyst.edge_iterator(lrs) + new{S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_p_idxs, edge_p_idxs, mtk_ps, p_setters, + nonspatial_rs_p_idxs, vert_ps, work_ps, v_ps_idx_types, transport_rates, + t_rate_idx_types, leaving_rates, edge_iterator) + end +end + + + + + + + + + + + + # Functor with information for the forcing function of a spatial ODE with spatial movement on a lattice. struct LatticeTransportODEf{S,T} """The ODEFunction of the (non-spatial) ReactionSystem that generated this LatticeTransportODEf instance.""" From cea895b890b4f270a198715429aa1504b6437601 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 16:03:56 -0400 Subject: [PATCH 408/446] up --- test/extensions/homotopy_continuation.jl | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/extensions/homotopy_continuation.jl b/test/extensions/homotopy_continuation.jl index 024c7dca18..aa5e7a3548 100644 --- a/test/extensions/homotopy_continuation.jl +++ b/test/extensions/homotopy_continuation.jl @@ -25,7 +25,7 @@ let u0 = [:X1 => 2.0, :X2 => 2.0, :X3 => 2.0, :X2_2X3 => 2.0] # Computes the single steady state, checks that when given to the ODE rhs, all are evaluated to 0. - hc_ss = hc_steady_states(rs, ps; u0 = u0, show_progress = false, seed = 1234) + hc_ss = hc_steady_states(rs, ps; u0 = u0, show_progress = false, seed = 0x000004d2) hc_ss = Pair.(unknowns(rs), hc_ss[1]) @test maximum(abs.(f_eval(rs, hc_ss, ps, 0.0))) ≈ 0.0 atol = 1e-12 @@ -45,11 +45,11 @@ let end ps = [:k3 => 1.0, :k2 => 2.0, :k4 => 1.5, :k1=>8.0] - hc_ss_1 = hc_steady_states(wilhelm_2009_model, ps; seed = 1234, show_progress = false) + hc_ss_1 = hc_steady_states(wilhelm_2009_model, ps; seed = 0x000004d2, show_progress = false) @test sort(hc_ss_1, by = sol->sol[1]) ≈ [[0.0, 0.0], [0.5, 2.0], [4.5, 6.0]] - hc_ss_2 = hc_steady_states(wilhelm_2009_model, ps; seed = 1234, show_progress = false) - hc_ss_3 = hc_steady_states(wilhelm_2009_model, ps; seed = 1234, show_progress = false) + hc_ss_2 = hc_steady_states(wilhelm_2009_model, ps; seed = 0x000004d2, show_progress = false) + hc_ss_3 = hc_steady_states(wilhelm_2009_model, ps; seed = 0x000004d2, show_progress = false) @test hc_ss_1 != hc_ss_2 @test hc_ss_2 == hc_ss_3 end @@ -69,7 +69,7 @@ let ps = (:kY1 => 1.0, :kY2 => 3, :kZ1 => 1.0, :kZ2 => 4.0) u0_1 = (:Y1 => 1.0, :Y2 => 3, :Z1 => 10, :Z2 =>40.0) - ss_1 = sort(hc_steady_states(rs_1, ps; u0 = u0_1, show_progress = false, seed = 1234), by = sol->sol[1]) + ss_1 = sort(hc_steady_states(rs_1, ps; u0 = u0_1, show_progress = false, seed = 0x000004d2), by = sol->sol[1]) @test ss_1 ≈ [[0.2, 0.1, 3.0, 1.0, 40.0, 10.0]] rs_2 = @reaction_network begin @@ -81,7 +81,7 @@ let end u0_2 = [:B2 => 1.0, :B1 => 3.0, :A2 => 10.0, :A1 =>40.0] - ss_2 = sort(hc_steady_states(rs_2, ps; u0 = u0_2, show_progress = false, seed = 1234), by = sol->sol[1]) + ss_2 = sort(hc_steady_states(rs_2, ps; u0 = u0_2, show_progress = false, seed = 0x000004d2), by = sol->sol[1]) @test ss_1 ≈ ss_2 end @@ -96,7 +96,7 @@ let d, X --> 0 end ps = [:v => 5.0, :K => 2.5, :n => 3, :d => 1.0] - sss = hc_steady_states(rs, ps; filter_negative = false, show_progress = false, seed = 1234) + sss = hc_steady_states(rs, ps; filter_negative = false, show_progress = false, seed = 0x000004d2) @test length(sss) == 4 for ss in sss @@ -104,7 +104,7 @@ let end ps = [:v => 5.0, :K => 2.5, :n => 2.7, :d => 1.0] - @test_throws Exception hc_steady_states(rs, ps; show_progress = false, seed = 1234) + @test_throws Exception hc_steady_states(rs, ps; show_progress = false, seed = 0x000004d2) end @@ -125,7 +125,7 @@ let # Checks that homotopy continuation correctly find the system's single steady state. ps = [:p => 2.0, :d => 1.0, :k => 5.0] - hc_ss = hc_steady_states(rs, ps; show_progress = false, seed = 1234) + hc_ss = hc_steady_states(rs, ps; show_progress = false, seed = 0x000004d2) @test hc_ss ≈ [[2.0, 0.2, 10.0]] end @@ -138,7 +138,7 @@ let p_start = [:p => 1.0, :d => 0.2] # Computes bifurcation diagram. - @test_throws Exception hc_steady_states(incomplete_network, p_start; show_progress = false, seed = 1234) + @test_throws Exception hc_steady_states(incomplete_network, p_start; show_progress = false, seed = 0x000004d2) end # Tests that non-autonomous system throws an error @@ -147,5 +147,5 @@ let (k,t), 0 <--> X end ps = [:k => 1.0] - @test_throws Exception hc_steady_states(rs, ps; show_progress = false, seed = 1234) + @test_throws Exception hc_steady_states(rs, ps; show_progress = false, seed = 0x000004d2) end \ No newline at end of file From bb6370d01e867b091e457fb265f78666bf364b2f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 16:47:19 -0400 Subject: [PATCH 409/446] save progress --- .../spatial_ODE_systems.jl | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 7d4893bea6..7b2751838e 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -74,7 +74,8 @@ struct LatticeTransportODEFunction{R,S,T} function LatticeTransportODEFunction(ofunc::S, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, edge_ps, transport_rates::Vector{Pair{Int64, SparseMatrixCSC{T, Int64}}}, - lrs::LatticeReactionSystem) where {S,T} + lrs::LatticeReactionSystem, jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, + sparse::Bool) where {S,T} # Records which parameters and rates are uniform and which are not. v_ps_idx_types = map(vp -> length(vp[2]) == 1, vert_ps) t_rate_idx_types = map(tr -> size(tr[2]) == (1,1), transport_rates) @@ -108,12 +109,58 @@ struct LatticeTransportODEFunction{R,S,T} # Declares `work_ps` (used as storage during computation) and the edge iterator. work_ps = zeros(length(parameters(lrs))) edge_iterator = Catalyst.edge_iterator(lrs) - new{S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_p_idxs, edge_p_idxs, mtk_ps, p_setters, + new{typeof(jac_transport),S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_p_idxs, edge_p_idxs, mtk_ps, p_setters, nonspatial_rs_p_idxs, vert_ps, work_ps, v_ps_idx_types, transport_rates, - t_rate_idx_types, leaving_rates, edge_iterator) + t_rate_idx_types, leaving_rates, edge_iterator, jac_transport) + end +end + + +# Defines the forcing functor's effect on the (spatial) ODE system. +function (f_func::LatticeTransportODEFunction)(du::AbstractVector, u, p, t) + # Updates for non-spatial reactions. + for vert_i in 1:(f_func.num_verts) + # Gets the indices of all the species at vertex i. + idxs = get_indexes(vert_i, f_func.num_species) + + # Updates the work vector to contain the vertex parameter values for vertex vert_i. + update_work_vert_ps!(f_func, p, vert_i) + + # Evaluate reaction contributions to du at vert_i. + f_func.ofunc((@view du[idxs]), (@view u[idxs]), f_func.mtk_ps, t) + end + + # s_idx is the species index among transport species, s is the index among all species. + # rates are the species' transport rates. + for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) + # Rate for leaving source vertex vert_i. + for vert_i in 1:(f_func.num_verts) + idx_src = get_index(vert_i, s, f_func.num_species) + du[idx_src] -= f_func.leaving_rates[s_idx, vert_i] * u[idx_src] + end + # Add rates for entering a destination vertex via an incoming edge. + for e in f_func.edge_iterator + idx_src = get_index(e[1], s, f_func.num_species) + idx_dst = get_index(e[2], s, f_func.num_species) + du[idx_dst] += get_transport_rate(s_idx, f_func, e) * u[idx_src] + end end end +# Defines the Jacobian functor's effect on the (spatial) ODE system. +function (jac_func::LatticeTransportODEFunction)(J::AbstractMatrix, u, p, t) + J .= 0.0 + + # Update the Jacobian from non-spatial reaction terms. + for vert_i in 1:(jac_func.num_verts) + idxs = get_indexes(vert_i, jac_func.num_species) + update_work_vert_ps!(jac_func, p, vert_i) + jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), jac_func.mtk_ps, t) + end + + # Updates for the spatial reactions (adds the Jacobian values from the transportation reactions). + J .+= jac_func.jac_transport +end @@ -399,6 +446,16 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{Basi J = nothing end + ofunc_dense = ODEFunction(osys; jac = true, sparse = false) + ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) + jac_transport = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) + f = LatticeTransportODEFunction(ofunc_sparse, vert_ps, edge_ps, transport_rates, lrs, jac_transport, sparse) + if jac + J = f + else + J = nothing + end + return ODEFunction(f; jac = J, jac_prototype = jac_prototype) end From 9f69a96e767dd0a3aaf181d25ae4d4e00424fa6f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 16:52:18 -0400 Subject: [PATCH 410/446] up --- test/extensions/homotopy_continuation.jl | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/extensions/homotopy_continuation.jl b/test/extensions/homotopy_continuation.jl index aa5e7a3548..3a59f0f3a8 100644 --- a/test/extensions/homotopy_continuation.jl +++ b/test/extensions/homotopy_continuation.jl @@ -25,7 +25,7 @@ let u0 = [:X1 => 2.0, :X2 => 2.0, :X3 => 2.0, :X2_2X3 => 2.0] # Computes the single steady state, checks that when given to the ODE rhs, all are evaluated to 0. - hc_ss = hc_steady_states(rs, ps; u0 = u0, show_progress = false, seed = 0x000004d2) + hc_ss = hc_steady_states(rs, ps; u0 = u0, show_progress = false, seed = 0x000004d1) hc_ss = Pair.(unknowns(rs), hc_ss[1]) @test maximum(abs.(f_eval(rs, hc_ss, ps, 0.0))) ≈ 0.0 atol = 1e-12 @@ -35,7 +35,8 @@ end # Tests for network with multiple steady state. # Tests for Symbol parameter input. -# Tests that passing kwargs to HC.solve does not error. +# Tests that passing kwargs to HC.solve does not error and have an effect (i.e. modifying the seed +# slightly modified the output in some way). let wilhelm_2009_model = @reaction_network begin k1, Y --> 2X @@ -43,9 +44,9 @@ let k3, X + Y --> Y k4, X --> 0 end - ps = [:k3 => 1.0, :k2 => 2.0, :k4 => 1.5, :k1=>8.0] + ps = [:k3 => 1.0, :k2 => 2.0, :k4 => 1.5, :k1 => 8.0] - hc_ss_1 = hc_steady_states(wilhelm_2009_model, ps; seed = 0x000004d2, show_progress = false) + hc_ss_1 = hc_steady_states(wilhelm_2009_model, ps; seed = 0x000004d1, show_progress = false) @test sort(hc_ss_1, by = sol->sol[1]) ≈ [[0.0, 0.0], [0.5, 2.0], [4.5, 6.0]] hc_ss_2 = hc_steady_states(wilhelm_2009_model, ps; seed = 0x000004d2, show_progress = false) @@ -69,7 +70,7 @@ let ps = (:kY1 => 1.0, :kY2 => 3, :kZ1 => 1.0, :kZ2 => 4.0) u0_1 = (:Y1 => 1.0, :Y2 => 3, :Z1 => 10, :Z2 =>40.0) - ss_1 = sort(hc_steady_states(rs_1, ps; u0 = u0_1, show_progress = false, seed = 0x000004d2), by = sol->sol[1]) + ss_1 = sort(hc_steady_states(rs_1, ps; u0 = u0_1, show_progress = false, seed = 0x000004d1), by = sol->sol[1]) @test ss_1 ≈ [[0.2, 0.1, 3.0, 1.0, 40.0, 10.0]] rs_2 = @reaction_network begin @@ -81,7 +82,7 @@ let end u0_2 = [:B2 => 1.0, :B1 => 3.0, :A2 => 10.0, :A1 =>40.0] - ss_2 = sort(hc_steady_states(rs_2, ps; u0 = u0_2, show_progress = false, seed = 0x000004d2), by = sol->sol[1]) + ss_2 = sort(hc_steady_states(rs_2, ps; u0 = u0_2, show_progress = false, seed = 0x000004d1), by = sol->sol[1]) @test ss_1 ≈ ss_2 end @@ -96,7 +97,7 @@ let d, X --> 0 end ps = [:v => 5.0, :K => 2.5, :n => 3, :d => 1.0] - sss = hc_steady_states(rs, ps; filter_negative = false, show_progress = false, seed = 0x000004d2) + sss = hc_steady_states(rs, ps; filter_negative = false, show_progress = false, seed = 0x000004d1) @test length(sss) == 4 for ss in sss @@ -104,7 +105,7 @@ let end ps = [:v => 5.0, :K => 2.5, :n => 2.7, :d => 1.0] - @test_throws Exception hc_steady_states(rs, ps; show_progress = false, seed = 0x000004d2) + @test_throws Exception hc_steady_states(rs, ps; show_progress = false, seed = 0x000004d1) end @@ -125,7 +126,7 @@ let # Checks that homotopy continuation correctly find the system's single steady state. ps = [:p => 2.0, :d => 1.0, :k => 5.0] - hc_ss = hc_steady_states(rs, ps; show_progress = false, seed = 0x000004d2) + hc_ss = hc_steady_states(rs, ps; show_progress = false, seed = 0x000004d1) @test hc_ss ≈ [[2.0, 0.2, 10.0]] end @@ -138,7 +139,7 @@ let p_start = [:p => 1.0, :d => 0.2] # Computes bifurcation diagram. - @test_throws Exception hc_steady_states(incomplete_network, p_start; show_progress = false, seed = 0x000004d2) + @test_throws Exception hc_steady_states(incomplete_network, p_start; show_progress = false, seed = 0x000004d1) end # Tests that non-autonomous system throws an error @@ -147,5 +148,5 @@ let (k,t), 0 <--> X end ps = [:k => 1.0] - @test_throws Exception hc_steady_states(rs, ps; show_progress = false, seed = 0x000004d2) + @test_throws Exception hc_steady_states(rs, ps; show_progress = false, seed = 0x000004d1) end \ No newline at end of file From d7cd5c2f3962186cc78bfe70b3a6f07a930898cd Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 7 Jul 2024 21:48:54 -0400 Subject: [PATCH 411/446] save progress --- .../spatial_ODE_systems.jl | 489 ++++-------------- src/spatial_reaction_systems/utility.jl | 30 +- 2 files changed, 107 insertions(+), 412 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 7b2751838e..ef79a0752c 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -1,57 +1,44 @@ -### Spatial ODE Functor Structures ### +### Spatial ODE Functor Structure ### -# Functor with information for the forcing function of a spatial ODE with spatial movement on a lattice. -struct LatticeTransportODEFunction{R,S,T} - """The ODEFunction of the (non-spatial) ReactionSystem that generated this LatticeTransportODEf instance.""" - ofunc::S - """The number of vertices.""" - num_verts::Int64 - """The number of species.""" - num_species::Int64 - """The indexes of the vertex parameters in the parameter vector (`parameters(lrs)`).""" - vert_p_idxs::Vector{Int64} - """The indexes of the edge parameters in the parameter vector (`parameters(lrs)`).""" - edge_p_idxs::Vector{Int64} - """ - Work in progress. +# Functor with information about a spatial Lattice Reaction ODE;s forcing and Jacobian functions. +# Also used as ODE Function input to corresponding `ODEProblem`. +struct LatticeTransportODEFunction{P,Q,R,S,T} """ - mtk_ps + The ODEFunction of the (non-spatial) ReactionSystem that generated this + LatticeTransportODEFunction instance. """ - Work in progress. - """ - p_setters + ofunc::P + """The lattice's number of vertices.""" + num_verts::Int64 + """The system's number of species.""" + num_species::Int64 """ - The non-spatial `ReactionSystem` which was used to create the `LatticeReactionSystem` contain - a set of parameters (either identical to, or a sub set of, `parameters(lrs)`). This vector - contain the indexes of the non-spatial system's parameters in `parameters(lrs)`. These are - required to manage the non-spatial ODEFunction in the spatial call. + Stores an index for each heterogeneous vertex parameter (i.e. vertex parameter which value is + not identical across the lattice). Each index corresponds to its position in the full parameter + vector (`parameters(lrs)`). """ - nonspatial_rs_p_idxs::Vector{Int64} - """The values of the parameters that are tied to vertices.""" - vert_ps::Vector{Vector{T}} + heterogeneous_vert_p_idxs::Vector{Int64} """ - Vector for storing temporary values. Repeatedly during simulations, we need to retrieve the - parameter values in a certain vertex. However, since most parameters (likely) are uniform - (and hence only have 1 value stored), we need to create a new vector each time we need to retrieve - the parameter values in a new vertex. To avoid relocating these values repeatedly, we write them - to this vector. + The MTKParameters structure which corresponds to the non-spatial `ReactionSystem`. During + simulations, as we loop through each vertex, this is updated to correspond to the vertex + parameters of that specific vertex. """ - work_ps::Vector{T} + mtk_ps::Q """ - For each parameter in vert_ps, its value is a vector with a length of either num_verts or 1. - To know whenever a parameter's value needs expanding to the work_ps array, its length needs checking. - This check is done once, and the value is stored in this array. True means a uniform value. + Stores a SymbolicIndexingInterface `setp` function for each heterogeneous vertex parameter (i.e. + vertex parameter which value is not identical across the lattice). The `setp` function at index + i of `p_setters` corresponds to the parameter in index i of `heterogeneous_vert_p_idxs`. """ - v_ps_idx_types::Vector{Bool} + p_setters::R """ A vector that stores, for each species with transportation, its transportation rate(s). Each entry is a pair from (the index of) the transported species (in the `species(lrs)` vector) to its transportation rate (each species only has a single transportation rate, the sum of all its transportation reactions' rates). If the transportation rate is uniform across all edges, - stores a single value (in a size (1,1) sparse matrix). Otherwise, stores these in a sparse matrix - where value (i,j) is the species transportation rate from vertex i to vertex j. + stores a single value (in a size (1,1) sparse matrix). Otherwise, stores these in a sparse + matrix where value (i,j) is the species transportation rate from vertex i to vertex j. """ - transport_rates::Vector{Pair{Int64,SparseMatrixCSC{T, Int64}}} + transport_rates::Vector{Pair{Int64,SparseMatrixCSC{S, Int64}}} """ For each transport rate in transport_rates, its value is a (sparse) matrix with a size of either (num_verts,num_verts) or (1,1). In the second case, the transportation rate is uniform across @@ -60,44 +47,37 @@ struct LatticeTransportODEFunction{R,S,T} """ t_rate_idx_types::Vector{Bool} """ - A matrix, NxM, where N is the number of species with transportation and M is the number of vertices. - Each value is the total rate at which that species leaves that vertex - (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D). + A matrix, NxM, where N is the number of species with transportation and M is the number of + vertices. Each value is the total rate at which that species leaves that vertex (e.g. for a + species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D). """ - leaving_rates::Matrix{T} + leaving_rates::Matrix{S} """An iterator over all the edges of the lattice.""" edge_iterator::Vector{Pair{Int64, Int64}} - """Whether the Jacobian is sparse or not.""" - sparse::Bool - """The transport rates. This is a dense or sparse matrix (depending on what type of Jacobian is used).""" - jac_transport::R + """ + The transport rates. This is a dense or sparse matrix (depending on what type of Jacobian is + used). + """ + jac_transport::T - function LatticeTransportODEFunction(ofunc::S, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, edge_ps, - transport_rates::Vector{Pair{Int64, SparseMatrixCSC{T, Int64}}}, - lrs::LatticeReactionSystem, jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, - sparse::Bool) where {S,T} - # Records which parameters and rates are uniform and which are not. - v_ps_idx_types = map(vp -> length(vp[2]) == 1, vert_ps) - t_rate_idx_types = map(tr -> size(tr[2]) == (1,1), transport_rates) - - # Computes the indexes of various parameters in in the `parameters(lrs)` vector. - vert_p_idxs = subset_indexes_of(vertex_parameters(lrs), parameters(lrs)) - edge_p_idxs = subset_indexes_of(edge_parameters(lrs), parameters(lrs)) - nonspatial_rs_p_idxs = subset_indexes_of(parameters(reactionsystem(lrs)), parameters(lrs)) + function LatticeTransportODEFunction(ofunc::P, ps::Vector{<:Pair}, + lrs::LatticeReactionSystem, transport_rates::Vector{Pair{Int64, SparseMatrixCSC{S, Int64}}}, + jac_transport::Union{Nothing, Matrix{S}, SparseMatrixCSC{S, Int64}}, sparse::Bool) where {P,S} + + # Creates a vector with the heterogeneous vertex parameters' indexes in the full parameter vector. + p_dict = Dict(ps) + heterogeneous_vert_p_idxs = findall((p_dict[p] isa Vector) && (length(p_dict[p]) > 1) + for p in parameters(lrs)) - # WIP. + # Creates the MTKParameters structure and `p_setters` vector (which are used to manage + # the vertex parameter values during the simulations). nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) - p_init = [p => Dict([vert_ps; edge_ps])[p][1] for p in parameters(nonspatial_osys)] + p_init = [p => p_dict[p][1] for p in parameters(nonspatial_osys)] mtk_ps = MT.MTKParameters(nonspatial_osys, p_init) - p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(nonspatial_osys)] + p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(lrs)[heterogeneous_vert_p_idxs]] - # Computes the indexes of the vertex parameters in the vector of parameters. - # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a - # vector). This vector is already sorted according to the order of the parameters. Here, we extract - # its values only and put them into `vert_ps`. - vert_ps = [vp[2] for vp in vert_ps] - - # Computes the leaving rate matrix. + # Computes the transport rate type vector and leaving rate matrix. + t_rate_idx_types = [size(tr[2]) == (1,1) for tr in transport_rates] leaving_rates = zeros(length(transport_rates), num_verts(lrs)) for (s_idx, tr_pair) in enumerate(transport_rates) for e in Catalyst.edge_iterator(lrs) @@ -106,261 +86,61 @@ struct LatticeTransportODEFunction{R,S,T} end end - # Declares `work_ps` (used as storage during computation) and the edge iterator. - work_ps = zeros(length(parameters(lrs))) - edge_iterator = Catalyst.edge_iterator(lrs) - new{typeof(jac_transport),S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_p_idxs, edge_p_idxs, mtk_ps, p_setters, - nonspatial_rs_p_idxs, vert_ps, work_ps, v_ps_idx_types, transport_rates, - t_rate_idx_types, leaving_rates, edge_iterator, jac_transport) + # Creates and returns the `LatticeTransportODEFunction` functor. + new{P,typeof(mtk_ps),typeof(p_setters),S,typeof(jac_transport)}(ofunc, num_verts(lrs), + num_species(lrs), heterogeneous_vert_p_idxs, mtk_ps, p_setters, transport_rates, + t_rate_idx_types, leaving_rates, Catalyst.edge_iterator(lrs), jac_transport) end end - -# Defines the forcing functor's effect on the (spatial) ODE system. -function (f_func::LatticeTransportODEFunction)(du::AbstractVector, u, p, t) +# Defines the functor's effect when applied as a forcing function. +function (lt_ofun::LatticeTransportODEFunction)(du::AbstractVector, u, p, t) # Updates for non-spatial reactions. - for vert_i in 1:(f_func.num_verts) + for vert_i in 1:(lt_ofun.num_verts) # Gets the indices of all the species at vertex i. - idxs = get_indexes(vert_i, f_func.num_species) + idxs = get_indexes(vert_i, lt_ofun.num_species) - # Updates the work vector to contain the vertex parameter values for vertex vert_i. - update_work_vert_ps!(f_func, p, vert_i) - - # Evaluate reaction contributions to du at vert_i. - f_func.ofunc((@view du[idxs]), (@view u[idxs]), f_func.mtk_ps, t) + # Updates the functors vertex parameter tracker (`mtk_ps`) to contain the vertex parameter + # values for vertex vert_i. Then evaluates the reaction contributions to du at vert_i. + update_mtk_ps!(lt_ofun, p, vert_i) + lt_ofun.ofunc((@view du[idxs]), (@view u[idxs]), lt_ofun.mtk_ps, t) end # s_idx is the species index among transport species, s is the index among all species. # rates are the species' transport rates. - for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) + for (s_idx, (s, rates)) in enumerate(lt_ofun.transport_rates) # Rate for leaving source vertex vert_i. - for vert_i in 1:(f_func.num_verts) - idx_src = get_index(vert_i, s, f_func.num_species) - du[idx_src] -= f_func.leaving_rates[s_idx, vert_i] * u[idx_src] + for vert_i in 1:(lt_ofun.num_verts) + idx_src = get_index(vert_i, s, lt_ofun.num_species) + du[idx_src] -= lt_ofun.leaving_rates[s_idx, vert_i] * u[idx_src] end # Add rates for entering a destination vertex via an incoming edge. - for e in f_func.edge_iterator - idx_src = get_index(e[1], s, f_func.num_species) - idx_dst = get_index(e[2], s, f_func.num_species) - du[idx_dst] += get_transport_rate(s_idx, f_func, e) * u[idx_src] + for e in lt_ofun.edge_iterator + idx_src = get_index(e[1], s, lt_ofun.num_species) + idx_dst = get_index(e[2], s, lt_ofun.num_species) + du[idx_dst] += get_transport_rate(s_idx, lt_ofun, e) * u[idx_src] end end end -# Defines the Jacobian functor's effect on the (spatial) ODE system. -function (jac_func::LatticeTransportODEFunction)(J::AbstractMatrix, u, p, t) +# Defines the functor's effect when applied as a Jacobian. +function (lt_ofun::LatticeTransportODEFunction)(J::AbstractMatrix, u, p, t) + # Resets the Jacobian J's values. J .= 0.0 # Update the Jacobian from non-spatial reaction terms. - for vert_i in 1:(jac_func.num_verts) - idxs = get_indexes(vert_i, jac_func.num_species) - update_work_vert_ps!(jac_func, p, vert_i) - jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), jac_func.mtk_ps, t) + for vert_i in 1:(lt_ofun.num_verts) + # Gets the indices of all the species at vertex i. + idxs = get_indexes(vert_i, lt_ofun.num_species) + + # Updates the functors vertex parameter tracker (`mtk_ps`) to contain the vertex parameter + # values for vertex vert_i. Then evaluates the reaction contributions to J at vert_i. + update_mtk_ps!(lt_ofun, p, vert_i) + lt_ofun.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), lt_ofun.mtk_ps, t) end # Updates for the spatial reactions (adds the Jacobian values from the transportation reactions). - J .+= jac_func.jac_transport -end - - - - - - - - - - - -# Functor with information for the forcing function of a spatial ODE with spatial movement on a lattice. -struct LatticeTransportODEf{S,T} - """The ODEFunction of the (non-spatial) ReactionSystem that generated this LatticeTransportODEf instance.""" - ofunc::S - """The number of vertices.""" - num_verts::Int64 - """The number of species.""" - num_species::Int64 - """The indexes of the vertex parameters in the parameter vector (`parameters(lrs)`).""" - vert_p_idxs::Vector{Int64} - """The indexes of the edge parameters in the parameter vector (`parameters(lrs)`).""" - edge_p_idxs::Vector{Int64} - """ - Work in progress. - """ - mtk_ps - """ - Work in progress. - """ - p_setters - """ - The non-spatial `ReactionSystem` which was used to create the `LatticeReactionSystem` contain - a set of parameters (either identical to, or a sub set of, `parameters(lrs)`). This vector - contain the indexes of the non-spatial system's parameters in `parameters(lrs)`. These are - required to manage the non-spatial ODEFunction in the spatial call. - """ - nonspatial_rs_p_idxs::Vector{Int64} - """The values of the parameters that are tied to vertices.""" - vert_ps::Vector{Vector{T}} - """ - Vector for storing temporary values. Repeatedly during simulations, we need to retrieve the - parameter values in a certain vertex. However, since most parameters (likely) are uniform - (and hence only have 1 value stored), we need to create a new vector each time we need to retrieve - the parameter values in a new vertex. To avoid relocating these values repeatedly, we write them - to this vector. - """ - work_ps::Vector{T} - """ - For each parameter in vert_ps, its value is a vector with a length of either num_verts or 1. - To know whenever a parameter's value needs expanding to the work_ps array, its length needs checking. - This check is done once, and the value is stored in this array. True means a uniform value. - """ - v_ps_idx_types::Vector{Bool} - """ - A vector that stores, for each species with transportation, its transportation rate(s). - Each entry is a pair from (the index of) the transported species (in the `species(lrs)` vector) - to its transportation rate (each species only has a single transportation rate, the sum of all - its transportation reactions' rates). If the transportation rate is uniform across all edges, - stores a single value (in a size (1,1) sparse matrix). Otherwise, stores these in a sparse matrix - where value (i,j) is the species transportation rate from vertex i to vertex j. - """ - transport_rates::Vector{Pair{Int64,SparseMatrixCSC{T, Int64}}} - """ - For each transport rate in transport_rates, its value is a (sparse) matrix with a size of either - (num_verts,num_verts) or (1,1). In the second case, the transportation rate is uniform across - all edges. To avoid having to check which case holds for each transportation rate, we store the - corresponding case in this value. `true` means that a species has a uniform transportation rate. - """ - t_rate_idx_types::Vector{Bool} - """ - A matrix, NxM, where N is the number of species with transportation and M is the number of vertices. - Each value is the total rate at which that species leaves that vertex - (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D). - """ - leaving_rates::Matrix{T} - """An iterator over all the edges of the lattice.""" - edge_iterator::Vector{Pair{Int64, Int64}} - - function LatticeTransportODEf(ofunc::S, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, edge_ps, - transport_rates::Vector{Pair{Int64, SparseMatrixCSC{T, Int64}}}, - lrs::LatticeReactionSystem) where {S,T} - # Records which parameters and rates are uniform and which are not. - v_ps_idx_types = map(vp -> length(vp[2]) == 1, vert_ps) - t_rate_idx_types = map(tr -> size(tr[2]) == (1,1), transport_rates) - - # Computes the indexes of various parameters in in the `parameters(lrs)` vector. - vert_p_idxs = subset_indexes_of(vertex_parameters(lrs), parameters(lrs)) - edge_p_idxs = subset_indexes_of(edge_parameters(lrs), parameters(lrs)) - nonspatial_rs_p_idxs = subset_indexes_of(parameters(reactionsystem(lrs)), parameters(lrs)) - - # WIP. - nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) - p_init = [p => Dict([vert_ps; edge_ps])[p][1] for p in parameters(nonspatial_osys)] - mtk_ps = MT.MTKParameters(nonspatial_osys, p_init) - p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(nonspatial_osys)] - - # Computes the indexes of the vertex parameters in the vector of parameters. - # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a - # vector). This vector is already sorted according to the order of the parameters. Here, we extract - # its values only and put them into `vert_ps`. - vert_ps = [vp[2] for vp in vert_ps] - - # Computes the leaving rate matrix. - leaving_rates = zeros(length(transport_rates), num_verts(lrs)) - for (s_idx, tr_pair) in enumerate(transport_rates) - for e in Catalyst.edge_iterator(lrs) - # Updates the exit rate for species s_idx from vertex e.src. - leaving_rates[s_idx, e[1]] += get_transport_rate(tr_pair[2], e, t_rate_idx_types[s_idx]) - end - end - - # Declares `work_ps` (used as storage during computation) and the edge iterator. - work_ps = zeros(length(parameters(lrs))) - edge_iterator = Catalyst.edge_iterator(lrs) - new{S,T}(ofunc, num_verts(lrs), num_species(lrs), vert_p_idxs, edge_p_idxs, mtk_ps, p_setters, - nonspatial_rs_p_idxs, vert_ps, work_ps, v_ps_idx_types, transport_rates, - t_rate_idx_types, leaving_rates, edge_iterator) - end -end - -# Functor with information for the Jacobian function of a spatial ODE with spatial movement on a lattice. -struct LatticeTransportODEjac{R,S,T} - """The ODEFunction of the (non-spatial) ReactionSystem that generated this LatticeTransportODEf instance.""" - ofunc::R - """The number of vertices.""" - num_verts::Int64 - """The number of species.""" - num_species::Int64 - """The indexes of the vertex parameters in the parameter vector (`parameters(lrs)`).""" - vert_p_idxs::Vector{Int64} - """The indexes of the edge parameters in the parameter vector (`parameters(lrs)`).""" - edge_p_idxs::Vector{Int64} - """ - Work in progress. - """ - mtk_ps - """ - Work in progress. - """ - p_setters - """ - The non-spatial `ReactionSystem` which was used to create the `LatticeReactionSystem` contain - a set of parameters (either identical to, or a sub set of, `parameters(lrs)`). This vector - contain the indexes of the non-spatial system's parameters in `parameters(lrs)`. These are - required to manage the non-spatial ODEFunction in the spatial call. - """ - nonspatial_rs_p_idxs::Vector{Int64} - """The values of the parameters that are tied to vertices.""" - vert_ps::Vector{Vector{S}} - """ - Vector for storing temporary values. Repeatedly during simulations, we need to retrieve the - parameter values in a certain vertex. However, since most parameters (likely) are uniform - (and hence only have 1 value stored), we need to create a new vector each time we need to retrieve - the parameter values in a new vertex. To avoid relocating these values repeatedly, we write them - to this vector. - """ - work_ps::Vector{S} - """ - For each parameter in vert_ps, its value is a vector with a length of either num_verts or 1. - To know whenever a parameter's value needs expanding to the work_ps array, its length needs checking. - This check is done once, and the value is stored in this array. True means a uniform value. - """ - v_ps_idx_types::Vector{Bool} - """Whether the Jacobian is sparse or not.""" - sparse::Bool - """The transport rates. This is a dense or sparse matrix (depending on what type of Jacobian is used).""" - jac_transport::T - - function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{S}}}, edge_ps, - jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, - lrs::LatticeReactionSystem, sparse::Bool) where {R,S} - - # Computes the indexes of various parameters in in the `parameters(lrs)` vector. - vert_p_idxs = subset_indexes_of(vertex_parameters(lrs), parameters(lrs)) - edge_p_idxs = subset_indexes_of(edge_parameters(lrs), parameters(lrs)) - nonspatial_rs_p_idxs = subset_indexes_of(parameters(reactionsystem(lrs)), parameters(lrs)) - - # WIP. - nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) - p_init = [p => Dict([vert_ps; edge_ps])[p][1] for p in parameters(nonspatial_osys)] - mtk_ps = MT.MTKParameters(nonspatial_osys, p_init) - p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(nonspatial_osys)] - - # Input `vert_ps` is a vector map taking each parameter symbolic to its value (potentially a - # vector). This vector is already sorted according to the order of the parameters. Here, we extract - # its values only and put them into `vert_ps`. - vert_ps = [vp[2] for vp in vert_ps] - - work_ps = zeros(length(parameters(lrs))) - v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) - new{R,S,typeof(jac_transport)}(ofunc, num_verts(lrs), num_species(lrs) , vert_p_idxs, - edge_p_idxs, mtk_ps, p_setters, nonspatial_rs_p_idxs, vert_ps, - work_ps, v_ps_idx_types, sparse, jac_transport) - end -end - -# For each symbolic in syms1, returns a vector with their indexes in syms2. -function subset_indexes_of(syms1, syms2) - [findfirst(isequal(sym1, sym2) for sym2 in syms2) for sym1 in syms1] + J .+= lt_ofun.jac_transport end ### ODEProblem ### @@ -406,57 +186,36 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{Basi edge_ps::Vector{Pair{BasicSymbolic{Real},SparseMatrixCSC{T, Int64}}}, jac::Bool, sparse::Bool, name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) where {T} + # Error check. if remove_conserved error("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s") end - # Creates a map, taking (the index in species(lrs) of) each species (with transportation) - # to its transportation rate (uniform or one value for each edge). The rates are sparse matrices. - transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) - - # Prepares the Jacobian and forcing functions (depending on jacobian and sparsity selection). + # Prepares the inputs to the `LatticeTransportODEFunction` functor. osys = complete(convert(ODESystem, reactionsystem(lrs); name, combinatoric_ratelaws, include_zero_odes, checks)) - if jac - # `build_jac_prototype` currently assumes a sparse (non-spatial) Jacobian. Hence compute this. - # `LatticeTransportODEjac` currently assumes a dense (non-spatial) Jacobian. Hence compute this. - # Long term we could write separate versions of these functions for generic input. - ofunc_dense = ODEFunction(osys; jac = true, sparse = false) - ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) - jac_transport = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) - if sparse - f = LatticeTransportODEf(ofunc_sparse, vert_ps, edge_ps, transport_rates, lrs) - jac_transport = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) - J = LatticeTransportODEjac(ofunc_dense, vert_ps, edge_ps, jac_transport, lrs, true) - jac_prototype = jac_transport - else - f = LatticeTransportODEf(ofunc_dense, vert_ps, edge_ps, transport_rates, lrs) - J = LatticeTransportODEjac(ofunc_dense, vert_ps, edge_ps, jac_transport, lrs, false) - jac_prototype = nothing - end - else - if sparse - ofunc_sparse = ODEFunction(osys; jac = false, sparse = true) - f = LatticeTransportODEf(ofunc_sparse, vert_ps, edge_ps, transport_rates, lrs) - jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = false) - else - ofunc_dense = ODEFunction(osys; jac = false, sparse = false) - f = LatticeTransportODEf(ofunc_dense, vert_ps, edge_ps, transport_rates, lrs) - jac_prototype = nothing - end - J = nothing - end - ofunc_dense = ODEFunction(osys; jac = true, sparse = false) ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) - jac_transport = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) - f = LatticeTransportODEFunction(ofunc_sparse, vert_ps, edge_ps, transport_rates, lrs, jac_transport, sparse) - if jac - J = f + transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) + + # Depending on Jacobian and sparsity options, computes the Jacobian transport matrix and prototype. + if sparse && !jac + jac_transport = nothing + jac_prototype = nothing else - J = nothing + jac_sparse = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = jac) + jac_dense = Matrix(jac_sparse) + jac_transport = (jac ? (sparse ? jac_sparse : jac_dense) : nothing) + jac_prototype = (sparse ? jac_sparse : nothing) end - return ODEFunction(f; jac = J, jac_prototype = jac_prototype) + # Creates the `LatticeTransportODEFunction` functor (if `jac`, sets it as the Jacobian as well). + f = LatticeTransportODEFunction(ofunc_dense, [vert_ps; edge_ps], lrs, transport_rates, jac_transport, sparse) + J = (jac ? f : nothing) + + # Extracts the `Symbol` form for species and parameters. Creates and returns the `ODEFunction`. + syms = MT.getname.(species(lrs)) + paramsyms = MT.getname.(parameters(lrs)) + return ODEFunction(f; jac = J, jac_prototype, syms, paramsyms) end # Builds a jacobian prototype. @@ -528,49 +287,3 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, return jac_prototype end - -# Defines the forcing functor's effect on the (spatial) ODE system. -function (f_func::LatticeTransportODEf)(du::AbstractVector, u, p, t) - # Updates for non-spatial reactions. - for vert_i in 1:(f_func.num_verts) - # Gets the indices of all the species at vertex i. - idxs = get_indexes(vert_i, f_func.num_species) - - # Updates the work vector to contain the vertex parameter values for vertex vert_i. - update_work_vert_ps!(f_func, p, vert_i) - - # Evaluate reaction contributions to du at vert_i. - f_func.ofunc((@view du[idxs]), (@view u[idxs]), f_func.mtk_ps, t) - end - - # s_idx is the species index among transport species, s is the index among all species. - # rates are the species' transport rates. - for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) - # Rate for leaving source vertex vert_i. - for vert_i in 1:(f_func.num_verts) - idx_src = get_index(vert_i, s, f_func.num_species) - du[idx_src] -= f_func.leaving_rates[s_idx, vert_i] * u[idx_src] - end - # Add rates for entering a destination vertex via an incoming edge. - for e in f_func.edge_iterator - idx_src = get_index(e[1], s, f_func.num_species) - idx_dst = get_index(e[2], s, f_func.num_species) - du[idx_dst] += get_transport_rate(s_idx, f_func, e) * u[idx_src] - end - end -end - -# Defines the Jacobian functor's effect on the (spatial) ODE system. -function (jac_func::LatticeTransportODEjac)(J::AbstractMatrix, u, p, t) - J .= 0.0 - - # Update the Jacobian from non-spatial reaction terms. - for vert_i in 1:(jac_func.num_verts) - idxs = get_indexes(vert_i, jac_func.num_species) - update_work_vert_ps!(jac_func, p, vert_i) - jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), jac_func.mtk_ps, t) - end - - # Updates for the spatial reactions (adds the Jacobian values from the transportation reactions). - J .+= jac_func.jac_transport -end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 92000302cc..3f7952f2ea 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -265,33 +265,15 @@ function get_transport_rate(transport_rate::SparseMatrixCSC{T, Int64}, edge::Pai return t_rate_idx_types ? transport_rate[1,1] : transport_rate[edge[1],edge[2]] end # Finds the transportation rate for a specific species, LatticeTransportODEf struct, and edge. -function get_transport_rate(trans_s_idx::Int64, f_func::LatticeTransportODEf, edge::Pair{Int64,Int64}) +function get_transport_rate(trans_s_idx::Int64, f_func, edge::Pair{Int64,Int64}) get_transport_rate(f_func.transport_rates[trans_s_idx][2], edge, f_func.t_rate_idx_types[trans_s_idx]) end -# Updates the internal work_ps vector for a given vertex. Updates only the parameters that -# actually are vertex parameters. -# To this vector, we write the system's parameter values at the specific vertex. -function update_work_vert_ps!(work_ps::Vector{S}, vert_p_idxs::Vector{Int64}, all_ps::Vector{T}, - vert::Int64, vert_ps_idx_types::Vector{Bool}) where {S,T} - # Loops through all parameters. - for (idx,loc_type) in enumerate(vert_ps_idx_types) - # If the parameter is uniform across the spatial structure, it will have a length-1 value vector - # (which value we write to the work vector). - # Else, we extract it value at the specific location. - work_ps[vert_p_idxs[idx]] = (loc_type ? all_ps[vert_p_idxs[idx]][1] : all_ps[vert_p_idxs[idx]][vert]) - end -end -# Input is either a LatticeTransportODEf or LatticeTransportODEjac function (which fields we pass on). -function update_work_vert_ps!(lt_ode_func, all_ps::Vector{T}, vert::Int64) where {T} - return update_work_vert_ps!(lt_ode_func.work_ps, lt_ode_func.vert_p_idxs, all_ps, vert, lt_ode_func.v_ps_idx_types) -end - -# Input is either a LatticeTransportODEf or LatticeTransportODEjac function (which fields we pass on). -function update_work_vert_ps!(lt_ode_func, all_ps::Vector{T}, vert::Int64) where {T} - for (setp, (idx, loc_type)) in zip(lt_ode_func.p_setters, enumerate(lt_ode_func.v_ps_idx_types)) - p_val = (loc_type ? all_ps[lt_ode_func.vert_p_idxs[idx]][1] : all_ps[lt_ode_func.vert_p_idxs[idx]][vert]) - setp(lt_ode_func.mtk_ps, p_val) +# For a `LatticeTransportODEFunction`, updates its stored parameters (in `mtk_ps`) so that they +# the heterogeneous parameters' values correspond to the values in the specified vertex. +function update_mtk_ps!(lt_ofun::LatticeTransportODEFunction, all_ps::Vector{T}, vert::Int64) where {T} + for (setp, idx) in zip(lt_ofun.p_setters, lt_ofun.heterogeneous_vert_p_idxs) + setp(lt_ofun.mtk_ps, all_ps[idx][vert]) end end From 102537e776c56b10e22605610ad9bc9229aaf038 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 8 Jul 2024 00:02:12 -0400 Subject: [PATCH 412/446] finish internal remake. Permit non-Float64 ints --- .../lattice_reaction_systems.jl | 6 +-- .../spatial_ODE_systems.jl | 14 +++--- src/spatial_reaction_systems/utility.jl | 29 ++++++------ .../lattice_reaction_systems.jl | 3 +- .../lattice_reaction_systems_ODEs.jl | 47 ++++++++++++------- .../lattice_reaction_systems_jumps.jl | 4 +- 6 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 43f410932e..7d985cb74c 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -29,17 +29,17 @@ struct LatticeReactionSystem{Q,R,S,T} <: MT.AbstractTimeDependentSystem All parameters related to the lattice reaction system (both those whose values are tied to vertices and edges). """ - parameters::Vector{BasicSymbolic{Real}} + parameters::Vector{Any} """ Parameters which values are tied to vertices, e.g. that possibly could have unique values at each vertex of the system. """ - vertex_parameters::Vector{BasicSymbolic{Real}} + vertex_parameters::Vector{Any} """ Parameters whose values are tied to edges (adjacencies), e.g. that possibly could have unique values at each edge of the system. """ - edge_parameters::Vector{BasicSymbolic{Real}} + edge_parameters::Vector{Any} """ An iterator over all the lattice's edges. Currently, the format is always a Vector{Pair{Int64,Int64}}. However, in the future, different types could potentially be used for different types of lattice diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index ef79a0752c..a037030d28 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -62,7 +62,7 @@ struct LatticeTransportODEFunction{P,Q,R,S,T} function LatticeTransportODEFunction(ofunc::P, ps::Vector{<:Pair}, lrs::LatticeReactionSystem, transport_rates::Vector{Pair{Int64, SparseMatrixCSC{S, Int64}}}, - jac_transport::Union{Nothing, Matrix{S}, SparseMatrixCSC{S, Int64}}, sparse::Bool) where {P,S} + jac_transport::Union{Nothing, Matrix{S}, SparseMatrixCSC{S, Int64}}) where {P,S} # Creates a vector with the heterogeneous vertex parameters' indexes in the full parameter vector. p_dict = Dict(ps) @@ -118,7 +118,7 @@ function (lt_ofun::LatticeTransportODEFunction)(du::AbstractVector, u, p, t) for e in lt_ofun.edge_iterator idx_src = get_index(e[1], s, lt_ofun.num_species) idx_dst = get_index(e[2], s, lt_ofun.num_species) - du[idx_dst] += get_transport_rate(s_idx, lt_ofun, e) * u[idx_src] + du[idx_dst] += get_transport_rate(rates, e, lt_ofun.t_rate_idx_types[s_idx]) * u[idx_src] end end end @@ -182,10 +182,10 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, end # Builds an ODEFunction for a spatial ODEProblem. -function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, - edge_ps::Vector{Pair{BasicSymbolic{Real},SparseMatrixCSC{T, Int64}}}, +function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{R,Vector{T}}}, + edge_ps::Vector{Pair{S,SparseMatrixCSC{T, Int64}}}, jac::Bool, sparse::Bool, name, include_zero_odes, combinatoric_ratelaws, - remove_conserved, checks) where {T} + remove_conserved, checks) where {R,S,T} # Error check. if remove_conserved error("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s") @@ -209,7 +209,7 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{Basi end # Creates the `LatticeTransportODEFunction` functor (if `jac`, sets it as the Jacobian as well). - f = LatticeTransportODEFunction(ofunc_dense, [vert_ps; edge_ps], lrs, transport_rates, jac_transport, sparse) + f = LatticeTransportODEFunction(ofunc_dense, [vert_ps; edge_ps], lrs, transport_rates, jac_transport) J = (jac ? f : nothing) # Extracts the `Symbol` form for species and parameters. Creates and returns the `ODEFunction`. @@ -268,7 +268,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, end # Create a sparse Jacobian prototype with 0-valued entries. - jac_prototype = sparse(i_idxs, j_idxs, zeros(num_entries)) + jac_prototype = sparse(i_idxs, j_idxs, zeros(T, num_entries)) # Set element values. if set_nonzero diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 3f7952f2ea..4a881118ab 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -15,7 +15,7 @@ end # From u0 input, extract their values and store them in the internal format. # Internal format: a vector on the form [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]). -function lattice_process_u0(u0_in, u0_syms::Vector{BasicSymbolic{Real}}, lrs::LatticeReactionSystem) +function lattice_process_u0(u0_in, u0_syms::Vector, lrs::LatticeReactionSystem) # u0 values can be given in various forms. This converts it to a Vector{Pair{Symbolics,...}} form. # Top-level vector: Maps each species to its value(s). u0 = lattice_process_input(u0_in, u0_syms) @@ -32,8 +32,8 @@ end # From a parameter input, split it into vertex parameters and edge parameters. # Store these in the desired internal format. -function lattice_process_p(ps_in, ps_vertex_syms::Vector{BasicSymbolic{Real}}, - ps_edge_syms::Vector{BasicSymbolic{Real}}, lrs::LatticeReactionSystem) +function lattice_process_p(ps_in, ps_vertex_syms::Vector, + ps_edge_syms::Vector, lrs::LatticeReactionSystem) # p values can be given in various forms. This converts it to a Vector{Pair{Symbolics,...}} form. # Top-level vector: Maps each parameter to its value(s). # Second-level: Contains either a vector (vertex parameters) or a sparse matrix (edge parameters). @@ -52,7 +52,7 @@ end # The input (parameters or initial conditions) may either be a dictionary (symbolics to value(s).) # or a map (in vector or tuple form) from symbolics to value(s). This converts the input to a # (Vector) map from symbolics to value(s), where the entries have the same order as `syms`. -function lattice_process_input(input::Dict{BasicSymbolic{Real}, T}, syms::Vector{BasicSymbolic{Real}}) where {T} +function lattice_process_input(input::Dict{<:Any, T}, syms::Vector) where {T} # Error checks if !isempty(setdiff(keys(input), syms)) error("You have provided values for the following unrecognised parameters/initial conditions: $(setdiff(keys(input), syms)).") @@ -63,7 +63,7 @@ function lattice_process_input(input::Dict{BasicSymbolic{Real}, T}, syms::Vector return [sym => input[sym] for sym in syms] end -function lattice_process_input(input, syms::Vector{BasicSymbolic{Real}}) +function lattice_process_input(input, syms::Vector) if ((input isa Vector) || (input isa Tuple)) && all(entry isa Pair for entry in input) return lattice_process_input(Dict(input), syms) end @@ -71,8 +71,7 @@ function lattice_process_input(input, syms::Vector{BasicSymbolic{Real}}) end # Splits parameters into vertex and edge parameters. -# function split_parameters(ps::Vector{<: Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) -function split_parameters(ps, p_vertex_syms::Vector{BasicSymbolic{Real}}, p_edge_syms::Vector{BasicSymbolic{Real}}) +function split_parameters(ps, p_vertex_syms::Vector, p_edge_syms::Vector) vert_ps = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] edge_ps = [p for p in ps if any(isequal(p[1]), p_edge_syms)] return vert_ps, edge_ps @@ -86,7 +85,7 @@ function vertex_value_map(values, lrs::LatticeReactionSystem) end # Converts the values for an individual species/vertex parameter to its correct vector form. -function vertex_value_form(values, lrs::LatticeReactionSystem, sym::BasicSymbolic{Real}) +function vertex_value_form(values, lrs::LatticeReactionSystem, sym::BasicSymbolic) # If the value is a scalar (i.e. uniform across the lattice), return it in vector form. (values isa AbstractArray) || (return [values]) @@ -112,7 +111,7 @@ end # Converts values to the correct vector form for a Cartesian grid lattice. function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::CartesianGridRej{N,T}, - sym::BasicSymbolic{Real}) where {N,T} + sym::BasicSymbolic) where {N,T} if size(values) != lattice.dims error("The values for $sym did not have the same format as the lattice. Expected a $(lattice.dims) array, got one of size $(size(values))") end @@ -124,7 +123,7 @@ end # Converts values to the correct vector form for a masked grid lattice. function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::Array{Bool,T}, - sym::BasicSymbolic{Real}) where {T} + sym::BasicSymbolic) where {T} if size(values) != size(lattice) error("The values for $sym did not have the same format as the lattice. Expected a $(size(lattice)) array, got one of size $(size(values))") end @@ -174,9 +173,9 @@ end # The species is represented by its index (in species(lrs). # If the rate is uniform across all edges, the transportation rate will be a size (1,1) sparse matrix. # Else, the rate will be a size (num_verts,num_verts) sparse matrix. -function make_sidxs_to_transrate_map(vert_ps::Vector{Pair{BasicSymbolic{Real},Vector{T}}}, - edge_ps::Vector{Pair{BasicSymbolic{Real},SparseMatrixCSC{T, Int64}}}, - lrs::LatticeReactionSystem) where {T} +function make_sidxs_to_transrate_map(vert_ps::Vector{Pair{R,Vector{T}}}, + edge_ps::Vector{Pair{S,SparseMatrixCSC{T, Int64}}}, + lrs::LatticeReactionSystem) where {R,S,T} # Creates a dictionary with each parameter's value(s). p_val_dict = Dict(vcat(vert_ps, edge_ps)) @@ -203,7 +202,7 @@ end # and the values of all our parameters, compute the transport rate(s). # If all parameters that the rate depends on are uniform across all edges, this becomes a length-1 vector. # Else it becomes a vector where each value corresponds to the rate at one specific edge. -function compute_transport_rates(s::BasicSymbolic{Real}, p_val_dict, lrs::LatticeReactionSystem) +function compute_transport_rates(s::BasicSymbolic, p_val_dict, lrs::LatticeReactionSystem) # Find parameters involved in the rate and create a function evaluating the rate law. rate_law = get_transport_rate_law(s, lrs) relevant_ps = Symbolics.get_variables(rate_law) @@ -228,7 +227,7 @@ end # For a species, retrieve the symbolic expression for its transportation rate # (likely only a single parameter, such as `D`, but could be e.g. L*D, where L and D are parameters). # If there are several transportation reactions for the species, their sum is used. -function get_transport_rate_law(s::BasicSymbolic{Real}, lrs::LatticeReactionSystem) +function get_transport_rate_law(s::BasicSymbolic, lrs::LatticeReactionSystem) rates = filter(sr -> isequal(s, sr.species), spatial_reactions(lrs)) return sum(getfield.(rates, :rate)) end diff --git a/test/spatial_modelling/lattice_reaction_systems.jl b/test/spatial_modelling/lattice_reaction_systems.jl index b2823b3111..ce8898d18c 100644 --- a/test/spatial_modelling/lattice_reaction_systems.jl +++ b/test/spatial_modelling/lattice_reaction_systems.jl @@ -223,10 +223,11 @@ let tr_3 = TransportReaction(dZ, Z) tr_macro_1 = @transport_reaction $dX X tr_macro_2 = @transport_reaction $(rate2) Y + @test_broken false # tr_macro_3 = @transport_reaction dZ $species3 # Currently does not work, something with meta programming. @test isequal(tr_1, tr_macro_1) - @test isequal(tr_2, tr_macro_2) # Unsure why these fails, since for components equality hold: `isequal(tr_1.species, tr_macro_1.species)` and `isequal(tr_1.rate, tr_macro_1.rate)` are both true. + @test isequal(tr_2, tr_macro_2) # @test isequal(tr_3, tr_macro_3) end diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index b8114e4eee..2805612869 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -85,24 +85,23 @@ end ### Tests Simulation Correctness ### -# Checks that non-spatial brusselator simulation is identical to all on an unconnected lattice. +# Tests with non-Float64 parameter values. let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, unconnected_graph) - u0 = [:X => 2.0 + 2.0 * rand(rng), :Y => 10.0 * (1.0 * rand(rng))] - pV = brusselator_p - pE = [:dX => 0.2] - oprob_nonspatial = ODEProblem(brusselator_system, u0, (0.0, 100.0), pV) - oprob_spatial = ODEProblem(lrs, u0, (0.0, 100.0), [pV; pE]) - sol_nonspatial = solve(oprob_nonspatial, QNDF(); abstol = 1e-12, reltol = 1e-12) - sol_spatial = solve(oprob_spatial, QNDF(); abstol = 1e-12, reltol = 1e-12) - - for i in 1:nv(unconnected_graph) - @test all(isapprox.(sol_nonspatial.u[end], - sol_spatial.u[end][((i - 1) * 2 + 1):((i - 1) * 2 + 2)])) + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_cartesian_grid) + u0 = [:S => 990.0, :I => rand_v_vals(lrs), :R => 0.0] + ps_1 = [:α => 0.1, :β => 0.01, :dS => 0.01, :dI => 0.01, :dR => 0.01] + ps_2 = [:α => 1//10, :β => 1//100, :dS => 1//100, :dI => 1//100, :dR => 1//100] + ps_3 = [:α => 1//10, :β => 0.01, :dS => 0.01, :dI => 1//100, :dR => 0.01] + sol_base = solve(ODEProblem(lrs, u0, (0.0, 100.0), ps_1), Rosenbrock23(); saveat = 0.1) + for ps in [ps_1, ps_2, ps_3] + for jac in [true, false], sparse in [true, false] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps; jac, sparse) + @test sol_base ≈ solve(oprob, Rosenbrock23(); saveat = 0.1) + end end end -# Compares Jacobian and forcing functions of spatial system to analytically computed on. +# Compares Jacobian and forcing functions of spatial system to analytically computed ones. let # Creates LatticeReactionNetwork ODEProblem. rs = @reaction_network begin @@ -119,7 +118,7 @@ let D_vals[1,2] = 0.2; D_vals[2,1] = 0.2; D_vals[2,3] = 0.3; D_vals[3,2] = 0.3; u0 = [:X => [1.0, 2.0, 3.0], :Y => 1.0] - ps = [:pX => [2.0, 2.5, 3.0], :pY => 0.5, :d => 0.1, :D => D_vals] + ps = [:pX => [2.0, 2.5, 3.0], :d => 0.1, :pY => 0.5, :D => D_vals] oprob = ODEProblem(lrs, u0, (0.0, 0.0), ps; jac=true, sparse=true) # Creates manual f and jac functions. @@ -172,7 +171,7 @@ let # Sets test input values. u = rand(rng, 6) - p = [rand(rng, 3), rand(rng, 1), rand(rng, 1)] + p = [rand(rng, 3), ps[2][2], ps[3][2]] # Tests forcing function. du1 = fill(0.0, 6) @@ -614,6 +613,22 @@ let end end +# Tests with non-Int64 parameter values. +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_cartesian_grid) + u0 = [:S => 990.0, :I => rand_v_vals(lrs), :R => 0.0] + ps_1 = [:α => 0.1, :β => 0.01, :dS => 0.01, :dI => 0.01, :dR => 0.01] + ps_2 = [:α => Float32(0.1), :β => Float32(0.01), :dS => Float32(0.01), :dI => Float32(0.01), :dR => Float32(0.01)] + ps_3 = [:α => 1//10, :β => 0.01, :dS => 0.01, :dI => 1//100, :dR => Float32(0.01)] + sol_base = solve(ODEProblem(lrs, u0, (0.0, 100.0), ps_1), Rosenbrock23(); savetat = 0.1) + for ps in [ps_1, ps_2, ps_3] + for jac in [true, false], sparse in [true, false] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps; jac, sparse) + @test sol_base ≈ solve(oprob, Rosenbrock23(); savetat = 0.1) + end + end +end + # Tests various types of numbers for initial conditions/parameters (e.g. Real numbers, Float32, etc.). let # Declare u0 versions. diff --git a/test/spatial_modelling/lattice_reaction_systems_jumps.jl b/test/spatial_modelling/lattice_reaction_systems_jumps.jl index 9ad28f9ed4..51c0c8c996 100644 --- a/test/spatial_modelling/lattice_reaction_systems_jumps.jl +++ b/test/spatial_modelling/lattice_reaction_systems_jumps.jl @@ -64,7 +64,9 @@ let # Prepare various (diffusion) parameter input types. pE_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pE_2 = [:dI => 0.02, :dS => uniform_e_vals(lrs, 0.01), :dR => 0.03] + dS_vals = spzeros(num_verts(lrs), num_verts(lrs)) + foreach(e -> (dS_vals[e[1], e[2]] = 0.01), edge_iterator(lrs)) + pE_2 = [:dI => 0.02, :dS => dS_vals, :dR => 0.03] # Checks hopping rates and u0 are correct. true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] From a13b812e3a986013004966fa28a98577bb72bfba Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 8 Jul 2024 00:19:13 -0400 Subject: [PATCH 413/446] test update --- .../lattice_reaction_systems_ODEs.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index 2805612869..e4b838f9ef 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -613,18 +613,20 @@ let end end -# Tests with non-Int64 parameter values. +# Tests with non-Float64 parameter values. +# Tests for all Jacobian/sparsity combinations. +# Tests for parameters with/without uniform values. let lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_cartesian_grid) u0 = [:S => 990.0, :I => rand_v_vals(lrs), :R => 0.0] ps_1 = [:α => 0.1, :β => 0.01, :dS => 0.01, :dI => 0.01, :dR => 0.01] - ps_2 = [:α => Float32(0.1), :β => Float32(0.01), :dS => Float32(0.01), :dI => Float32(0.01), :dR => Float32(0.01)] - ps_3 = [:α => 1//10, :β => 0.01, :dS => 0.01, :dI => 1//100, :dR => Float32(0.01)] - sol_base = solve(ODEProblem(lrs, u0, (0.0, 100.0), ps_1), Rosenbrock23(); savetat = 0.1) + ps_2 = [:α => 1//10, :β => 1//100, :dS => 1//100, :dI => 1//100, :dR => 1//100] + ps_3 = [:α => 1//10, :β => 0.01, :dS => 0.01, :dI => 1//100, :dR => 0.01] + sol_base = solve(ODEProblem(lrs, u0, (0.0, 100.0), ps_1), Rosenbrock23(); saveat = 0.1) for ps in [ps_1, ps_2, ps_3] for jac in [true, false], sparse in [true, false] oprob = ODEProblem(lrs, u0, (0.0, 100.0), ps; jac, sparse) - @test sol_base ≈ solve(oprob, Rosenbrock23(); savetat = 0.1) + @test sol_base ≈ solve(oprob, Rosenbrock23(); saveat = 0.1) end end end From 5b6d6ecbbce7bc7e10aeafdc5b31edd16f210fbd Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 8 Jul 2024 06:39:55 -0400 Subject: [PATCH 414/446] enable rebuildig oproblems/integrators --- src/Catalyst.jl | 1 + .../spatial_ODE_systems.jl | 172 ++++++++++++++---- .../spatial_reactions.jl | 5 + .../lattice_reaction_systems_ODEs.jl | 118 ++++++++++++ .../lattice_reaction_systems_jumps.jl | 5 +- 5 files changed, 260 insertions(+), 41 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index edf896dd54..8b8895cdea 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -181,6 +181,7 @@ export make_edge_p_values, make_directed_edge_values # Specific spatial problem types. include("spatial_reaction_systems/spatial_ODE_systems.jl") +export rebuild_lat_internals! include("spatial_reaction_systems/lattice_jump_systems.jl") # General spatial modelling utility functions. diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index a037030d28..b186cc7199 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -1,6 +1,6 @@ ### Spatial ODE Functor Structure ### -# Functor with information about a spatial Lattice Reaction ODE;s forcing and Jacobian functions. +# Functor with information about a spatial Lattice Reaction ODEs forcing and Jacobian functions. # Also used as ODE Function input to corresponding `ODEProblem`. struct LatticeTransportODEFunction{P,Q,R,S,T} """ @@ -59,40 +59,60 @@ struct LatticeTransportODEFunction{P,Q,R,S,T} used). """ jac_transport::T + """ Whether sparse jacobian representation is used. """ + sparse::Bool + """Remove when we add this as problem metadata""" + lrs::LatticeReactionSystem function LatticeTransportODEFunction(ofunc::P, ps::Vector{<:Pair}, lrs::LatticeReactionSystem, transport_rates::Vector{Pair{Int64, SparseMatrixCSC{S, Int64}}}, - jac_transport::Union{Nothing, Matrix{S}, SparseMatrixCSC{S, Int64}}) where {P,S} - - # Creates a vector with the heterogeneous vertex parameters' indexes in the full parameter vector. - p_dict = Dict(ps) - heterogeneous_vert_p_idxs = findall((p_dict[p] isa Vector) && (length(p_dict[p]) > 1) - for p in parameters(lrs)) - - # Creates the MTKParameters structure and `p_setters` vector (which are used to manage - # the vertex parameter values during the simulations). - nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) - p_init = [p => p_dict[p][1] for p in parameters(nonspatial_osys)] - mtk_ps = MT.MTKParameters(nonspatial_osys, p_init) - p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(lrs)[heterogeneous_vert_p_idxs]] - - # Computes the transport rate type vector and leaving rate matrix. - t_rate_idx_types = [size(tr[2]) == (1,1) for tr in transport_rates] - leaving_rates = zeros(length(transport_rates), num_verts(lrs)) - for (s_idx, tr_pair) in enumerate(transport_rates) - for e in Catalyst.edge_iterator(lrs) - # Updates the exit rate for species s_idx from vertex e.src. - leaving_rates[s_idx, e[1]] += get_transport_rate(tr_pair[2], e, t_rate_idx_types[s_idx]) - end - end + jac_transport::Union{Nothing, Matrix{S}, SparseMatrixCSC{S, Int64}}, sparse) where {P,S} + # Computes `LatticeTransportODEFunction` functor fields. + heterogeneous_vert_p_idxs = make_heterogeneous_vert_p_idxs(ps, lrs) + mtk_ps, p_setters = make_mtk_ps_structs(ps, lrs, heterogeneous_vert_p_idxs) + t_rate_idx_types, leaving_rates = make_t_types_and_leaving_rates(transport_rates, lrs) # Creates and returns the `LatticeTransportODEFunction` functor. new{P,typeof(mtk_ps),typeof(p_setters),S,typeof(jac_transport)}(ofunc, num_verts(lrs), num_species(lrs), heterogeneous_vert_p_idxs, mtk_ps, p_setters, transport_rates, - t_rate_idx_types, leaving_rates, Catalyst.edge_iterator(lrs), jac_transport) + t_rate_idx_types, leaving_rates, Catalyst.edge_iterator(lrs), jac_transport, sparse, lrs) + end +end + +# `LatticeTransportODEFunction` helper functions (re used by rebuild function later on). + +# Creates a vector with the heterogeneous vertex parameters' indexes in the full parameter vector. +function make_heterogeneous_vert_p_idxs(ps, lrs) + p_dict = Dict(ps) + return findall((p_dict[p] isa Vector) && (length(p_dict[p]) > 1) for p in parameters(lrs)) +end + +# Creates the MTKParameters structure and `p_setters` vector (which are used to manage +# the vertex parameter values during the simulations). +function make_mtk_ps_structs(ps, lrs, heterogeneous_vert_p_idxs) + p_dict = Dict(ps) + nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) + p_init = [p => p_dict[p][1] for p in parameters(nonspatial_osys)] + mtk_ps = MT.MTKParameters(nonspatial_osys, p_init) + p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(lrs)[heterogeneous_vert_p_idxs]] + return mtk_ps, p_setters +end + +# Computes the transport rate type vector and leaving rate matrix. +function make_t_types_and_leaving_rates(transport_rates, lrs) + t_rate_idx_types = [size(tr[2]) == (1,1) for tr in transport_rates] + leaving_rates = zeros(length(transport_rates), num_verts(lrs)) + for (s_idx, tr_pair) in enumerate(transport_rates) + for e in Catalyst.edge_iterator(lrs) + # Updates the exit rate for species s_idx from vertex e.src. + leaving_rates[s_idx, e[1]] += get_transport_rate(tr_pair[2], e, t_rate_idx_types[s_idx]) + end end + return t_rate_idx_types, leaving_rates end +### Spatial ODE Functor Functions ### + # Defines the functor's effect when applied as a forcing function. function (lt_ofun::LatticeTransportODEFunction)(du::AbstractVector, u, p, t) # Updates for non-spatial reactions. @@ -198,7 +218,7 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{R,Ve transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) # Depending on Jacobian and sparsity options, computes the Jacobian transport matrix and prototype. - if sparse && !jac + if !sparse && !jac jac_transport = nothing jac_prototype = nothing else @@ -209,7 +229,7 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{R,Ve end # Creates the `LatticeTransportODEFunction` functor (if `jac`, sets it as the Jacobian as well). - f = LatticeTransportODEFunction(ofunc_dense, [vert_ps; edge_ps], lrs, transport_rates, jac_transport) + f = LatticeTransportODEFunction(ofunc_dense, [vert_ps; edge_ps], lrs, transport_rates, jac_transport, sparse) J = (jac ? f : nothing) # Extracts the `Symbol` form for species and parameters. Creates and returns the `ODEFunction`. @@ -267,23 +287,95 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, end end - # Create a sparse Jacobian prototype with 0-valued entries. + # Create a sparse Jacobian prototype with 0-valued entries. If requested, + # updates values with non-zero entries. jac_prototype = sparse(i_idxs, j_idxs, zeros(T, num_entries)) + set_nonzero && set_jac_transport_values!(jac_prototype, transport_rates, lrs) - # Set element values. - if set_nonzero - for (s, rates) in transport_rates, e in edge_iterator(lrs) - idx_src = get_index(e[1], s, num_species(lrs)) - idx_dst = get_index(e[2], s, num_species(lrs)) - val = get_transport_rate(rates, e, size(rates)==(1,1)) + return jac_prototype +end - # Term due to species leaving source vertex. - jac_prototype[idx_src, idx_src] -= val +# For a Jacobian prototype with zero-valued entries. Set entry values according to a set of +# transport reaction values. +function set_jac_transport_values!(jac_prototype, transport_rates, lrs) + for (s, rates) in transport_rates, e in edge_iterator(lrs) + idx_src = get_index(e[1], s, num_species(lrs)) + idx_dst = get_index(e[2], s, num_species(lrs)) + val = get_transport_rate(rates, e, size(rates)==(1,1)) - # Term due to species arriving to destination vertex. - jac_prototype[idx_src, idx_dst] += val - end + # Term due to species leaving source vertex. + jac_prototype[idx_src, idx_src] -= val + + # Term due to species arriving to destination vertex. + jac_prototype[idx_src, idx_dst] += val end +end - return jac_prototype +### Functor Updating Functionality ### + +# Function for rebuilding a `LatticeReactionSystem` `ODEProblem` after it has been updated. +function rebuild_lat_internals!(oprob::ODEProblem) + rebuild_lat_internals!(oprob.f.f, oprob.p, oprob.f.f.lrs) +end + +# Function for rebuilding a `LatticeReactionSystem` integrator after it has been updated. +# We could specify `integrator`'s type, but that required adding OrdinaryDiffEq as a direct +# dependency of Catalyst. +function rebuild_lat_internals!(integrator) + rebuild_lat_internals!(integrator.f.f, integrator.p, integrator.f.f.lrs) end + +# Function which rebuilds a `LatticeTransportODEFunction` functor for a new parameter set. +function rebuild_lat_internals!(lt_ofun::LatticeTransportODEFunction, ps_new, lrs::LatticeReactionSystem) + # Computes Jacobian properties. + jac = !isnothing(lt_ofun.jac_transport) + sparse = lt_ofun.sparse + + # Recreates the new parameters on the requisite form. + ps_new = [(length(p) == 1) ? p[1] : p for p in deepcopy(ps_new)] + ps_new = [p => p_val for (p, p_val) in zip(parameters(lrs), deepcopy(ps_new))] + vert_ps, edge_ps = lattice_process_p(ps_new, vertex_parameters(lrs), edge_parameters(lrs), lrs) + ps_new = [vert_ps; edge_ps] + + # Creates the new transport rates and transport Jacobian part. + transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) + if !isnothing(lt_ofun.jac_transport) + lt_ofun.jac_transport .= 0.0 + set_jac_transport_values!(lt_ofun.jac_transport, transport_rates, lrs) + end + + # Computes new field values. + heterogeneous_vert_p_idxs = make_heterogeneous_vert_p_idxs(ps_new, lrs) + mtk_ps, p_setters = make_mtk_ps_structs(ps_new, lrs, heterogeneous_vert_p_idxs) + t_rate_idx_types, leaving_rates = make_t_types_and_leaving_rates(transport_rates, lrs) + + # Updates functor fields. + replace_vec!(lt_ofun.heterogeneous_vert_p_idxs, heterogeneous_vert_p_idxs) + replace_vec!(lt_ofun.p_setters, p_setters) + replace_vec!(lt_ofun.transport_rates, transport_rates) + replace_vec!(lt_ofun.t_rate_idx_types, t_rate_idx_types) + lt_ofun.leaving_rates .= leaving_rates + + # Updating the `MTKParameters` structure is a bit more complicated. + p_dict = Dict(ps_new) + osys = complete(convert(ODESystem, reactionsystem(lrs))) + for p in parameters(osys) + MT.setp(osys, p)(lt_ofun.mtk_ps, (p_dict[p] isa Number) ? p_dict[p] : p_dict[p][1]) + end + + return nothing +end + +# Specialised function which replaced one vector in another in a mutating way. +# Required to update the vectors in the `LatticeTransportODEFunction` functor. +function replace_vec!(vec1, vec2) + l1 = length(vec1) + l2 = length(vec2) + + # Updates the fields, then deletes superfluous fields, or additional ones. + for (i, v) in enumerate(vec2[1:min(l1, l2)]) + vec1[i] = v + end + foreach(idx -> deleteat!(vec1, idx), l1:-1:(l2 + 1)) + foreach(val -> push!(vec1, val), vec2[l1+1:l2]) +end \ No newline at end of file diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 7a941761b7..004f0646a8 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -63,6 +63,11 @@ function make_transport_reaction(rateex, species) iv = :(@variables $(DEFAULT_IV_SYM)) trxexpr = :(TransportReaction($rateex, $species)) + # Appends `edgeparameter` metadata to all declared parameters. + for idx = 4:2:(2 + 2*length(parameters)) + insert!(pexprs.args, idx, :([edgeparameter=true])) + end + quote $pexprs $iv diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index e4b838f9ef..3e9544d5fa 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -547,6 +547,124 @@ let @test all(isequal.(ss_1, ss_2)) end +### ODEProblem & Integrator Interfacing ### + +# Checks that basic interfacing with ODEProblem parameters (getting and setting) works. +let + # Creates an initial `ODEProblem`. + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_1d_cartesian_grid) + u0 = [:X => 1.0, :Y => 2.0] + ps = [:A => 1.0, :B => [1.0, 2.0, 3.0, 4.0, 5.0], :dX => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), ps) + + # Checks that retrieved parameters are correct. + @test oprob.ps[:A] == [1.0] + @test oprob.ps[:B] == [1.0, 2.0, 3.0, 4.0, 5.0] + @test oprob.ps[:dX] == sparse([1], [1], [0.1]) + + # Updates content. + oprob.ps[:A] = [10.0, 20.0, 30.0, 40.0, 50.0] + oprob.ps[:B] = [10.0] + oprob.ps[:dX] = [0.01] + + # Checks that content is correct. + @test oprob.ps[:A] == [10.0, 20.0, 30.0, 40.0, 50.0] + @test oprob.ps[:B] == [10.0] + @test oprob.ps[:dX] == [0.01] +end + +# Checks that the `rebuild_lat_internals!` function is correctly applied to an ODEProblem. +let + # Creates a brusselator `LatticeReactionSystem`. + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_2, very_small_2d_cartesian_grid) + + # Checks for all combinations of Jacobian and sparsity. + for jac in [false, true], sparse in [false, true] + # Creates an initial ODEProblem. + u0 = [:X => 1.0, :Y => [1.0 2.0; 3.0 4.0]] + dY_vals = spzeros(4,4) + dY_vals[1,2] = 0.1; dY_vals[2,1] = 0.1; + dY_vals[1,3] = 0.2; dY_vals[3,1] = 0.2; + dY_vals[2,4] = 0.3; dY_vals[4,2] = 0.3; + dY_vals[3,4] = 0.4; dY_vals[4,3] = 0.4; + ps = [:A => 1.0, :B => [4.0 5.0; 6.0 7.0], :dX => 0.1, :dY => dY_vals] + oprob_1 = ODEProblem(lrs, u0, (0.0, 10.0), ps; jac, sparse) + + # Creates an alternative version of the ODEProblem. + dX_vals = spzeros(4,4) + dX_vals[1,2] = 0.01; dX_vals[2,1] = 0.01; + dX_vals[1,3] = 0.02; dX_vals[3,1] = 0.02; + dX_vals[2,4] = 0.03; dX_vals[4,2] = 0.03; + dX_vals[3,4] = 0.04; dX_vals[4,3] = 0.04; + ps = [:A => [1.1 1.2; 1.3 1.4], :B => 5.0, :dX => dX_vals, :dY => 0.01] + oprob_2 = ODEProblem(lrs, u0, (0.0, 10.0), ps; jac, sparse) + + # Modifies the initial ODEProblem to be identical to the new one. + oprob_1.ps[:A] = [1.1 1.2; 1.3 1.4] + oprob_1.ps[:B] = [5.0] + oprob_1.ps[:dX] = dX_vals + oprob_1.ps[:dY] = [0.01] + rebuild_lat_internals!(oprob_1) + + # Checks that simulations of the two `ODEProblem`s are identical. + @test solve(oprob_1, Rodas5P()) ≈ solve(oprob_2, Rodas5P()) + end +end + +# Checks that the `rebuild_lat_internals!` function is correctly applied to an integrator. +# Does through by applying it within a callback, and compare to simulations without callback. +let + # Prepares problem inputs. + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_2, very_small_2d_cartesian_grid) + u0 = [:X => 1.0, :Y => [1.0 2.0; 3.0 4.0]] + A1 = 1.0 + B1 = [4.0 5.0; 6.0 7.0] + A2 = [1.1 1.2; 1.3 1.4] + B2 = 5.0 + dY_vals = spzeros(4,4) + dY_vals[1,2] = 0.1; dY_vals[2,1] = 0.1; + dY_vals[1,3] = 0.2; dY_vals[3,1] = 0.2; + dY_vals[2,4] = 0.3; dY_vals[4,2] = 0.3; + dY_vals[3,4] = 0.4; dY_vals[4,3] = 0.4; + dX_vals = spzeros(4,4) + dX_vals[1,2] = 0.01; dX_vals[2,1] = 0.01; + dX_vals[1,3] = 0.02; dX_vals[3,1] = 0.02; + dX_vals[2,4] = 0.03; dX_vals[4,2] = 0.03; + dX_vals[3,4] = 0.04; dX_vals[4,3] = 0.04; + dX1 = 0.1 + dY1 = dY_vals + dX2 = dX_vals + dY2 = 0.01 + ps_1 = [:A => A1, :B => B1, :dX => dX1, :dY => dY1] + ps_2 = [:A => A2, :B => B2, :dX => dX2, :dY => dY2] + + # Checks for all combinations of Jacobian and sparsity. + for jac in [false, true], sparse in [false, true] + # Creates simulation through two different separate simulations. + oprob_1_1 = ODEProblem(lrs, u0, (0.0, 5.0), ps_1; jac, sparse) + sol_1_1 = solve(oprob_1_1, Rosenbrock23(); saveat = 1.0, abstol = 1e-8, reltol = 1e-8) + u0_1_2 = [:X => sol_1_1.u[end][1:2:end], :Y => sol_1_1.u[end][2:2:end]] + oprob_1_2 = ODEProblem(lrs, u0_1_2, (0.0, 5.0), ps_2; jac, sparse) + sol_1_2 = solve(oprob_1_2, Rosenbrock23(); saveat = 1.0, abstol = 1e-8, reltol = 1e-8) + + # Creates simulation through a single simulation with a callback + oprob_2 = ODEProblem(lrs, u0, (0.0, 10.0), ps_1; jac, sparse) + condition(u, t, integrator) = (t == 5.0) + function affect!(integrator) + integrator.ps[:A] = A2 + integrator.ps[:B] = [B2] + integrator.ps[:dX] = dX2 + integrator.ps[:dY] = [dY2] + rebuild_lat_internals!(integrator) + end + callback = DiscreteCallback(condition, affect!) + sol_2 = solve(oprob_2, Rosenbrock23(); saveat = 1.0, tstops = [5.0], callback, abstol = 1e-8, reltol = 1e-8) + + # Check that trajectories are equivalent. + @test [sol_1_1.u; sol_1_2.u] ≈ sol_2.u + end +end + ### Tests Special Cases ### # Create network using either graphs or di-graphs. diff --git a/test/spatial_modelling/lattice_reaction_systems_jumps.jl b/test/spatial_modelling/lattice_reaction_systems_jumps.jl index 51c0c8c996..69928e9c12 100644 --- a/test/spatial_modelling/lattice_reaction_systems_jumps.jl +++ b/test/spatial_modelling/lattice_reaction_systems_jumps.jl @@ -200,4 +200,7 @@ let @test abs(d) < reltol * non_spatial_mean[i] end end -end \ No newline at end of file +end + + +### JumpProblem & Integrator Interfacing ### \ No newline at end of file From e231734ff71b10613c5caa3d498f088a042c4016 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 8 Jul 2024 07:43:15 -0400 Subject: [PATCH 415/446] add additional tests, use argumenterror where appropriate --- .../lattice_jump_systems.jl | 2 +- .../lattice_reaction_systems.jl | 18 +-- .../spatial_ODE_systems.jl | 2 +- .../spatial_reactions.jl | 2 +- src/spatial_reaction_systems/utility.jl | 24 ++-- .../lattice_reaction_systems.jl | 115 ++++++++++++++++-- .../lattice_reaction_systems_ODEs.jl | 39 +++++- .../lattice_reaction_systems_jumps.jl | 12 +- .../lattice_reaction_systems_lattice_types.jl | 4 +- 9 files changed, 181 insertions(+), 37 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 44b26ecbbd..5dd8084bf7 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -30,7 +30,7 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator combinatoric_ratelaws = get_combinatoric_ratelaws(reactionsystem(lrs)), kwargs...) # Error checks. if !isnothing(dprob.f.sys) - error("Unexpected `DiscreteProblem` passed into `JumpProblem`. Was a `LatticeReactionSystem` used as input to the initial `DiscreteProblem`?") + throw(ArgumentError("Unexpected `DiscreteProblem` passed into `JumpProblem`. Was a `LatticeReactionSystem` used as input to the initial `DiscreteProblem`?")) end # Computes hopping constants and mass action jumps (requires some internal juggling). diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 7d985cb74c..ca9c88c911 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -51,19 +51,22 @@ struct LatticeReactionSystem{Q,R,S,T} <: MT.AbstractTimeDependentSystem num_verts::Int64, num_edges::Int64, edge_iterator::T) where {Q,R,S,T} # Error checks. if !(R <: AbstractSpatialReaction) - error("The second argument must be a vector of AbstractSpatialReaction subtypes.") + throw(ArgumentError("The second argument must be a vector of AbstractSpatialReaction subtypes.")) + end + if !iscomplete(rs) + throw(ArgumentError("A non-complete `ReactionSystem` was used as input, this is not permitted.")) end if !isempty(MT.get_systems(rs)) - error("A non-flattened (hierarchical) `ReactionSystem` was used as input. `LatticeReactionSystem`s can only be based on non-hierarchical `ReactionSystem`s.") + throw(ArgumentError("A non-flattened (hierarchical) `ReactionSystem` was used as input. `LatticeReactionSystem`s can only be based on non-hierarchical `ReactionSystem`s.")) end if length(species(rs)) != length(unknowns(rs)) - error("The `ReactionSystem` used as input contain variable unknowns (in addition to species unknowns). This is not permitted (the input `ReactionSystem` must contain species unknowns only).") + throw(ArgumentError("The `ReactionSystem` used as input contain variable unknowns (in addition to species unknowns). This is not permitted (the input `ReactionSystem` must contain species unknowns only).")) end if length(reactions(rs)) != length(equations(rs)) - error("The `ReactionSystem` used as input contain equations (in addition to reactions). This is not permitted.") + throw(ArgumentError("The `ReactionSystem` used as input contain equations (in addition to reactions). This is not permitted.")) end if !isempty(MT.continuous_events(rs)) || !isempty(MT.discrete_events(rs)) - @warn "The `ReactionSystem` used as input to `LatticeReactionSystem contain events. These will be ignored in any simulations based on the created `LatticeReactionSystem`." + throw(ArgumentError("The `ReactionSystem` used as input to `LatticeReactionSystem contain events. These will be ignored in any simulations based on the created `LatticeReactionSystem`.")) end if !isempty(observed(rs)) @warn "The `ReactionSystem` used as input to `LatticeReactionSystem contain observables. It will not be possible to access these from the created `LatticeReactionSystem`." @@ -295,7 +298,7 @@ E.g. for a lattice `CartesianGrid(4,6)`, `(4,6)` is returned. grid_size(lrs::LatticeReactionSystem) = grid_size(lattice(lrs)) grid_size(lattice::CartesianGridRej{N,T}) where {N,T} = lattice.dims grid_size(lattice::Array{Bool, N}) where {N} = size(lattice) -grid_size(lattice::Graph) = error("Grid size is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") +grid_size(lattice::Graphs.AbstractGraph) = throw(ArgumentError("Grid size is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).")) """ grid_dims(lrs::LatticeReactionSystem) @@ -305,7 +308,7 @@ The output is either `1`, `2`, or `3`. """ grid_dims(lrs::LatticeReactionSystem) = grid_dims(lattice(lrs)) grid_dims(lattice::GridLattice{N,T}) where {N,T} = return N -grid_dims(lattice::Graph) = error("Grid dimensions is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).") +grid_dims(lattice::Graphs.AbstractGraph) = throw(ArgumentError("Grid dimensions is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).")) """ get_lattice_graph(lrs::LatticeReactionSystem) @@ -333,7 +336,6 @@ reactions(lrs::LatticeReactionSystem) = reactions(reactionsystem(lrs)) MT.nameof(lrs::LatticeReactionSystem) = MT.nameof(reactionsystem(lrs)) MT.get_iv(lrs::LatticeReactionSystem) = MT.get_iv(reactionsystem(lrs)) MT.equations(lrs::LatticeReactionSystem) = MT.equations(reactionsystem(lrs)) -MT.equations(lrs::LatticeReactionSystem) = MT.equations(reactionsystem(lrs)) MT.unknowns(lrs::LatticeReactionSystem) = MT.unknowns(reactionsystem(lrs)) MT.get_metadata(lrs::LatticeReactionSystem) = MT.get_metadata(reactionsystem(lrs)) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index b186cc7199..40da3aa6b0 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -208,7 +208,7 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{R,Ve remove_conserved, checks) where {R,S,T} # Error check. if remove_conserved - error("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s") + throw(ArgumentError("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s")) end # Prepares the inputs to the `LatticeTransportODEFunction` functor. diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 004f0646a8..222ea4640b 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -165,5 +165,5 @@ function find_parameters_in_rate!(parameters, rateex::ExprValues) find_parameters_in_rate!(parameters, rateex.args[i]) end end - nothing + return nothing end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 4a881118ab..4f4930e3bd 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -55,10 +55,10 @@ end function lattice_process_input(input::Dict{<:Any, T}, syms::Vector) where {T} # Error checks if !isempty(setdiff(keys(input), syms)) - error("You have provided values for the following unrecognised parameters/initial conditions: $(setdiff(keys(input), syms)).") + throw(ArgumentError("You have provided values for the following unrecognised parameters/initial conditions: $(setdiff(keys(input), syms)).")) end if !isempty(setdiff(syms, keys(input))) - error("You have not provided values for the following parameters/initial conditions: $(setdiff(syms, keys(input))).") + throw(ArgumentError("You have not provided values for the following parameters/initial conditions: $(setdiff(syms, keys(input))).")) end return [sym => input[sym] for sym in syms] @@ -67,7 +67,7 @@ function lattice_process_input(input, syms::Vector) if ((input isa Vector) || (input isa Tuple)) && all(entry isa Pair for entry in input) return lattice_process_input(Dict(input), syms) end - error("Input parameters/initial conditions have the wrong format ($(typeof(input))). These should either be a Dictionary, or a Tuple or a Vector (where each entry is a Pair taking a parameter/species to its value).") + throw(ArgumentError("Input parameters/initial conditions have the wrong format ($(typeof(input))). These should either be a Dictionary, or a Tuple or a Vector (where each entry is a Pair taking a parameter/species to its value).")) end # Splits parameters into vertex and edge parameters. @@ -100,7 +100,7 @@ function vertex_value_form(values, lrs::LatticeReactionSystem, sym::BasicSymboli # For the case where the i'th value of the vector corresponds to the value in the i'th vertex. # This is the only (non-uniform) case possible for graph grids. if (length(values) != num_verts(lrs)) - error("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertices ($(num_verts(lrs))).") + throw(ArgumentError("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertices ($(num_verts(lrs))).")) end return values end @@ -113,10 +113,10 @@ end function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::CartesianGridRej{N,T}, sym::BasicSymbolic) where {N,T} if size(values) != lattice.dims - error("The values for $sym did not have the same format as the lattice. Expected a $(lattice.dims) array, got one of size $(size(values))") + throw(ArgumentError("The values for $sym did not have the same format as the lattice. Expected a $(lattice.dims) array, got one of size $(size(values))")) end if (length(values) != num_verts) - error("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertices ($(num_verts)).") + throw(ArgumentError("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertices ($(num_verts)).")) end return [values[flat_idx] for flat_idx in 1:num_verts] end @@ -125,7 +125,7 @@ end function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::Array{Bool,T}, sym::BasicSymbolic) where {T} if size(values) != size(lattice) - error("The values for $sym did not have the same format as the lattice. Expected a $(size(lattice)) array, got one of size $(size(values))") + throw(ArgumentError("The values for $sym did not have the same format as the lattice. Expected a $(size(lattice)) array, got one of size $(size(values))")) end # Pre-declares a vector with the values in each vertex (return_values). @@ -139,7 +139,7 @@ function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::Arr # Checks that the correct number of values was provided, and returns the values. if (length(return_values) != num_verts) - error("You have provided ($(length(return_values))) values for $sym. This is not equal to the number of vertices ($(num_verts)).") + throw(ArgumentError("You have provided ($(length(return_values))) values for $sym. This is not equal to the number of vertices ($(num_verts)).")) end return return_values end @@ -158,10 +158,10 @@ function edge_value_form(values, lrs::LatticeReactionSystem, sym) # Error checks. if nnz(values) != num_edges(lrs) - error("You have provided ($(nnz(values))) values for $sym. This is not equal to the number of edges ($(num_edges(lrs))).") + throw(ArgumentError("You have provided ($(nnz(values))) values for $sym. This is not equal to the number of edges ($(num_edges(lrs))).")) end if !all(Base.isstored(values, e[1], e[2]) for e in edge_iterator(lrs)) - error("Values was not provided for some edges for edge parameter $sym.") + throw(ArgumentError("Values was not provided for some edges for edge parameter $sym.")) end # Unlike initial conditions/vertex parameters, (unless uniform) edge parameters' values are @@ -296,7 +296,7 @@ function compute_edge_value(exp, lrs::LatticeReactionSystem, edge_ps) # Finds the symbols in the expression. Checks that all correspond to edge parameters. relevant_syms = Symbolics.get_variables(exp) if !all(any(isequal(sym, p) for p in edge_parameters(lrs)) for sym in relevant_syms) - error("An non-edge parameter was encountered in expressions: $exp. Here, only edge parameters are expected.") + throw(ArgumentError("An non-edge parameter was encountered in expressions: $exp. Here, only edge parameters are expected.")) end # Creates a Function tha computes the expressions value for a parameter set. @@ -320,7 +320,7 @@ function compute_vertex_value(exp, lrs::LatticeReactionSystem; u = [], ps = []) # Finds the symbols in the expression. Checks that all correspond to unknowns or vertex parameters. relevant_syms = Symbolics.get_variables(exp) if any(any(isequal(sym) in edge_parameters(lrs)) for sym in relevant_syms) - error("An edge parameter was encountered in expressions: $exp. Here, only vertex-based components are expected.") + throw(ArgumentError("An edge parameter was encountered in expressions: $exp. Here, only vertex-based components are expected.")) end # Creates a Function that computes the expressions value for a parameter set. diff --git a/test/spatial_modelling/lattice_reaction_systems.jl b/test/spatial_modelling/lattice_reaction_systems.jl index ce8898d18c..4f08f4b723 100644 --- a/test/spatial_modelling/lattice_reaction_systems.jl +++ b/test/spatial_modelling/lattice_reaction_systems.jl @@ -9,6 +9,27 @@ include("../spatial_test_networks.jl") # Pre declares a grid. grids = [very_small_2d_cartesian_grid, very_small_2d_masked_grid, very_small_2d_graph_grid] +### Test Spatial Reactions ### + +# Test creation of TransportReaction with non-parameters in rate. +# Tests that it works even when rate is highly nested. +let + @variables t + @species X(t) Y(t) + @parameters D1 D2 D3 + @test_throws ErrorException TransportReaction(D1 + D2*(D3 + Y), X) + @test_throws ErrorException TransportReaction(Y, X) +end + +# Checks that the `hash` functions works for `TransportReaction`s. +let + tr1 = @transport_reaction D1 X + tr2 = @transport_reaction D1 X + tr3 = @transport_reaction D2 X + hash(tr1, 0x0000000000000001) == hash(tr2, 0x0000000000000001) + hash(tr2, 0x0000000000000001) != hash(tr3, 0x0000000000000001) +end + ### Tests LatticeReactionSystem Getters Correctness ### # Test case 1. @@ -127,6 +148,37 @@ let end end +# Tests using various more obscure types of getters. +let + # Create LatticeReactionsSystems. + t = default_t() + @parameters p d kB kD + @species X(t) X2(t) + rxs = [ + Reaction(p, [], [X]) + Reaction(d, [X], []) + Reaction(kB, [X], [X2], [2], [1]) + Reaction(kD, [X2], [X], [1], [2]) + ] + @named rs = ReactionSystem(rxs, t; metadata = "Metadata string") + rs = complete(rs) + tr = @transport_reaction D X2 + lrs = LatticeReactionSystem(rs, [tr], small_2d_cartesian_grid) + + # Generic ones (simply forwards call to the non-spatial system). + @test isequal(reactions(lrs), rxs) + @test isequal(nameof(lrs), :rs) + @test isequal(ModelingToolkit.get_iv(lrs), t) + @test isequal(equations(lrs), rxs) + @test isequal(unknowns(lrs), [X, X2]) + @test isequal(ModelingToolkit.get_metadata(lrs), "Metadata string") + @test isequal(ModelingToolkit.get_eqs(lrs), rxs) + @test isequal(ModelingToolkit.get_unknowns(lrs), [X, X2]) + @test isequal(ModelingToolkit.get_ps(lrs), [p, d, kB, kD]) + @test isequal(ModelingToolkit.get_systems(lrs), []) + @test isequal(independent_variables(lrs), [t]) +end + ### Tests Spatial Reactions Getters Correctness ### # Test case 1. @@ -233,16 +285,6 @@ end ### Tests Error generation ### -# Test creation of TransportReaction with non-parameters in rate. -# Tests that it works even when rate is highly nested. -let - @variables t - @species X(t) Y(t) - @parameters D1 D2 D3 - @test_throws ErrorException TransportReaction(D1 + D2*(D3 + Y), X) - @test_throws ErrorException TransportReaction(Y, X) -end - # Network where diffusion species is not declared in non-spatial network. let rs = @reaction_network begin @@ -297,6 +339,59 @@ let end end +# Tests various networks with non-permitted content. + let + tr = @transport_reaction D X + + # Variable unknowns. + rs1 = @reaction_network begin + @variables V(t) + (p,d), 0 <--> X + end + @test_throws ArgumentError LatticeReactionSystem(rs1, [tr], short_path) + + # Non-reaction equations. + rs2 = @reaction_network begin + @equations D(V) ~ X - V + (p,d), 0 <--> X + end + @test_throws ArgumentError LatticeReactionSystem(rs2, [tr], short_path) + + # Events. + rs3 = @reaction_network begin + @discrete_events [1.0] => [p ~ p + 1] + (p,d), 0 <--> X + end + @test_throws ArgumentError LatticeReactionSystem(rs3, [tr], short_path) + + # Observables (only generates a warning). + rs4 = @reaction_network begin + @observables X2 ~ 2X + (p,d), 0 <--> X + end + @test_logs (:warn, r"The `ReactionSystem` used as input to `LatticeReactionSystem contain observables. It *") match_mode=:any LatticeReactionSystem(rs4, [tr], short_path) +end + +# Tests for hierarchical input system. +let + t = default_t() + @parameters d + @species X(t) + rxs = [Reaction(d, [X], [])] + @named rs1 = ReactionSystem(rxs, t) + @named rs2 = ReactionSystem(rxs, t; systems = [rs1]) + rs2 = complete(rs2) + @test_throws ArgumentError LatticeReactionSystem(rs2, [tr], short_path) +end + +# Tests for non-complete input `ReactionSystem`. +let + tr = @transport_reaction D X + rs = @network_component begin + (p,d), 0 <--> X + end + @test_throws ArgumentError LatticeReactionSystem(rs1, [tr], short_path) +end ### Tests Grid Vertex and Edge Number Computation ### diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index 3e9544d5fa..b2c3035dbf 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -547,7 +547,7 @@ let @test all(isequal.(ss_1, ss_2)) end -### ODEProblem & Integrator Interfacing ### +### ODEProblem & Integrator Interfacing ### # Checks that basic interfacing with ODEProblem parameters (getting and setting) works. let @@ -788,4 +788,41 @@ let end end end +end + + +### Error Tests ### + +# Checks that attempting to remove conserved quantities yields an error. +let + lrs = LatticeReactionSystem(binding_system, binding_srs, very_small_2d_masked_grid) + @test_throws ArgumentError ODEProblem(lrs, binding_u0, (0.0, 10.0), binding_p; remove_conserved = true) +end + +# Checks that various erroneous inputs to `ODEProblem` yields errors. +let + # Create `LatticeReactionSystem`. + @parameters d1 d2 D [edgeparameter=true] + @species X1(t) X2(t) + rxs = [Reaction(d1, [X1], [])] + @named rs = ReactionSystem(rxs, t) + rs = complete(rs) + lrs = LatticeReactionSystem(rs, [TransportReaction(D, X1)], CartesianGrid((4,))) + + # Attempts to create `ODEProblem` using various faulty inputs. + u0 = [X1 => 1.0] + tspan = (0.0, 1.0) + ps = [d1 => 1.0, D => 0.1] + @test_throws ArgumentError ODEProblem(lrs, [1.0], tspan, ps) + @test_throws ArgumentError ODEProblem(lrs, u0, tspan, [1.0, 0.1]) + @test_throws ArgumentError ODEProblem(lrs, [X1 => 1.0, X2 => 2.0], tspan, ps) + @test_throws ArgumentError ODEProblem(lrs, u0, tspan, [d1 => 1.0, d2 => 0.2, D => 0.1]) + @test_throws ArgumentError ODEProblem(lrs, [X1 => [1.0, 2.0, 3.0]], tspan, ps) + @test_throws ArgumentError ODEProblem(lrs, u0, tspan, [d1 => [1.0, 2.0, 3.0], D => 0.1]) + @test_throws ArgumentError ODEProblem(lrs, [X1 => [1.0 2.0; 3.0 4.0]], tspan, ps) + @test_throws ArgumentError ODEProblem(lrs, u0, tspan, [d1 => [1.0 2.0; 3.0 4.0], D => 0.1]) + bad_D_vals_1 = sparse([0.0 1.0 0.0 1.0; 1.0 0.0 1.0 0.0; 0.0 1.0 0.0 1.0; 1.0 0.0 1.0 0.0]) + @test_throws ArgumentError ODEProblem(lrs, u0, tspan, [d1 => 1.0, D => bad_D_vals_1]) + bad_D_vals_2 = sparse([0.0 0.0 0.0 1.0; 1.0 0.0 1.0 0.0; 0.0 1.0 0.0 1.0; 1.0 0.0 0.0 0.0]) + @test_throws ArgumentError ODEProblem(lrs, u0, tspan, [d1 => 1.0, D => bad_D_vals_2]) end \ No newline at end of file diff --git a/test/spatial_modelling/lattice_reaction_systems_jumps.jl b/test/spatial_modelling/lattice_reaction_systems_jumps.jl index 69928e9c12..701922381a 100644 --- a/test/spatial_modelling/lattice_reaction_systems_jumps.jl +++ b/test/spatial_modelling/lattice_reaction_systems_jumps.jl @@ -203,4 +203,14 @@ let end -### JumpProblem & Integrator Interfacing ### \ No newline at end of file +### JumpProblem & Integrator Interfacing ### + + +### Other Tests ### + +# Checks that providing a non-spatial `DiscreteProblem` to a `JumpProblem` gives an error. +let + lrs = LatticeReactionSystem(binding_system, binding_srs, very_small_2d_masked_grid) + dprob = DiscreteProblem(binding_system, binding_u0, (0.0, 10.0), binding_p[1:2]) + @test_throws ArgumentError JumpProblem(lrs, dprob, NSM()) +end \ No newline at end of file diff --git a/test/spatial_modelling/lattice_reaction_systems_lattice_types.jl b/test/spatial_modelling/lattice_reaction_systems_lattice_types.jl index de77d54d5f..bde1bb14f8 100644 --- a/test/spatial_modelling/lattice_reaction_systems_lattice_types.jl +++ b/test/spatial_modelling/lattice_reaction_systems_lattice_types.jl @@ -50,7 +50,7 @@ let @test grid_dims(masked_1d_lrs) == 1 @test grid_dims(masked_2d_lrs) == 2 @test grid_dims(masked_3d_lrs) == 3 - @test_throws Exception grid_dims(graph_lrs) + @test_throws ArgumentError grid_dims(graph_lrs) # Checks grid sizes. @test grid_size(cartesian_1d_lrs) == (5,) @@ -59,7 +59,7 @@ let @test grid_size(masked_1d_lrs) == (5,) @test grid_size(masked_2d_lrs) == (5,5) @test grid_size(masked_3d_lrs) == (5,5,5) - @test_throws Exception grid_size(graph_lrs) + @test_throws ArgumentError grid_size(graph_lrs) end # Checks grid dimensions for 2d and 3d grids where some dimension is equal to 1. From a8aae9392ed195492ae39c9b3f5c09913b99eae1 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 8 Jul 2024 07:48:20 -0400 Subject: [PATCH 416/446] remove compute_edge_value function (not used after revamp). --- src/spatial_reaction_systems/utility.jl | 52 ------------------------- 1 file changed, 52 deletions(-) diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 4f4930e3bd..e242ba3935 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -263,10 +263,6 @@ function get_transport_rate(transport_rate::SparseMatrixCSC{T, Int64}, edge::Pai t_rate_idx_types::Bool) where {T} return t_rate_idx_types ? transport_rate[1,1] : transport_rate[edge[1],edge[2]] end -# Finds the transportation rate for a specific species, LatticeTransportODEf struct, and edge. -function get_transport_rate(trans_s_idx::Int64, f_func, edge::Pair{Int64,Int64}) - get_transport_rate(f_func.transport_rates[trans_s_idx][2], edge, f_func.t_rate_idx_types[trans_s_idx]) -end # For a `LatticeTransportODEFunction`, updates its stored parameters (in `mtk_ps`) so that they # the heterogeneous parameters' values correspond to the values in the specified vertex. @@ -276,42 +272,6 @@ function update_mtk_ps!(lt_ofun::LatticeTransportODEFunction, all_ps::Vector{T}, end end -# Fetches the parameter values that currently are in the work parameter vector and which -# corresponds to the parameters of the non-spatial `ReactionSystem` stored in the `ReactionSystem`. -function nonspatial_ps(lt_ode_func) - return @view lt_ode_func.work_ps[lt_ode_func.nonspatial_rs_p_idxs] -end - -# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form -# (currently only used in Spatial Jump systems). -function matrix_expand_component_values(values::Vector{<:Vector}, n::Int64) - reshape(expand_component_values(values, n), length(values), n) -end - -# For an expression, computes its values using the provided state and parameter vectors. -# The expression is assumed to be valid in edges (and can have edges parameter components). -# If some component is non-uniform, output is a vector of length equal to the number of vertexes. -# If all components are uniform, the output is a length one vector. -function compute_edge_value(exp, lrs::LatticeReactionSystem, edge_ps) - # Finds the symbols in the expression. Checks that all correspond to edge parameters. - relevant_syms = Symbolics.get_variables(exp) - if !all(any(isequal(sym, p) for p in edge_parameters(lrs)) for sym in relevant_syms) - throw(ArgumentError("An non-edge parameter was encountered in expressions: $exp. Here, only edge parameters are expected.")) - end - - # Creates a Function tha computes the expressions value for a parameter set. - exp_func = drop_expr(@RuntimeGeneratedFunction(build_function(exp, relevant_syms...))) - # Creates a dictionary with the value(s) for all edge parameters. - sym_val_dict = vals_to_dict(edge_parameters(lrs), edge_ps) - - # If all values are uniform, compute value once. Else, do it at all edges. - if !has_spatial_edge_component(exp, lrs, edge_ps) - return [exp_func([sym_val_dict[sym][1] for sym in relevant_syms]...)] - end - return [exp_func([get_component_value(sym_val_dict[sym], idxE) for sym in relevant_syms]...) - for idxE in 1:num_edges(lrs)] -end - # For an expression, computes its values using the provided state and parameter vectors. # The expression is assumed to be valid in vertexes (and can have vertex parameter and state components). # If at least one component is non-uniform, output is a vector of length equal to the number of vertexes. @@ -339,18 +299,6 @@ end ### System Property Checks ### -# For a Symbolic expression, a LatticeReactionSystem, and a parameter list of the internal format: -# Checks if any edge parameter in the expression have a spatial component (that is, is not uniform). -function has_spatial_edge_component(exp, lrs::LatticeReactionSystem, edge_ps) - # Finds the edge parameters in the expression. Computes their indexes. - exp_syms = Symbolics.get_variables(exp) - exp_edge_ps = filter(sym -> any(isequal(sym), edge_parameters(lrs)), exp_syms) - p_idxs = [findfirst(isequal(sym, edge_p) for edge_p in edge_parameters(lrs)) - for sym in exp_syms] - # Checks if any of the corresponding value vectors have length != 1 (that is, is not uniform). - return any(length(edge_ps[p_idx]) != 1 for p_idx in p_idxs) -end - # For a Symbolic expression, and a parameter set, checks if any relevant parameters have a # spatial component. Filters out any parameters that are edge parameters. function has_spatial_vertex_component(exp, ps) From aa81594f0032fd6f632dd7b42b4e9e5cc8ab501c Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 8 Jul 2024 07:54:11 -0400 Subject: [PATCH 417/446] testfix --- test/runtests.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 23cb457f4b..374e598d88 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,7 +9,6 @@ using SafeTestsets, Test ### Run Tests ### @time begin - @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_jumps.jl") end # Tests the `ReactionSystem` structure and its properties. @time @safetestset "Reaction Structure" begin include("reactionsystem_core/reaction.jl") end From 445af774c19fa46a6726d8b466f0872012e40d59 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 8 Jul 2024 08:22:09 -0400 Subject: [PATCH 418/446] split LatticeReactiontests. Move spatial tests to the end of runtests.jl --- test/runtests.jl | 17 +- .../lattice_reaction_systems.jl | 150 ++---------------- test/spatial_modelling/spatial_reactions.jl | 136 ++++++++++++++++ 3 files changed, 158 insertions(+), 145 deletions(-) create mode 100644 test/spatial_modelling/spatial_reactions.jl diff --git a/test/runtests.jl b/test/runtests.jl index 374e598d88..eec0279da7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -50,13 +50,6 @@ using SafeTestsets, Test @time @safetestset "MTK Structure Indexing" begin include("upstream/mtk_structure_indexing.jl") end @time @safetestset "MTK Problem Inputs" begin include("upstream/mtk_problem_inputs.jl") end - # Tests spatial modelling and simulations. - @time @safetestset "PDE Systems Simulations" begin include("spatial_modelling/simulate_PDEs.jl") end - @time @safetestset "Lattice Reaction Systems" begin include("spatial_modelling/lattice_reaction_systems.jl") end - @time @safetestset "Spatial Lattice Variants" begin include("spatial_modelling/lattice_reaction_systems_lattice_types.jl") end - @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_ODEs.jl") end - @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_jumps.jl") end - # Tests network visualisation. @time @safetestset "Latexify" begin include("visualisation/latexify.jl") end # Disable on Macs as can't install GraphViz via jll @@ -69,7 +62,15 @@ using SafeTestsets, Test @time @safetestset "HomotopyContinuation Extension" begin include("extensions/homotopy_continuation.jl") end @time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end - # test stability (uses HomotopyContinuation extension) + # Transportest stability computation (uses HomotopyContinuation extension). @time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end + # Tests spatial modelling and simulations. + @time @safetestset "PDE Systems Simulations" begin include("spatial_modelling/simulate_PDEs.jl") end + @time @safetestset "Spatial Reactions" begin include("spatial_modelling/spatial_reactions.jl") end + @time @safetestset "Lattice Reaction Systems" begin include("spatial_modelling/lattice_reaction_systems.jl") end + @time @safetestset "Spatial Lattice Variants" begin include("spatial_modelling/lattice_reaction_systems_lattice_types.jl") end + @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_ODEs.jl") end + @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_jumps.jl") end + end # @time diff --git a/test/spatial_modelling/lattice_reaction_systems.jl b/test/spatial_modelling/lattice_reaction_systems.jl index 4f08f4b723..d3e067edb4 100644 --- a/test/spatial_modelling/lattice_reaction_systems.jl +++ b/test/spatial_modelling/lattice_reaction_systems.jl @@ -6,29 +6,9 @@ using Catalyst, Graphs, OrdinaryDiffEq, Test # Fetch test networks. include("../spatial_test_networks.jl") -# Pre declares a grid. +# Pre-declares a set of grids. grids = [very_small_2d_cartesian_grid, very_small_2d_masked_grid, very_small_2d_graph_grid] -### Test Spatial Reactions ### - -# Test creation of TransportReaction with non-parameters in rate. -# Tests that it works even when rate is highly nested. -let - @variables t - @species X(t) Y(t) - @parameters D1 D2 D3 - @test_throws ErrorException TransportReaction(D1 + D2*(D3 + Y), X) - @test_throws ErrorException TransportReaction(Y, X) -end - -# Checks that the `hash` functions works for `TransportReaction`s. -let - tr1 = @transport_reaction D1 X - tr2 = @transport_reaction D1 X - tr3 = @transport_reaction D2 X - hash(tr1, 0x0000000000000001) == hash(tr2, 0x0000000000000001) - hash(tr2, 0x0000000000000001) != hash(tr3, 0x0000000000000001) -end ### Tests LatticeReactionSystem Getters Correctness ### @@ -179,110 +159,6 @@ let @test isequal(independent_variables(lrs), [t]) end -### Tests Spatial Reactions Getters Correctness ### - -# Test case 1. -let - tr_1 = @transport_reaction dX X - tr_2 = @transport_reaction dY1*dY2 Y - - # @test ModelingToolkit.getname.(species(tr_1)) == ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] # species(::TransportReaction) currently not supported. - # @test ModelingToolkit.getname.(species(tr_2)) == ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] - @test ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] - @test ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] - @test ModelingToolkit.getname.(parameters(tr_1)) == [:dX] - @test ModelingToolkit.getname.(parameters(tr_2)) == [:dY1, :dY2] - - # @test issetequal(species(tr_1), [tr_1.species]) - # @test issetequal(species(tr_2), [tr_2.species]) - @test issetequal(spatial_species(tr_1), [tr_1.species]) - @test issetequal(spatial_species(tr_2), [tr_2.species]) -end - -# Test case 2. -let - rs = @reaction_network begin - @species X(t) Y(t) - @parameters dX dY1 dY2 - end - @unpack X, Y, dX, dY1, dY2 = rs - tr_1 = TransportReaction(dX, X) - tr_2 = TransportReaction(dY1*dY2, Y) - # @test isequal(species(tr_1), [X]) - # @test isequal(species(tr_1), [X]) - @test issetequal(spatial_species(tr_2), [Y]) - @test issetequal(spatial_species(tr_2), [Y]) - @test issetequal(parameters(tr_1), [dX]) - @test issetequal(parameters(tr_2), [dY1, dY2]) -end - -### Tests Spatial Reactions Generation ### - -# Tests TransportReaction with non-trivial rate. -let - rs = @reaction_network begin - @parameters dV dE [edgeparameter=true] - (p,1), 0 <--> X - end - @unpack dV, dE, X = rs - - tr = TransportReaction(dV*dE, X) - @test isequal(tr.rate, dV*dE) -end - -# Tests transport_reactions function for creating TransportReactions. -let - rs = @reaction_network begin - @parameters d - (p,1), 0 <--> X - end - @unpack d, X = rs - trs = TransportReactions([(d, X), (d, X)]) - @test isequal(trs[1], trs[2]) -end - -# Test reactions with constants in rate. -let - @variables t - @species X(t) Y(t) - - tr_1 = TransportReaction(1.5, X) - tr_1_macro = @transport_reaction 1.5 X - @test isequal(tr_1.rate, tr_1_macro.rate) - @test isequal(tr_1.species, tr_1_macro.species) - - tr_2 = TransportReaction(π, Y) - tr_2_macro = @transport_reaction π Y - @test isequal(tr_2.rate, tr_2_macro.rate) - @test isequal(tr_2.species, tr_2_macro.species) -end - -### Test Interpolation ### - -# Does not currently work. The 3 tr_macro_ lines generate errors. -# Test case 1. -let - rs = @reaction_network begin - @species X(t) Y(t) Z(t) - @parameters dX dY1 dY2 dZ - end - @unpack X, Y, Z, dX, dY1, dY2, dZ = rs - rate1 = dX - rate2 = dY1*dY2 - species3 = Z - tr_1 = TransportReaction(dX, X) - tr_2 = TransportReaction(dY1*dY2, Y) - tr_3 = TransportReaction(dZ, Z) - tr_macro_1 = @transport_reaction $dX X - tr_macro_2 = @transport_reaction $(rate2) Y - @test_broken false - # tr_macro_3 = @transport_reaction dZ $species3 # Currently does not work, something with meta programming. - - @test isequal(tr_1, tr_macro_1) - @test isequal(tr_2, tr_macro_2) - # @test isequal(tr_3, tr_macro_3) -end - ### Tests Error generation ### # Network where diffusion species is not declared in non-spatial network. @@ -375,13 +251,13 @@ end # Tests for hierarchical input system. let t = default_t() - @parameters d + @parameters d D @species X(t) rxs = [Reaction(d, [X], [])] @named rs1 = ReactionSystem(rxs, t) @named rs2 = ReactionSystem(rxs, t; systems = [rs1]) rs2 = complete(rs2) - @test_throws ArgumentError LatticeReactionSystem(rs2, [tr], short_path) + @test_throws ArgumentError LatticeReactionSystem(rs2, [TransportReaction(D, X)], CartesianGrid((2,2))) end # Tests for non-complete input `ReactionSystem`. @@ -390,7 +266,7 @@ let rs = @network_component begin (p,d), 0 <--> X end - @test_throws ArgumentError LatticeReactionSystem(rs1, [tr], short_path) + @test_throws ArgumentError LatticeReactionSystem(rs, [tr], CartesianGrid((2,2))) end ### Tests Grid Vertex and Edge Number Computation ### @@ -409,14 +285,14 @@ let random_1d_masked_grid, random_2d_masked_grid, random_3d_masked_grid] lrs1 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) lrs2 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice; diagonal_connections=true) - @test lrs1.num_edges == iterator_count(edge_iterator(lrs1)) - @test lrs2.num_edges == iterator_count(edge_iterator(lrs2)) + @test num_edges(lrs1) == iterator_count(edge_iterator(lrs1)) + @test num_edges(lrs2) == iterator_count(edge_iterator(lrs2)) end # Graph grids (cannot test diagonal connections). for lattice in [small_2d_graph_grid, small_3d_graph_grid, undirected_cycle, small_directed_cycle, unconnected_graph] lrs1 = LatticeReactionSystem(SIR_system, SIR_srs_1, lattice) - @test lrs1.num_edges == iterator_count(edge_iterator(lrs1)) + @test num_edges(lrs1) == iterator_count(edge_iterator(lrs1)) end end @@ -462,33 +338,33 @@ let lrs = LatticeReactionSystem(rn, [tr], CartesianGrid(n)) ps = [:D => make_directed_edge_values(lrs, (10.0, 0.0))] oprob = ODEProblem(lrs, u0, tspan, ps) - @test isapprox(solve(oprob, Tsit5())[end][5], n, rtol=1e-6) + @test isapprox(solve(oprob, Tsit5()).u[end][5], n, rtol=1e-6) # Checks the 2d case (both with 1d and 2d flow). lrs = LatticeReactionSystem(rn, [tr], CartesianGrid((n,n))) ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (0.0, 0.0))] oprob = ODEProblem(lrs, u0, tspan, ps) - @test all(isapprox.(solve(oprob, Tsit5())[end][5:5:25], n, rtol=1e-6)) + @test all(isapprox.(solve(oprob, Tsit5()).u[end][5:5:25], n, rtol=1e-6)) ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0))] oprob = ODEProblem(lrs, u0, tspan, ps) - @test isapprox(solve(oprob, Tsit5())[end][25], n^2, rtol=1e-6) + @test isapprox(solve(oprob, Tsit5()).u[end][25], n^2, rtol=1e-6) # Checks the 3d case (both with 1d and 2d flow). lrs = LatticeReactionSystem(rn, [tr], CartesianGrid((n,n,n))) ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (0.0, 0.0), (0.0, 0.0))] oprob = ODEProblem(lrs, u0, tspan, ps) - @test all(isapprox.(solve(oprob, Tsit5())[end][5:5:125], n, rtol=1e-6)) + @test all(isapprox.(solve(oprob, Tsit5()).u[end][5:5:125], n, rtol=1e-6)) ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0), (0.0, 0.0))] oprob = ODEProblem(lrs, u0, tspan, ps) - @test all(isapprox.(solve(oprob, Tsit5())[end][25:25:125], n^2, rtol=1e-6)) + @test all(isapprox.(solve(oprob, Tsit5()).u[end][25:25:125], n^2, rtol=1e-6)) ps = [:D => make_directed_edge_values(lrs, (1.0, 0.0), (1.0, 0.0), (1.0, 0.0))] oprob = ODEProblem(lrs, u0, tspan, ps) - @test isapprox(solve(oprob, Tsit5())[end][125], n^3, rtol=1e-6) + @test isapprox(solve(oprob, Tsit5()).u[end][125], n^3, rtol=1e-6) end # Checks that erroneous input yields errors. diff --git a/test/spatial_modelling/spatial_reactions.jl b/test/spatial_modelling/spatial_reactions.jl new file mode 100644 index 0000000000..32895135da --- /dev/null +++ b/test/spatial_modelling/spatial_reactions.jl @@ -0,0 +1,136 @@ +### Preparations ### + +# Fetch packages. +using Catalyst, Graphs, OrdinaryDiffEq, Test + +# Fetch test networks. +include("../spatial_test_networks.jl") + +# Pre-declares a set of grids. +grids = [very_small_2d_cartesian_grid, very_small_2d_masked_grid, very_small_2d_graph_grid] + + +### TransportReaction Creation Tests ### + +# Tests TransportReaction with non-trivial rate. +let + rs = @reaction_network begin + @parameters dV dE [edgeparameter=true] + (p,1), 0 <--> X + end + @unpack dV, dE, X = rs + + tr = TransportReaction(dV*dE, X) + @test isequal(tr.rate, dV*dE) +end + +# Tests transport_reactions function for creating TransportReactions. +let + rs = @reaction_network begin + @parameters d + (p,1), 0 <--> X + end + @unpack d, X = rs + trs = TransportReactions([(d, X), (d, X)]) + @test isequal(trs[1], trs[2]) +end + +# Test reactions with constants in rate. +let + @variables t + @species X(t) Y(t) + + tr_1 = TransportReaction(1.5, X) + tr_1_macro = @transport_reaction 1.5 X + @test isequal(tr_1.rate, tr_1_macro.rate) + @test isequal(tr_1.species, tr_1_macro.species) + + tr_2 = TransportReaction(π, Y) + tr_2_macro = @transport_reaction π Y + @test isequal(tr_2.rate, tr_2_macro.rate) + @test isequal(tr_2.species, tr_2_macro.species) +end + +### Spatial Reactions Getters Correctness ### + +# Test case 1. +let + tr_1 = @transport_reaction dX X + tr_2 = @transport_reaction dY1*dY2 Y + + # @test ModelingToolkit.getname.(species(tr_1)) == ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] # species(::TransportReaction) currently not supported. + # @test ModelingToolkit.getname.(species(tr_2)) == ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] + @test ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] + @test ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] + @test ModelingToolkit.getname.(parameters(tr_1)) == [:dX] + @test ModelingToolkit.getname.(parameters(tr_2)) == [:dY1, :dY2] + + # @test issetequal(species(tr_1), [tr_1.species]) + # @test issetequal(species(tr_2), [tr_2.species]) + @test issetequal(spatial_species(tr_1), [tr_1.species]) + @test issetequal(spatial_species(tr_2), [tr_2.species]) +end + +# Test case 2. +let + rs = @reaction_network begin + @species X(t) Y(t) + @parameters dX dY1 dY2 + end + @unpack X, Y, dX, dY1, dY2 = rs + tr_1 = TransportReaction(dX, X) + tr_2 = TransportReaction(dY1*dY2, Y) + # @test isequal(species(tr_1), [X]) + # @test isequal(species(tr_1), [X]) + @test issetequal(spatial_species(tr_2), [Y]) + @test issetequal(spatial_species(tr_2), [Y]) + @test issetequal(parameters(tr_1), [dX]) + @test issetequal(parameters(tr_2), [dY1, dY2]) +end + +### Error Tests ### + +# Tests that creation of TransportReaction with non-parameters in rate yield errors. +# Tests that errors are throw even when the rate is highly nested. +let + @variables t + @species X(t) Y(t) + @parameters D1 D2 D3 + @test_throws ErrorException TransportReaction(D1 + D2*(D3 + Y), X) + @test_throws ErrorException TransportReaction(Y, X) +end + +### Other Tests ### + +# Test Interpolation +# Does not currently work. The 3 tr_macro_ lines generate errors. +let + rs = @reaction_network begin + @species X(t) Y(t) Z(t) + @parameters dX dY1 dY2 dZ + end + @unpack X, Y, Z, dX, dY1, dY2, dZ = rs + rate1 = dX + rate2 = dY1*dY2 + species3 = Z + tr_1 = TransportReaction(dX, X) + tr_2 = TransportReaction(dY1*dY2, Y) + tr_3 = TransportReaction(dZ, Z) + tr_macro_1 = @transport_reaction $dX X + tr_macro_2 = @transport_reaction $(rate2) Y + @test_broken false + # tr_macro_3 = @transport_reaction dZ $species3 # Currently does not work, something with meta programming. + + @test isequal(tr_1, tr_macro_1) + @test isequal(tr_2, tr_macro_2) + # @test isequal(tr_3, tr_macro_3) +end + +# Checks that the `hash` functions works for `TransportReaction`s. +let + tr1 = @transport_reaction D1 X + tr2 = @transport_reaction D1 X + tr3 = @transport_reaction D2 X + hash(tr1, 0x0000000000000001) == hash(tr2, 0x0000000000000001) + hash(tr2, 0x0000000000000001) != hash(tr3, 0x0000000000000001) +end \ No newline at end of file From 0a2192d8c527c64131d9bb3ba39ca753c28dcd53 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 8 Jul 2024 09:44:19 -0400 Subject: [PATCH 419/446] reduce test tuntimes --- ...attice_reaction_systems_ODE_performance.jl | 52 ++-- .../lattice_reaction_systems.jl | 2 +- .../lattice_reaction_systems_ODEs.jl | 229 +++++++----------- .../lattice_reaction_systems_jumps.jl | 16 +- test/spatial_modelling/spatial_reactions.jl | 8 +- test/spatial_test_networks.jl | 6 + 6 files changed, 133 insertions(+), 180 deletions(-) diff --git a/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl b/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl index 972ea1529c..804dc4bc05 100644 --- a/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl +++ b/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl @@ -20,7 +20,7 @@ include("../spatial_test_networks.jl") # Small grid, small, non-stiff, system. let lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_graph_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lattice(lrs)), :R => 0.0] + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs), :R => 0.0] pV = SIR_p pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] oprob = ODEProblem(lrs, u0, (0.0, 500.0), [pV; pE]; jac = false) @@ -35,7 +35,7 @@ end # Large grid, small, non-stiff, system. let lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lattice(lrs)), :R => 0.0] + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs), :R => 0.0] pV = SIR_p pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] oprob = ODEProblem(lrs, u0, (0.0, 500.0), [pV; pE]; jac = false) @@ -50,7 +50,7 @@ end # Small grid, small, stiff, system. let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_graph_grid) - u0 = [:X => rand_v_vals(lattice(lrs), 10), :Y => rand_v_vals(lattice(lrs), 10)] + u0 = [:X => rand_v_vals(lrs, 10), :Y => rand_v_vals(lrs, 10)] pV = brusselator_p pE = [:dX => 0.2] oprob = ODEProblem(lrs, u0, (0.0, 100.0), [pV; pE]) @@ -65,7 +65,7 @@ end # Large grid, small, stiff, system. let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) - u0 = [:X => rand_v_vals(lattice(lrs), 10), :Y => rand_v_vals(lattice(lrs), 10)] + u0 = [:X => rand_v_vals(lrs, 10), :Y => rand_v_vals(lrs, 10)] pV = brusselator_p pE = [:dX => 0.2] oprob = ODEProblem(lrs, u0, (0.0, 100.0), [pV; pE]) @@ -82,10 +82,10 @@ let lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, small_2d_graph_grid) u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lattice(lrs), 0.005), - :Ligand => 0.005 .+ rand_v_vals(lattice(lrs), 0.005), + :CuoAc => 0.005 .+ rand_v_vals(lrs, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs, 0.005), :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :Silane => 0.5 .+ rand_v_vals(lrs, 0.5), :CuHLigand => 0.0, :SilaneOAc => 0.0, :Styrene => 0.16, @@ -113,10 +113,10 @@ let lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, large_2d_grid) u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lattice(lrs), 0.005), - :Ligand => 0.005 .+ rand_v_vals(lattice(lrs), 0.005), + :CuoAc => 0.005 .+ rand_v_vals(lrs, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs, 0.005), :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :Silane => 0.5 .+ rand_v_vals(lrs, 0.5), :CuHLigand => 0.0, :SilaneOAc => 0.0, :Styrene => 0.16, @@ -143,14 +143,14 @@ end let lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_graph_grid) u0 = [ - :w => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :w2 => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :w2v => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :v => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :vP => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :σB => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :w2σB => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :w => 0.5 .+ rand_v_vals(lrs, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs, 0.5), + :v => 0.5 .+ rand_v_vals(lrs, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs, 0.5), :vPp => 0.0, :phos => 0.4, ] @@ -169,14 +169,14 @@ end let lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) u0 = [ - :w => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :w2 => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :w2v => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :v => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :vP => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :σB => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :w2σB => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), + :w => 0.5 .+ rand_v_vals(lrs, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs, 0.5), + :v => 0.5 .+ rand_v_vals(lrs, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs, 0.5), :vPp => 0.0, :phos => 0.4, ] diff --git a/test/spatial_modelling/lattice_reaction_systems.jl b/test/spatial_modelling/lattice_reaction_systems.jl index d3e067edb4..52b0ac916b 100644 --- a/test/spatial_modelling/lattice_reaction_systems.jl +++ b/test/spatial_modelling/lattice_reaction_systems.jl @@ -331,7 +331,7 @@ let end n = 5 tr = @transport_reaction D X - tspan = (0.0, 10000.0) + tspan = (0.0, 1000.0) u0 = [:X => 1.0] # Checks the 1d case. diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index b2c3035dbf..92d905e169 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -15,68 +15,33 @@ rng = StableRNG(12345) t = default_t() ### Tests Simulations Don't Error ### -for grid in [small_2d_graph_grid, short_path, small_directed_cycle, - small_1d_cartesian_grid, small_2d_cartesian_grid, small_3d_cartesian_grid, - small_1d_masked_grid, small_2d_masked_grid, small_3d_masked_grid, - random_2d_masked_grid] - # Non-stiff case - for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] - lrs = LatticeReactionSystem(SIR_system, srs, grid) - u0_1 = [:S => 999.0, :I => 1.0, :R => 0.0] - u0_2 = [:S => 500.0 .+ 500.0 * rand_v_vals(lattice(lrs)), :I => 1.0, :R => 0.0] - u0_3 = [ - :S => 500.0 .+ 500.0 * rand_v_vals(lattice(lrs)), - :I => 50 * rand_v_vals(lattice(lrs)), - :R => 50 * rand_v_vals(lattice(lrs)), - ] - for u0 in [u0_1, u0_2, u0_3] - pV_1 = [:α => 0.1 / 1000, :β => 0.01] - pV_2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lattice(lrs))] - pV_3 = [ - :α => 0.1 / 2000 * rand_v_vals(lattice(lrs)), - :β => 0.02 * rand_v_vals(lattice(lrs)), - ] - for pV in [pV_1, pV_2, pV_3] - pE_1 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) - pE_2 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) - pE_3 = map(sp -> sp => rand_e_vals(lrs, 0.01), spatial_param_syms(lrs)) - for pE in [pE_1, pE_2, pE_3] - isempty(spatial_param_syms(lrs)) && (pE = Vector{Pair{Symbol, Float64}}()) - oprob = ODEProblem(lrs, u0, (0.0, 500.0), [pV; pE]) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - oprob = ODEProblem(lrs, u0, (0.0, 10.0), [pV; pE]; jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - end - end - end - end - - # Stiff case - for srs in [Vector{TransportReaction}(), brusselator_srs_1, brusselator_srs_2] - lrs = LatticeReactionSystem(brusselator_system, srs, grid) - u0_1 = [:X => 1.0, :Y => 20.0] - u0_2 = [:X => rand_v_vals(lattice(lrs), 10.0), :Y => 2.0] - u0_3 = [:X => rand_v_vals(lattice(lrs), 20), :Y => rand_v_vals(lattice(lrs), 10)] - for u0 in [u0_1, u0_2, u0_3] - p1 = [:A => 1.0, :B => 4.0] - p2 = [:A => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), :B => 4.0] - p3 = [ - :A => 0.5 .+ rand_v_vals(lattice(lrs), 0.5), - :B => 4.0 .+ rand_v_vals(lattice(lrs), 1.0), +let + for grid in [small_1d_cartesian_grid, small_1d_masked_grid, small_1d_graph_grid] + for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] + lrs = LatticeReactionSystem(SIR_system, srs, grid) + u0_1 = [:S => 999.0, :I => 1.0, :R => 0.0] + u0_2 = [:S => 500.0 .+ 500.0 * rand_v_vals(lrs), :I => 1.0, :R => 0.0] + u0_3 = [ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs), + :I => 50 * rand_v_vals(lrs), + :R => 50 * rand_v_vals(lrs), ] - for pV in [p1, p2, p3] - pE_1 = map(sp -> sp => 0.2, spatial_param_syms(lrs)) - pE_2 = map(sp -> sp => rand(rng), spatial_param_syms(lrs)) - pE_3 = map(sp -> sp => rand_e_vals(lrs, 0.2), - spatial_param_syms(lrs)) - for pE in [pE_1, pE_2, pE_3] - isempty(spatial_param_syms(lrs)) && (pE = Vector{Pair{Symbol, Float64}}()) - oprob = ODEProblem(lrs, u0, (0.0, 10.0), [pV; pE]) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - oprob = ODEProblem(lrs, u0, (0.0, 10.0), [pV; pE]; sparse = false) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + for u0 in [u0_1, u0_2, u0_3] + pV_1 = [:α => 0.1 / 1000, :β => 0.01] + pV_2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs)] + pV_3 = [ + :α => 0.1 / 2000 * rand_v_vals(lrs), + :β => 0.02 * rand_v_vals(lrs), + ] + for pV in [pV_1, pV_2, pV_3] + pE_1 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) + pE_2 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) + pE_3 = map(sp -> sp => rand_e_vals(lrs, 0.01), spatial_param_syms(lrs)) + for pE in [pE_1, pE_2, pE_3] + isempty(spatial_param_syms(lrs)) && (pE = Vector{Pair{Symbol, Float64}}()) + oprob = ODEProblem(lrs, u0, (0.0, 500.0), [pV; pE]; jac = false, sparse = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + end end end end @@ -192,8 +157,8 @@ end let lrs = LatticeReactionSystem(binding_system, binding_srs, undirected_cycle) u0 = [ - :X => 1.0 .+ rand_v_vals(lattice(lrs)), - :Y => 2.0 * rand_v_vals(lattice(lrs)), + :X => 1.0 .+ rand_v_vals(lrs), + :Y => 2.0 * rand_v_vals(lrs), :XY => 0.5 ] oprob = ODEProblem(lrs, u0, (0.0, 1000.0), binding_p; tstops = 0.1:0.1:1000.0) @@ -207,24 +172,18 @@ end # Checks that various combinations of jac and sparse gives the same result. let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_graph_grid) - u0 = [:X => rand_v_vals(lattice(lrs), 10), :Y => rand_v_vals(lattice(lrs), 10)] + u0 = [:X => rand_v_vals(lrs, 10), :Y => rand_v_vals(lrs, 10)] pV = brusselator_p pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 50.0), [pV; pE]; jac = false, sparse = false) - oprob_sparse = ODEProblem(lrs, u0, (0.0, 50.0), [pV; pE]; jac = false, sparse = true) - oprob_jac = ODEProblem(lrs, u0, (0.0, 50.0), [pV; pE]; jac = true, sparse = false) - oprob_sparse_jac = ODEProblem(lrs, u0, (0.0, 50.0), [pV; pE]; jac = true, sparse = true) + oprob = ODEProblem(lrs, u0, (0.0, 5.0), [pV; pE]; jac = false, sparse = false) + oprob_sparse = ODEProblem(lrs, u0, (0.0, 5.0), [pV; pE]; jac = false, sparse = true) + oprob_jac = ODEProblem(lrs, u0, (0.0, 5.0), [pV; pE]; jac = true, sparse = false) + oprob_sparse_jac = ODEProblem(lrs, u0, (0.0, 5.0), [pV; pE]; jac = true, sparse = true) ss = solve(oprob, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end] - @test all(isapprox.(ss, - solve(oprob_sparse, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; - rtol = 0.0001)) - @test all(isapprox.(ss, - solve(oprob_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; - rtol = 0.0001)) - @test all(isapprox.(ss, - solve(oprob_sparse_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; - rtol = 0.0001)) + @test all(isapprox.(ss, solve(oprob_sparse, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; rtol = 0.0001)) + @test all(isapprox.(ss, solve(oprob_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; rtol = 0.0001)) + @test all(isapprox.(ss, solve(oprob_sparse_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; rtol = 0.0001)) end # Compares Catalyst-generated to hand written one for the brusselator for a line of cells. @@ -300,7 +259,7 @@ let return sparse(jac_prototype_pre) end - num_verts = 5000 + num_verts = 100 u0 = 2 * rand(rng, 2*num_verts) p = [1.0, 4.0, 0.1] tspan = (0.0, 100.0) @@ -353,34 +312,34 @@ let sigmaB_p_spat = [:DσB => 0.05, :Dw => 0.04, :Dv => 0.03] # 1d lattices. - lrs1_cartesian = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_1d_cartesian_grid) - lrs1_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_1d_masked_grid) - lrs1_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_1d_graph_grid) + lrs1_cartesian = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, very_small_1d_cartesian_grid) + lrs1_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, very_small_1d_masked_grid) + lrs1_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, very_small_1d_graph_grid) - oprob1_cartesian = ODEProblem(lrs1_cartesian, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) - oprob1_masked = ODEProblem(lrs1_masked, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) - oprob1_graph = ODEProblem(lrs1_graph, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) - @test solve(oprob1_cartesian, QNDF()) == solve(oprob1_masked, QNDF()) == solve(oprob1_graph, QNDF()) + oprob1_cartesian = ODEProblem(lrs1_cartesian, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) + oprob1_masked = ODEProblem(lrs1_masked, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) + oprob1_graph = ODEProblem(lrs1_graph, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) + @test solve(oprob1_cartesian, QNDF()) ≈ solve(oprob1_masked, QNDF()) ≈ solve(oprob1_graph, QNDF()) # 2d lattices. - lrs2_cartesian = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_cartesian_grid) - lrs2_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_masked_grid) - lrs2_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_graph_grid) + lrs2_cartesian = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, very_small_2d_cartesian_grid) + lrs2_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, very_small_2d_masked_grid) + lrs2_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, very_small_2d_graph_grid) - oprob2_cartesian = ODEProblem(lrs2_cartesian, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) - oprob2_masked = ODEProblem(lrs2_masked, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) - oprob2_graph = ODEProblem(lrs2_graph, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) - @test solve(oprob2_cartesian, QNDF()) == solve(oprob2_masked, QNDF()) == solve(oprob2_graph, QNDF()) + oprob2_cartesian = ODEProblem(lrs2_cartesian, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) + oprob2_masked = ODEProblem(lrs2_masked, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) + oprob2_graph = ODEProblem(lrs2_graph, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) + @test solve(oprob2_cartesian, QNDF()) ≈ solve(oprob2_masked, QNDF()) ≈ solve(oprob2_graph, QNDF()) # 3d lattices. - lrs3_cartesian = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_3d_cartesian_grid) - lrs3_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_3d_masked_grid) - lrs3_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_3d_graph_grid) - - oprob3_cartesian = ODEProblem(lrs3_cartesian, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) - oprob3_masked = ODEProblem(lrs3_masked, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) - oprob3_graph = ODEProblem(lrs3_graph, sigmaB_u0, (0.0,10.0), [sigmaB_p; sigmaB_p_spat]) - @test solve(oprob3_cartesian, QNDF()) == solve(oprob3_masked, QNDF()) == solve(oprob3_graph, QNDF()) + lrs3_cartesian = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, very_small_3d_cartesian_grid) + lrs3_masked = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, very_small_3d_masked_grid) + lrs3_graph = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, very_small_3d_graph_grid) + + oprob3_cartesian = ODEProblem(lrs3_cartesian, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) + oprob3_masked = ODEProblem(lrs3_masked, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) + oprob3_graph = ODEProblem(lrs3_graph, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) + @test solve(oprob3_cartesian, QNDF()) ≈ solve(oprob3_masked, QNDF()) ≈ solve(oprob3_graph, QNDF()) end # Tests that input parameter and u0 values can be given using different types of input for 2d lattices. @@ -457,7 +416,7 @@ let lrs_1 = LatticeReactionSystem(SIR_system, [tr_1, tr_2], small_2d_graph_grid) lrs_2 = LatticeReactionSystem(SIR_system, [tr_macros_1, tr_macros_2], small_2d_graph_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1), :R => 0.0] pV = [:α => 0.1 / 1000, :β => 0.01] pE = [:dS => 0.01, :dI => 0.01] ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), [pV; pE]), Tsit5()).u[end] @@ -474,7 +433,7 @@ let lrs_1 = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_graph_grid) lrs_2 = LatticeReactionSystem(SIR_system, SIR_srs_2_alt, small_2d_graph_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1), :R => 0.0] pV = [:α => 0.1 / 1000, :β => 0.01] pE_1 = [:dS => 0.01, :dI => 0.01, :dR => 0.01] pE_2 = [:dS1 => 0.003, :dS2 => 0.007, :dI1 => 2, :dI2 => 0.005, :dR1 => 1.010050167084168, :dR2 => 1.0755285551056204e-16] @@ -613,6 +572,7 @@ end # Checks that the `rebuild_lat_internals!` function is correctly applied to an integrator. # Does through by applying it within a callback, and compare to simulations without callback. +# To keep test faster, only checks for `jac = sparse = true`. let # Prepares problem inputs. lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_2, very_small_2d_cartesian_grid) @@ -638,31 +598,28 @@ let ps_1 = [:A => A1, :B => B1, :dX => dX1, :dY => dY1] ps_2 = [:A => A2, :B => B2, :dX => dX2, :dY => dY2] - # Checks for all combinations of Jacobian and sparsity. - for jac in [false, true], sparse in [false, true] - # Creates simulation through two different separate simulations. - oprob_1_1 = ODEProblem(lrs, u0, (0.0, 5.0), ps_1; jac, sparse) - sol_1_1 = solve(oprob_1_1, Rosenbrock23(); saveat = 1.0, abstol = 1e-8, reltol = 1e-8) - u0_1_2 = [:X => sol_1_1.u[end][1:2:end], :Y => sol_1_1.u[end][2:2:end]] - oprob_1_2 = ODEProblem(lrs, u0_1_2, (0.0, 5.0), ps_2; jac, sparse) - sol_1_2 = solve(oprob_1_2, Rosenbrock23(); saveat = 1.0, abstol = 1e-8, reltol = 1e-8) - - # Creates simulation through a single simulation with a callback - oprob_2 = ODEProblem(lrs, u0, (0.0, 10.0), ps_1; jac, sparse) - condition(u, t, integrator) = (t == 5.0) - function affect!(integrator) - integrator.ps[:A] = A2 - integrator.ps[:B] = [B2] - integrator.ps[:dX] = dX2 - integrator.ps[:dY] = [dY2] - rebuild_lat_internals!(integrator) - end - callback = DiscreteCallback(condition, affect!) - sol_2 = solve(oprob_2, Rosenbrock23(); saveat = 1.0, tstops = [5.0], callback, abstol = 1e-8, reltol = 1e-8) - - # Check that trajectories are equivalent. - @test [sol_1_1.u; sol_1_2.u] ≈ sol_2.u + # Creates simulation through two different separate simulations. + oprob_1_1 = ODEProblem(lrs, u0, (0.0, 5.0), ps_1; jac = true, sparse = true) + sol_1_1 = solve(oprob_1_1, Rosenbrock23(); saveat = 1.0, abstol = 1e-8, reltol = 1e-8) + u0_1_2 = [:X => sol_1_1.u[end][1:2:end], :Y => sol_1_1.u[end][2:2:end]] + oprob_1_2 = ODEProblem(lrs, u0_1_2, (0.0, 5.0), ps_2; jac = true, sparse = true) + sol_1_2 = solve(oprob_1_2, Rosenbrock23(); saveat = 1.0, abstol = 1e-8, reltol = 1e-8) + + # Creates simulation through a single simulation with a callback + oprob_2 = ODEProblem(lrs, u0, (0.0, 10.0), ps_1; jac = true, sparse = true) + condition(u, t, integrator) = (t == 5.0) + function affect!(integrator) + integrator.ps[:A] = A2 + integrator.ps[:B] = [B2] + integrator.ps[:dX] = dX2 + integrator.ps[:dY] = [dY2] + rebuild_lat_internals!(integrator) end + callback = DiscreteCallback(condition, affect!) + sol_2 = solve(oprob_2, Rosenbrock23(); saveat = 1.0, tstops = [5.0], callback, abstol = 1e-8, reltol = 1e-8) + + # Check that trajectories are equivalent. + @test [sol_1_1.u; sol_1_2.u] ≈ sol_2.u end ### Tests Special Cases ### @@ -772,20 +729,16 @@ let # Creates a base solution to compare all solution to. lrs_base = LatticeReactionSystem(brusselator_system, brusselator_srs_2, very_small_2d_graph_grid) - oprob_base = ODEProblem(lrs_base, u0s[1], (0.0, 20.0), ps[1]) - sol_base = solve(oprob_base, QNDF(); abstol=1e-8, reltol=1e-8, saveat=0.1) + oprob_base = ODEProblem(lrs_base, u0s[1], (0.0, 1.0), ps[1]) + sol_base = solve(oprob_base, QNDF(); saveat = 0.01) # Checks all combinations of input types. - for grid in [very_small_2d_cartesian_grid, very_small_2d_masked_grid, very_small_2d_graph_grid] - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_2, grid) - for u0_base in u0s, p_base in ps - for u0 in [u0_base, Tuple(u0_base), Dict(u0_base)], p in [p_base, Tuple(p_base), Dict(p_base)] - for sparse in [false, true], jac in [false, true] - oprob = ODEProblem(lrs, u0, (0.0, 20.0), p; sparse, jac) - sol = solve(oprob, QNDF(); abstol=1e-8, reltol=1e-8, saveat=0.1) - @test sol == sol_base - end - end + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_2, very_small_2d_cartesian_grid) + for u0_base in u0s, p_base in ps + for u0 in [u0_base, Tuple(u0_base), Dict(u0_base)], p in [p_base, Dict(p_base)] + oprob = ODEProblem(lrs, u0, (0.0, 1.0), p; sparse = true, jac = true) + sol = solve(oprob, QNDF(); saveat = 0.01) + @test sol.u ≈ sol_base.u atol = 1e-6 rtol = 1e-6 end end end diff --git a/test/spatial_modelling/lattice_reaction_systems_jumps.jl b/test/spatial_modelling/lattice_reaction_systems_jumps.jl index 701922381a..904bad1173 100644 --- a/test/spatial_modelling/lattice_reaction_systems_jumps.jl +++ b/test/spatial_modelling/lattice_reaction_systems_jumps.jl @@ -16,18 +16,18 @@ let for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) u0_1 = [:S => 999, :I => 1, :R => 0] - u0_2 = [:S => round.(Int64, 500 .+ 500 * rand_v_vals(lattice(lrs))), :I => 1, :R => 0] + u0_2 = [:S => round.(Int64, 500 .+ 500 * rand_v_vals(lrs)), :I => 1, :R => 0] u0_3 = [ - :S => round.(Int64, 500 .+ 500 * rand_v_vals(lattice(lrs))), - :I => round.(Int64, 50 * rand_v_vals(lattice(lrs))), - :R => round.(Int64, 50 * rand_v_vals(lattice(lrs))), + :S => round.(Int64, 500 .+ 500 * rand_v_vals(lrs)), + :I => round.(Int64, 50 * rand_v_vals(lrs)), + :R => round.(Int64, 50 * rand_v_vals(lrs)), ] for u0 in [u0_1, u0_2, u0_3] pV_1 = [:α => 0.1 / 1000, :β => 0.01] - pV_2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lattice(lrs))] + pV_2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs)] pV_3 = [ - :α => 0.1 / 2000 * rand_v_vals(lattice(lrs)), - :β => 0.02 * rand_v_vals(lattice(lrs)), + :α => 0.1 / 2000 * rand_v_vals(lrs), + :β => 0.02 * rand_v_vals(lrs), ] for pV in [pV_1, pV_2, pV_3] pE_1 = [sp => 0.01 for sp in spatial_param_syms(lrs)] @@ -103,7 +103,7 @@ let # Create JumpProblem u0 = [:X => 1, :Y => rand(1:10, num_verts(lrs))] tspan = (0.0, 100.0) - ps = [:A => 1.0, :B => 5.0 .+ rand_v_vals(lattice(lrs)), :dX => rand_e_vals(lrs)] + ps = [:A => 1.0, :B => 5.0 .+ rand_v_vals(lrs), :dX => rand_e_vals(lrs)] dprob = DiscreteProblem(lrs, u0, tspan, ps) jprob = JumpProblem(lrs, dprob, NSM()) diff --git a/test/spatial_modelling/spatial_reactions.jl b/test/spatial_modelling/spatial_reactions.jl index 32895135da..395c264444 100644 --- a/test/spatial_modelling/spatial_reactions.jl +++ b/test/spatial_modelling/spatial_reactions.jl @@ -1,13 +1,7 @@ ### Preparations ### # Fetch packages. -using Catalyst, Graphs, OrdinaryDiffEq, Test - -# Fetch test networks. -include("../spatial_test_networks.jl") - -# Pre-declares a set of grids. -grids = [very_small_2d_cartesian_grid, very_small_2d_masked_grid, very_small_2d_graph_grid] +using Catalyst, Test ### TransportReaction Creation Tests ### diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index 924fecb045..e9322290c1 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -191,7 +191,9 @@ sigmaB_srs_2 = [sigmaB_tr_σB, sigmaB_tr_w, sigmaB_tr_v] ### Declares Lattices ### # Cartesian grids. +very_small_1d_cartesian_grid = CartesianGrid(2) very_small_2d_cartesian_grid = CartesianGrid((2,2)) +very_small_3d_cartesian_grid = CartesianGrid((2,2,2)) small_1d_cartesian_grid = CartesianGrid(5) small_2d_cartesian_grid = CartesianGrid((5,5)) @@ -202,7 +204,9 @@ large_2d_cartesian_grid = CartesianGrid((100,100)) large_3d_cartesian_grid = CartesianGrid((100,100,100)) # Masked grids. +very_small_1d_masked_grid = fill(true, 2) very_small_2d_masked_grid = fill(true, 2, 2) +very_small_3d_masked_grid = fill(true, 2, 2, 2) small_1d_masked_grid = fill(true, 5) small_2d_masked_grid = fill(true, 5, 5) @@ -217,7 +221,9 @@ random_2d_masked_grid = rand(rng, [true, true, true, false], 10, 10) random_3d_masked_grid = rand(rng, [true, true, true, false], 10, 10, 10) # Graph - grids. +very_small_1d_graph_grid = Graphs.grid([2]) very_small_2d_graph_grid = Graphs.grid([2, 2]) +very_small_3d_graph_grid = Graphs.grid([2, 2, 2]) small_1d_graph_grid = path_graph(5) small_2d_graph_grid = Graphs.grid([5,5]) From 28743ab323587ffef4dd1328bf432f45852d16e7 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 8 Jul 2024 09:45:01 -0400 Subject: [PATCH 420/446] writing fix --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index eec0279da7..6479ae2234 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -62,7 +62,7 @@ using SafeTestsets, Test @time @safetestset "HomotopyContinuation Extension" begin include("extensions/homotopy_continuation.jl") end @time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end - # Transportest stability computation (uses HomotopyContinuation extension). + # Tests stability computation (uses HomotopyContinuation extension). @time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end # Tests spatial modelling and simulations. From a98f4be70f6c7c0da3a914d8e913e9e8e914f8ae Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 8 Jul 2024 21:39:12 -0400 Subject: [PATCH 421/446] format --- src/Catalyst.jl | 3 +- .../lattice_jump_systems.jl | 44 +++--- .../lattice_reaction_systems.jl | 130 +++++++++++------- .../spatial_ODE_systems.jl | 122 ++++++++-------- .../spatial_reactions.jl | 11 +- src/spatial_reaction_systems/utility.jl | 86 ++++++------ .../lattice_reaction_systems_jumps.jl | 1 - 7 files changed, 223 insertions(+), 174 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 8b8895cdea..7c431a90c5 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -176,7 +176,8 @@ include("spatial_reaction_systems/lattice_reaction_systems.jl") export LatticeReactionSystem export spatial_species, vertex_parameters, edge_parameters export CartesianGrid, CartesianGridReJ # (Implemented in JumpProcesses) -export has_cartesian_lattice, has_masked_lattice, has_grid_lattice, has_graph_lattice, grid_dims, grid_size +export has_cartesian_lattice, has_masked_lattice, has_grid_lattice, has_graph_lattice, + grid_dims, grid_size export make_edge_p_values, make_directed_edge_values # Specific spatial problem types. diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 5dd8084bf7..97cd86b1ad 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -9,25 +9,27 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, # Converts potential symmaps to varmaps. u0_in = symmap_to_varmap(lrs, u0_in) - p_in = symmap_to_varmap(lrs, p_in) + p_in = symmap_to_varmap(lrs, p_in) # Converts u0 and p to their internal forms. # u0 is simply a vector with all the species' initial condition values across all vertices. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. - u0 = lattice_process_u0(u0_in, species(lrs), lrs) + u0 = lattice_process_u0(u0_in, species(lrs), lrs) # vert_ps and `edge_ps` are vector maps, taking each parameter's Symbolics representation to its value(s). # vert_ps values are vectors. Here, index (i) is a parameter's value in vertex i. # edge_ps values are sparse matrices. Here, index (i,j) is a parameter's value in the edge from vertex i to vertex j. # Uniform vertex/edge parameters store only a single value (a length 1 vector, or size 1x1 sparse matrix). - vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), + lrs) # Returns a DiscreteProblem (which basically just stores the processed input). return DiscreteProblem(u0, tspan, [vert_ps; edge_ps], args...; kwargs...) end # Builds a spatial JumpProblem from a DiscreteProblem containing a `LatticeReactionSystem`. -function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(reactionsystem(lrs)), - combinatoric_ratelaws = get_combinatoric_ratelaws(reactionsystem(lrs)), kwargs...) +function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator,args...; + combinatoric_ratelaws = get_combinatoric_ratelaws(reactionsystem(lrs)), + name = nameof(reactionsystem(lrs)), kwargs...) # Error checks. if !isnothing(dprob.f.sys) throw(ArgumentError("Unexpected `DiscreteProblem` passed into `JumpProblem`. Was a `LatticeReactionSystem` used as input to the initial `DiscreteProblem`?")) @@ -40,12 +42,13 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator # The non-spatial DiscreteProblem have a u0 matrix with entries for all combinations of species and vertexes. hopping_constants = make_hopping_constants(dprob, lrs) sma_jumps = make_spatial_majumps(dprob, lrs) - non_spat_dprob = DiscreteProblem(reshape(dprob.u0, num_species(lrs), num_verts(lrs)), dprob.tspan, first.(dprob.p[1])) + non_spat_dprob = DiscreteProblem(reshape(dprob.u0, num_species(lrs), num_verts(lrs)), + dprob.tspan, first.(dprob.p[1])) # Creates and returns a spatial JumpProblem (masked lattices are not supported by these). spatial_system = has_masked_lattice(lrs) ? get_lattice_graph(lrs) : lattice(lrs) - return JumpProblem(non_spat_dprob, aggregator, sma_jumps; - hopping_constants, spatial_system , name, kwargs...) + return JumpProblem(non_spat_dprob, aggregator, sma_jumps; + hopping_constants, spatial_system, name, kwargs...) end # Creates the hopping constants from a discrete problem and a lattice reaction system. @@ -59,7 +62,7 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst # Creates an array (of the same size as the hopping constant array) containing all edges. # First the array is a NxM matrix (number of species x number of vertices). Each element is a # vector containing all edges leading out from that vertex (sorted by destination index). - edge_array = [Pair{Int64,Int64}[] for _1 in 1:num_species(lrs), _2 in 1:num_verts(lrs)] + edge_array = [Pair{Int64, Int64}[] for _1 in 1:num_species(lrs), _2 in 1:num_verts(lrs)] for e in edge_iterator(lrs), s_idx in 1:num_species(lrs) push!(edge_array[s_idx, e[1]], e) end @@ -67,10 +70,9 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst # Creates the hopping constants array. It has the same shape as the edge array, but each # element is that species transportation rate along that edge - hopping_constants = [ - [Catalyst.get_edge_value(all_diff_rates[s_idx], e) for e in edge_array[s_idx, src_idx]] - for s_idx in 1:num_species(lrs), src_idx in 1:num_verts(lrs) - ] + hopping_constants = [[Catalyst.get_edge_value(all_diff_rates[s_idx], e) + for e in edge_array[s_idx, src_idx]] + for s_idx in 1:num_species(lrs), src_idx in 1:num_verts(lrs)] return hopping_constants end @@ -79,16 +81,17 @@ end # Not sure if there is any form of performance improvement from that though. Likely not the case. function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) # Creates a vector, storing which reactions have spatial components. - is_spatials = [has_spatial_vertex_component(rx.rate, dprob.p) - for rx in reactions(reactionsystem(lrs))] + is_spatials = [has_spatial_vertex_component(rx.rate, dprob.p) + for rx in reactions(reactionsystem(lrs))] # Creates templates for the rates (uniform and spatial) and the stoichiometries. # We cannot fetch reactant_stoich and net_stoich from a (non-spatial) MassActionJump. # The reason is that we need to re-order the reactions so that uniform appears first, and spatial next. - u_rates = Vector{Float64}(undef, length(reactions(reactionsystem(lrs))) - count(is_spatials)) + num_rxs = length(reactions(reactionsystem(lrs))) + u_rates = Vector{Float64}(undef, num_rxs - count(is_spatials)) s_rates = Matrix{Float64}(undef, count(is_spatials), num_verts(lrs)) - reactant_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(reactionsystem(lrs)))) - net_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(reactionsystem(lrs)))) + reactant_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, num_rxs) + net_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, num_rxs) # Loops through reactions with non-spatial rates, computes their rates and stoichiometries. cur_rx = 1 @@ -101,9 +104,10 @@ function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) cur_rx += 1 end # Loops through reactions with spatial rates, computes their rates and stoichiometries. - for (is_spat, rx) in zip(is_spatials, reactions(reactionsystem(lrs))) + for (is_spat, rx) in zip(is_spatials, reactions(reactionsystem(lrs))) is_spat || continue - s_rates[cur_rx - length(u_rates), :] .= compute_vertex_value(rx.rate, lrs; ps = dprob.p) + s_rates[cur_rx - length(u_rates), :] .= compute_vertex_value(rx.rate, lrs; + ps = dprob.p) substoich_map = Pair.(rx.substrates, rx.substoich) reactant_stoich[cur_rx] = int_map(substoich_map, reactionsystem(lrs)) net_stoich[cur_rx] = int_map(rx.netstoich, reactionsystem(lrs)) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index ca9c88c911..e26cc6051f 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,12 +1,12 @@ ### New Type Unions ### # Cartesian and masked grids share several traits, hence we declare a common (union) type for them. -const GridLattice{N,T} = Union{Array{Bool, N}, CartesianGridRej{N,T}} +const GridLattice{N, T} = Union{Array{Bool, N}, CartesianGridRej{N, T}} ### Lattice Reaction Network Structure ### # Describes a spatial reaction network over a lattice. -struct LatticeReactionSystem{Q,R,S,T} <: MT.AbstractTimeDependentSystem +struct LatticeReactionSystem{Q, R, S, T} <: MT.AbstractTimeDependentSystem # Input values. """The (non-spatial) reaction system within each vertex.""" reactionsystem::ReactionSystem{Q} @@ -47,8 +47,8 @@ struct LatticeReactionSystem{Q,R,S,T} <: MT.AbstractTimeDependentSystem """ edge_iterator::T - function LatticeReactionSystem(rs::ReactionSystem{Q}, spatial_reactions::Vector{R}, lattice::S, - num_verts::Int64, num_edges::Int64, edge_iterator::T) where {Q,R,S,T} + function LatticeReactionSystem(rs::ReactionSystem{Q}, spatial_reactions::Vector{R}, + lattice::S, num_verts::Int64, num_edges::Int64, edge_iterator::T) where {Q, R, S, T} # Error checks. if !(R <: AbstractSpatialReaction) throw(ArgumentError("The second argument must be a vector of AbstractSpatialReaction subtypes.")) @@ -97,10 +97,13 @@ struct LatticeReactionSystem{Q,R,S,T} <: MT.AbstractTimeDependentSystem ps = [parameters(rs); setdiff([edge_parameters; vertex_parameters], parameters(rs))] # Checks that all spatial reactions are valid for this reaction system. - foreach(sr -> check_spatial_reaction_validity(rs, sr; edge_parameters=edge_parameters), spatial_reactions) + foreach( + sr -> check_spatial_reaction_validity(rs, sr; edge_parameters = edge_parameters), + spatial_reactions) - return new{Q,R,S,T}(rs, spatial_reactions, lattice, num_verts, num_edges, num_species, - spat_species, ps, vertex_parameters, edge_parameters, edge_iterator) + return new{Q, R, S, T}( + rs, spatial_reactions, lattice, num_verts, num_edges, num_species, + spat_species, ps, vertex_parameters, edge_parameters, edge_iterator) end end @@ -112,11 +115,14 @@ function LatticeReactionSystem(rs, srs, lattice::DiGraph) return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator) end # Creates a LatticeReactionSystem from a (undirected) Graph lattice (graph grid). -LatticeReactionSystem(rs, srs, lattice::SimpleGraph) = LatticeReactionSystem(rs, srs, DiGraph(lattice)) +function LatticeReactionSystem(rs, srs, lattice::SimpleGraph) + LatticeReactionSystem(rs, srs, DiGraph(lattice)) +end # Creates a LatticeReactionSystem from a CartesianGrid lattice (cartesian grid) or a Boolean Array # lattice (masked grid). These two are quite similar, so much code can be reused in a single interface. -function LatticeReactionSystem(rs, srs, lattice::GridLattice{N,T}; diagonal_connections=false) where {N,T} +function LatticeReactionSystem(rs, srs, lattice::GridLattice{N, T}; + diagonal_connections = false) where {N, T} # Error checks. (N > 3) && error("Grids of higher dimension than 3 is currently not supported.") @@ -135,9 +141,10 @@ function LatticeReactionSystem(rs, srs, lattice::GridLattice{N,T}; diagonal_conn # indices and adds the edges to `edge_iterator`. cur_vert = 0 g_size = grid_size(lattice) - edge_iterator = Vector{Pair{Int64,Int64}}(undef, num_edges) + edge_iterator = Vector{Pair{Int64, Int64}}(undef, num_edges) for (flat_idx, grid_idx) in enumerate(flat_to_grid_idx) - for neighbour_grid_idx in get_neighbours(lattice, grid_idx, g_size; diagonal_connections) + for neighbour_grid_idx in get_neighbours(lattice, grid_idx, g_size; + diagonal_connections) cur_vert += 1 edge_iterator[cur_vert] = flat_idx => grid_to_flat_idx[neighbour_grid_idx...] end @@ -146,24 +153,24 @@ function LatticeReactionSystem(rs, srs, lattice::GridLattice{N,T}; diagonal_conn return LatticeReactionSystem(rs, srs, lattice, num_verts, num_edges, edge_iterator) end - ### LatticeReactionSystem Helper Functions ### # Note, most of these are specifically for (Cartesian or masked) grids, we call them `grid`, not `lattice`. # Counts the number of vertices on a (Cartesian or masked) grid. -count_verts(grid::CartesianGridRej{N,T}) where {N,T} = prod(grid_size(grid)) +count_verts(grid::CartesianGridRej{N, T}) where {N, T} = prod(grid_size(grid)) count_verts(grid::Array{Bool, N}) where {N} = count(grid) # Counts and edges on a Cartesian grid. The formula counts the number of internal, side, edge, and # corner vertices (on the grid). `l,m,n = grid_dims(grid),1,1` ensures that "extra" dimensions get # length 1. The formula holds even if one or more of l, m, and n are 1. -function count_edges(grid::CartesianGridRej{N,T}; diagonal_connections = false) where {N,T} - l,m,n = grid_size(grid)...,1,1 - (ni, ns, ne, nc) = diagonal_connections ? (26,17,11,7) : (6,5,4,3) - num_edges = ni*(l-2)*(m-2)*(n-2) + # Edges from internal vertices. - ns*(2(l-2)*(m-2) + 2(l-2)*(n-2) + 2(m-2)*(n-2)) + # Edges from side vertices. - ne*(4(l-2) + 4(m-2) + 4(n-2)) + # Edges from edge vertices. - nc*8 # Edges from corner vertices. +function count_edges(grid::CartesianGridRej{N, T}; + diagonal_connections = false) where {N, T} + l, m, n = grid_size(grid)..., 1, 1 + (ni, ns, ne, nc) = diagonal_connections ? (26, 17, 11, 7) : (6, 5, 4, 3) + num_edges = ni * (l - 2) * (m - 2) * (n - 2) + # Edges from internal vertices. + ns * (2(l - 2) * (m - 2) + 2(l - 2) * (n - 2) + 2(m - 2) * (n - 2)) + # Edges from side vertices. + ne * (4(l - 2) + 4(m - 2) + 4(n - 2)) + # Edges from edge vertices. + nc * 8 # Edges from corner vertices. return num_edges end @@ -174,7 +181,8 @@ function count_edges(grid::Array{Bool, N}; diagonal_connections = false) where { num_edges = 0 for grid_idx in get_grid_indices(grid) grid[grid_idx] || continue - num_edges += length(get_neighbours(grid, Tuple(grid_idx), g_size; diagonal_connections)) + num_edges += length(get_neighbours(grid, Tuple(grid_idx), g_size; + diagonal_connections)) end return num_edges end @@ -182,7 +190,7 @@ end # For a (1d, 2d, or 3d) (Cartesian or masked) grid, returns a vector and an array, permitting the # conversion between a vertex's flat (scalar) and grid indices. E.g. for a 2d grid, if grid point (3,2) # corresponds to the fifth vertex, then `flat_to_grid_idx[5] = (3,2)` and `grid_to_flat_idx[3,2] = 5`. -function get_index_converters(grid::GridLattice{N,T}, num_verts) where {N,T} +function get_index_converters(grid::GridLattice{N, T}, num_verts) where {N, T} flat_to_grid_idx = Vector{typeof(grid_size(grid))}(undef, num_verts) grid_to_flat_idx = Array{Int64}(undef, grid_size(grid)) @@ -200,21 +208,24 @@ function get_index_converters(grid::GridLattice{N,T}, num_verts) where {N,T} end # For a vertex's grid index, and a lattice, returns the grid indices of all its (valid) neighbours. -function get_neighbours(grid::GridLattice{N,T}, grid_idx, g_size; diagonal_connections = false) where {N,T} +function get_neighbours(grid::GridLattice{N, T}, grid_idx, g_size; + diagonal_connections = false) where {N, T} # Depending on the grid's dimension, find all potential neighbours. if grid_dims(grid) == 1 potential_neighbours = [grid_idx .+ (i) for i in -1:1] elseif grid_dims(grid) == 2 - potential_neighbours = [grid_idx .+ (i,j) for i in -1:1 for j in -1:1] + potential_neighbours = [grid_idx .+ (i, j) for i in -1:1 for j in -1:1] else - potential_neighbours = [grid_idx .+ (i,j,k) for i in -1:1 for j in -1:1 for k in -1:1] + potential_neighbours = [grid_idx .+ (i, j, k) for i in -1:1 for j in -1:1 + for k in -1:1] end # Depending on whether diagonal connections are used or not, find valid neighbours. if diagonal_connections filter!(n_idx -> n_idx !== grid_idx, potential_neighbours) else - filter!(n_idx -> count(n_idx .== grid_idx) == (length(g_size) - 1), potential_neighbours) + filter!(n_idx -> count(n_idx .== grid_idx) == (length(g_size) - 1), + potential_neighbours) end # Removes neighbours outside of the grid, and returns the full list. @@ -223,16 +234,19 @@ end # Checks if a grid index corresponds to a valid grid point. First, check that each dimension of the # index is within the grid's bounds. Next, perform an extra check for the masked grid. -function is_valid_grid_point(grid::GridLattice{N,T}, grid_idx, g_size) where {N,T} - all(0 < g_idx <= dim_leng for (g_idx, dim_leng) in zip(grid_idx, g_size)) || return false +function is_valid_grid_point(grid::GridLattice{N, T}, grid_idx, g_size) where {N, T} + if !all(0 < g_idx <= dim_leng for (g_idx, dim_leng) in zip(grid_idx, g_size)) + return false + end return (grid isa Array{Bool}) ? grid[grid_idx...] : true end # Gets an iterator over a grid's grid indices. Separate function so we can handle the two grid types # separately (i.e. not calling `CartesianIndices(ones(grid_size(grid)))` unnecessarily for masked grids). -get_grid_indices(grid::CartesianGridRej{N,T}) where {N,T} = CartesianIndices(ones(grid_size(grid))) -get_grid_indices(grid::Array{Bool, N}) where {N} = CartesianIndices(grid) - +function get_grid_indices(grid::CartesianGridRej{N, T}) where {N, T} + CartesianIndices(ones(grid_size(grid))) +end +get_grid_indices(grid::Array{Bool, N}) where {N} = CartesianIndices(grid) ### LatticeReactionSystem-specific Getters ### @@ -256,7 +270,9 @@ edge_iterator(lrs::LatticeReactionSystem) = getfield(lrs, :edge_iterator) Returns `true` if all spatial reactions in `lrs` are `TransportReaction`s. """ -is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReaction, spatial_reactions(lrs)) +function is_transport_system(lrs::LatticeReactionSystem) + return all(sr -> sr isa TransportReaction, spatial_reactions(lrs)) +end """ has_cartesian_lattice(lrs::LatticeReactionSystem) @@ -264,7 +280,8 @@ is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReac Returns `true` if `lrs` was created using a cartesian grid lattice (e.g. created via `CartesianGrid(5,5)`). Otherwise, returns `false`. """ -has_cartesian_lattice(lrs::LatticeReactionSystem) = lattice(lrs) isa CartesianGridRej{N,T} where {N,T} +has_cartesian_lattice(lrs::LatticeReactionSystem) = lattice(lrs) isa + CartesianGridRej{N, T} where {N, T} """ has_masked_lattice(lrs::LatticeReactionSystem) @@ -272,14 +289,16 @@ has_cartesian_lattice(lrs::LatticeReactionSystem) = lattice(lrs) isa CartesianGr Returns `true` if `lrs` was created using a masked grid lattice (e.g. created via `[true true; true false]`). Otherwise, returns `false`. """ -has_masked_lattice(lrs::LatticeReactionSystem) = lattice(lrs) isa Array{Bool, N} where N +has_masked_lattice(lrs::LatticeReactionSystem) = lattice(lrs) isa Array{Bool, N} where {N} """ has_grid_lattice(lrs::LatticeReactionSystem) Returns `true` if `lrs` was created using a cartesian or masked grid lattice. Otherwise, returns `false`. """ -has_grid_lattice(lrs::LatticeReactionSystem) = (has_cartesian_lattice(lrs) || has_masked_lattice(lrs)) +function has_grid_lattice(lrs::LatticeReactionSystem) + return has_cartesian_lattice(lrs) || has_masked_lattice(lrs) +end """ has_graph_lattice(lrs::LatticeReactionSystem) @@ -296,9 +315,11 @@ Returns the size of `lrs`'s lattice (only if it is a cartesian or masked grid la E.g. for a lattice `CartesianGrid(4,6)`, `(4,6)` is returned. """ grid_size(lrs::LatticeReactionSystem) = grid_size(lattice(lrs)) -grid_size(lattice::CartesianGridRej{N,T}) where {N,T} = lattice.dims +grid_size(lattice::CartesianGridRej{N, T}) where {N, T} = lattice.dims grid_size(lattice::Array{Bool, N}) where {N} = size(lattice) -grid_size(lattice::Graphs.AbstractGraph) = throw(ArgumentError("Grid size is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).")) +function grid_size(lattice::Graphs.AbstractGraph) + throw(ArgumentError("Grid size is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).")) +end """ grid_dims(lrs::LatticeReactionSystem) @@ -307,8 +328,10 @@ Returns the number of dimensions of `lrs`'s lattice (only if it is a cartesian o The output is either `1`, `2`, or `3`. """ grid_dims(lrs::LatticeReactionSystem) = grid_dims(lattice(lrs)) -grid_dims(lattice::GridLattice{N,T}) where {N,T} = return N -grid_dims(lattice::Graphs.AbstractGraph) = throw(ArgumentError("Grid dimensions is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).")) +grid_dims(lattice::GridLattice{N, T}) where {N, T} = return N +function grid_dims(lattice::Graphs.AbstractGraph) + throw(ArgumentError("Grid dimensions is only defined for LatticeReactionSystems with grid-based lattices (not graph-based).")) +end """ get_lattice_graph(lrs::LatticeReactionSystem) @@ -317,14 +340,16 @@ Returns lrs's lattice, but in as a graph. Currently does not work for Cartesian """ function get_lattice_graph(lrs::LatticeReactionSystem) has_graph_lattice(lrs) && return lattice(lrs) - return Graphs.SimpleGraphFromIterator(Graphs.SimpleEdge(e[1], e[2]) - for e in edge_iterator(lrs)) + return Graphs.SimpleGraphFromIterator(Graphs.SimpleEdge(e[1], e[2]) + for e in edge_iterator(lrs)) end ### Catalyst-based Getters ### # Get all species. -species(lrs::LatticeReactionSystem) = unique([species(reactionsystem(lrs)); spatial_species(lrs)]) +function species(lrs::LatticeReactionSystem) + unique([species(reactionsystem(lrs)); spatial_species(lrs)]) +end # Generic ones (simply forwards call to the non-spatial system). reactions(lrs::LatticeReactionSystem) = reactions(reactionsystem(lrs)) @@ -341,18 +366,18 @@ MT.get_metadata(lrs::LatticeReactionSystem) = MT.get_metadata(reactionsystem(lrs # Lattice reaction systems should not be combined with compositional modelling. # Maybe these should be allowed anyway? Still feel a bit weird -function MT.get_eqs(lrs::LatticeReactionSystem) +function MT.get_eqs(lrs::LatticeReactionSystem) MT.get_eqs(reactionsystem(lrs)) end -function MT.get_unknowns(lrs::LatticeReactionSystem) +function MT.get_unknowns(lrs::LatticeReactionSystem) MT.get_unknowns(reactionsystem(lrs)) end -function MT.get_ps(lrs::LatticeReactionSystem) +function MT.get_ps(lrs::LatticeReactionSystem) MT.get_ps(reactionsystem(lrs)) end # Technically should not be used, but has to be declared for the `show` function to work. -function MT.get_systems(lrs::LatticeReactionSystem) +function MT.get_systems(lrs::LatticeReactionSystem) return [] end @@ -412,15 +437,16 @@ function make_edge_p_values(lrs::LatticeReactionSystem, make_edge_p_value::Funct # Makes the flat to index grid converts. Predeclared the edge parameter value sparse matrix. flat_to_grid_idx = get_index_converters(lattice(lrs), num_verts(lrs))[1] - values = spzeros(num_verts(lrs),num_verts(lrs)) + values = spzeros(num_verts(lrs), num_verts(lrs)) # Loops through all edges, and applies the value function to these. for e in edge_iterator(lrs) # This extra step is needed to ensure that `0` is stored if make_edge_p_value yields a 0. # If not, then the sparse matrix simply becomes empty in that position. - values[e[1],e[2]] = eps() + values[e[1], e[2]] = eps() - values[e[1],e[2]] = make_edge_p_value(flat_to_grid_idx[e[1]], flat_to_grid_idx[e[2]]) + values[e[1], e[2]] = make_edge_p_value(flat_to_grid_idx[e[1]], + flat_to_grid_idx[e[2]]) end return values @@ -469,8 +495,9 @@ D_vals = make_directed_edge_values(lrs, (0.1, 0.1), (0.1, 0.0)) ``` Here, since we have a 2d grid, we only provide the first two Tuples to `make_directed_edge_values`. """ -function make_directed_edge_values(lrs::LatticeReactionSystem, x_vals::Tuple{T,T}, y_vals::Union{Nothing,Tuple{T,T}} = nothing, - z_vals::Union{Nothing,Tuple{T,T}} = nothing) where {T} +function make_directed_edge_values(lrs::LatticeReactionSystem, x_vals::Tuple{T, T}, + y_vals::Union{Nothing, Tuple{T, T}} = nothing, + z_vals::Union{Nothing, Tuple{T, T}} = nothing) where {T} # Error checks. if has_graph_lattice(lrs) error("The `make_directed_edge_values` function is only meant for lattices with (Cartesian or masked) grid structures. It cannot be applied to graph lattices.") @@ -497,4 +524,3 @@ function make_directed_edge_values(lrs::LatticeReactionSystem, x_vals::Tuple{T,T # Uses the make_edge_p_values function to compute the output. return make_edge_p_values(lrs, directed_vals_func) end - diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 40da3aa6b0..1d31a0ff35 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -2,7 +2,7 @@ # Functor with information about a spatial Lattice Reaction ODEs forcing and Jacobian functions. # Also used as ODE Function input to corresponding `ODEProblem`. -struct LatticeTransportODEFunction{P,Q,R,S,T} +struct LatticeTransportODEFunction{P, Q, R, S, T} """ The ODEFunction of the (non-spatial) ReactionSystem that generated this LatticeTransportODEFunction instance. @@ -38,7 +38,7 @@ struct LatticeTransportODEFunction{P,Q,R,S,T} stores a single value (in a size (1,1) sparse matrix). Otherwise, stores these in a sparse matrix where value (i,j) is the species transportation rate from vertex i to vertex j. """ - transport_rates::Vector{Pair{Int64,SparseMatrixCSC{S, Int64}}} + transport_rates::Vector{Pair{Int64, SparseMatrixCSC{S, Int64}}} """ For each transport rate in transport_rates, its value is a (sparse) matrix with a size of either (num_verts,num_verts) or (1,1). In the second case, the transportation rate is uniform across @@ -63,19 +63,22 @@ struct LatticeTransportODEFunction{P,Q,R,S,T} sparse::Bool """Remove when we add this as problem metadata""" lrs::LatticeReactionSystem - - function LatticeTransportODEFunction(ofunc::P, ps::Vector{<:Pair}, - lrs::LatticeReactionSystem, transport_rates::Vector{Pair{Int64, SparseMatrixCSC{S, Int64}}}, - jac_transport::Union{Nothing, Matrix{S}, SparseMatrixCSC{S, Int64}}, sparse) where {P,S} + + function LatticeTransportODEFunction(ofunc::P, ps::Vector{<:Pair}, + lrs::LatticeReactionSystem, sparse::Bool, + jac_transport::Union{Nothing, Matrix{S}, SparseMatrixCSC{S, Int64}}, + transport_rates::Vector{Pair{Int64, SparseMatrixCSC{S, Int64}}}) where {P, S} # Computes `LatticeTransportODEFunction` functor fields. heterogeneous_vert_p_idxs = make_heterogeneous_vert_p_idxs(ps, lrs) mtk_ps, p_setters = make_mtk_ps_structs(ps, lrs, heterogeneous_vert_p_idxs) - t_rate_idx_types, leaving_rates = make_t_types_and_leaving_rates(transport_rates, lrs) + t_rate_idx_types, leaving_rates = make_t_types_and_leaving_rates(transport_rates, + lrs) # Creates and returns the `LatticeTransportODEFunction` functor. - new{P,typeof(mtk_ps),typeof(p_setters),S,typeof(jac_transport)}(ofunc, num_verts(lrs), - num_species(lrs), heterogeneous_vert_p_idxs, mtk_ps, p_setters, transport_rates, - t_rate_idx_types, leaving_rates, Catalyst.edge_iterator(lrs), jac_transport, sparse, lrs) + new{P, typeof(mtk_ps), typeof(p_setters), S, typeof(jac_transport)}(ofunc, + num_verts(lrs), num_species(lrs), heterogeneous_vert_p_idxs, mtk_ps, p_setters, + transport_rates, t_rate_idx_types, leaving_rates, Catalyst.edge_iterator(lrs), + jac_transport, sparse, lrs) end end @@ -84,7 +87,8 @@ end # Creates a vector with the heterogeneous vertex parameters' indexes in the full parameter vector. function make_heterogeneous_vert_p_idxs(ps, lrs) p_dict = Dict(ps) - return findall((p_dict[p] isa Vector) && (length(p_dict[p]) > 1) for p in parameters(lrs)) + return findall((p_dict[p] isa Vector) && (length(p_dict[p]) > 1) + for p in parameters(lrs)) end # Creates the MTKParameters structure and `p_setters` vector (which are used to manage @@ -94,18 +98,20 @@ function make_mtk_ps_structs(ps, lrs, heterogeneous_vert_p_idxs) nonspatial_osys = complete(convert(ODESystem, reactionsystem(lrs))) p_init = [p => p_dict[p][1] for p in parameters(nonspatial_osys)] mtk_ps = MT.MTKParameters(nonspatial_osys, p_init) - p_setters = [MT.setp(nonspatial_osys, p) for p in parameters(lrs)[heterogeneous_vert_p_idxs]] + p_setters = [MT.setp(nonspatial_osys, p) + for p in parameters(lrs)[heterogeneous_vert_p_idxs]] return mtk_ps, p_setters end # Computes the transport rate type vector and leaving rate matrix. function make_t_types_and_leaving_rates(transport_rates, lrs) - t_rate_idx_types = [size(tr[2]) == (1,1) for tr in transport_rates] + t_rate_idx_types = [size(tr[2]) == (1, 1) for tr in transport_rates] leaving_rates = zeros(length(transport_rates), num_verts(lrs)) for (s_idx, tr_pair) in enumerate(transport_rates) for e in Catalyst.edge_iterator(lrs) # Updates the exit rate for species s_idx from vertex e.src. - leaving_rates[s_idx, e[1]] += get_transport_rate(tr_pair[2], e, t_rate_idx_types[s_idx]) + leaving_rates[s_idx, e[1]] += get_transport_rate(tr_pair[2], e, + t_rate_idx_types[s_idx]) end end return t_rate_idx_types, leaving_rates @@ -119,26 +125,27 @@ function (lt_ofun::LatticeTransportODEFunction)(du::AbstractVector, u, p, t) for vert_i in 1:(lt_ofun.num_verts) # Gets the indices of all the species at vertex i. idxs = get_indexes(vert_i, lt_ofun.num_species) - + # Updates the functors vertex parameter tracker (`mtk_ps`) to contain the vertex parameter # values for vertex vert_i. Then evaluates the reaction contributions to du at vert_i. update_mtk_ps!(lt_ofun, p, vert_i) - lt_ofun.ofunc((@view du[idxs]), (@view u[idxs]), lt_ofun.mtk_ps, t) + lt_ofun.ofunc((@view du[idxs]), (@view u[idxs]), lt_ofun.mtk_ps, t) end # s_idx is the species index among transport species, s is the index among all species. # rates are the species' transport rates. - for (s_idx, (s, rates)) in enumerate(lt_ofun.transport_rates) + for (s_idx, (s, rates)) in enumerate(lt_ofun.transport_rates) # Rate for leaving source vertex vert_i. - for vert_i in 1:(lt_ofun.num_verts) - idx_src = get_index(vert_i, s, lt_ofun.num_species) + for vert_i in 1:(lt_ofun.num_verts) + idx_src = get_index(vert_i, s, lt_ofun.num_species) du[idx_src] -= lt_ofun.leaving_rates[s_idx, vert_i] * u[idx_src] end # Add rates for entering a destination vertex via an incoming edge. - for e in lt_ofun.edge_iterator + for e in lt_ofun.edge_iterator idx_src = get_index(e[1], s, lt_ofun.num_species) - idx_dst = get_index(e[2], s, lt_ofun.num_species) - du[idx_dst] += get_transport_rate(rates, e, lt_ofun.t_rate_idx_types[s_idx]) * u[idx_src] + idx_dst = get_index(e[2], s, lt_ofun.num_species) + du[idx_dst] += get_transport_rate(rates, e, lt_ofun.t_rate_idx_types[s_idx]) * + u[idx_src] end end end @@ -152,7 +159,7 @@ function (lt_ofun::LatticeTransportODEFunction)(J::AbstractMatrix, u, p, t) for vert_i in 1:(lt_ofun.num_verts) # Gets the indices of all the species at vertex i. idxs = get_indexes(vert_i, lt_ofun.num_species) - + # Updates the functors vertex parameter tracker (`mtk_ps`) to contain the vertex parameter # values for vertex vert_i. Then evaluates the reaction contributions to J at vert_i. update_mtk_ps!(lt_ofun, p, vert_i) @@ -167,52 +174,54 @@ end # Creates an ODEProblem from a LatticeReactionSystem. function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, - p_in = DiffEqBase.NullParameters(), args...; - jac = false, sparse = false, - name = nameof(lrs), include_zero_odes = true, - combinatoric_ratelaws = get_combinatoric_ratelaws(reactionsystem(lrs)), - remove_conserved = false, checks = false, kwargs...) + p_in = DiffEqBase.NullParameters(), args...; + jac = false, sparse = false, + name = nameof(lrs), include_zero_odes = true, + combinatoric_ratelaws = get_combinatoric_ratelaws(reactionsystem(lrs)), + remove_conserved = false, checks = false, kwargs...) if !is_transport_system(lrs) error("Currently lattice ODE simulations are only supported when all spatial reactions are TransportReactions.") end - + # Converts potential symmaps to varmaps. u0_in = symmap_to_varmap(lrs, u0_in) - p_in = symmap_to_varmap(lrs, p_in) + p_in = symmap_to_varmap(lrs, p_in) # Converts u0 and p to their internal forms. # u0 is simply a vector with all the species' initial condition values across all vertices. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. - u0 = lattice_process_u0(u0_in, species(lrs), lrs) + u0 = lattice_process_u0(u0_in, species(lrs), lrs) # vert_ps and `edge_ps` are vector maps, taking each parameter's Symbolics representation to its value(s). # vert_ps values are vectors. Here, index (i) is a parameter's value in vertex i. # edge_ps values are sparse matrices. Here, index (i,j) is a parameter's value in the edge from vertex i to vertex j. # Uniform vertex/edge parameters store only a single value (a length 1 vector, or size 1x1 sparse matrix). # In the `ODEProblem` vert_ps and edge_ps are merged (but for building the ODEFunction, they are separate). - vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), + lrs) # Creates the ODEFunction. - ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse, name, include_zero_odes, - combinatoric_ratelaws, remove_conserved, checks) + ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse, name, include_zero_odes, + combinatoric_ratelaws, remove_conserved, checks) # Combines `vert_ps` and `edge_ps` to a single vector with values only (not a map). Creates ODEProblem. pval_dict = Dict([vert_ps; edge_ps]) ps = [pval_dict[p] for p in parameters(lrs)] - return ODEProblem(ofun, u0, tspan, ps, args...; kwargs...) + return ODEProblem(ofun, u0, tspan, ps, args...; kwargs...) end # Builds an ODEFunction for a spatial ODEProblem. -function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{R,Vector{T}}}, - edge_ps::Vector{Pair{S,SparseMatrixCSC{T, Int64}}}, - jac::Bool, sparse::Bool, name, include_zero_odes, combinatoric_ratelaws, - remove_conserved, checks) where {R,S,T} +function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{R, Vector{T}}}, + edge_ps::Vector{Pair{S, SparseMatrixCSC{T, Int64}}}, + jac::Bool, sparse::Bool, name, include_zero_odes, combinatoric_ratelaws, + remove_conserved, checks) where {R, S, T} # Error check. - if remove_conserved + if remove_conserved throw(ArgumentError("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s")) end # Prepares the inputs to the `LatticeTransportODEFunction` functor. - osys = complete(convert(ODESystem, reactionsystem(lrs); name, combinatoric_ratelaws, include_zero_odes, checks)) + osys = complete(convert(ODESystem, reactionsystem(lrs); + name, combinatoric_ratelaws, include_zero_odes, checks)) ofunc_dense = ODEFunction(osys; jac = true, sparse = false) ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) @@ -222,14 +231,16 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{R,Ve jac_transport = nothing jac_prototype = nothing else - jac_sparse = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = jac) + jac_sparse = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; + set_nonzero = jac) jac_dense = Matrix(jac_sparse) jac_transport = (jac ? (sparse ? jac_sparse : jac_dense) : nothing) jac_prototype = (sparse ? jac_sparse : nothing) end # Creates the `LatticeTransportODEFunction` functor (if `jac`, sets it as the Jacobian as well). - f = LatticeTransportODEFunction(ofunc_dense, [vert_ps; edge_ps], lrs, transport_rates, jac_transport, sparse) + f = LatticeTransportODEFunction(ofunc_dense, [vert_ps; edge_ps], lrs, sparse, + jac_transport, transport_rates) J = (jac ? f : nothing) # Extracts the `Symbol` form for species and parameters. Creates and returns the `ODEFunction`. @@ -240,13 +251,14 @@ end # Builds a jacobian prototype. # If requested, populate it with the constant values of the Jacobian's transportation part. -function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, - transport_rates::Vector{Pair{Int64,SparseMatrixCSC{T, Int64}}}, - lrs::LatticeReactionSystem; set_nonzero = false) where {T} +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, + transport_rates::Vector{Pair{Int64, SparseMatrixCSC{T, Int64}}}, + lrs::LatticeReactionSystem; set_nonzero = false) where {T} # Finds the indices of both the transport species, # and the species with transport only (that is, with no non-spatial dynamics but with spatial dynamics). trans_species = [tr[1] for tr in transport_rates] - trans_only_species = filter(s_idx -> !Base.isstored(ns_jac_prototype, s_idx, s_idx), trans_species) + trans_only_species = filter(s_idx -> !Base.isstored(ns_jac_prototype, s_idx, s_idx), + trans_species) # Finds the indices of all terms in the non-spatial jacobian. ns_jac_prototype_idxs = findnz(ns_jac_prototype) @@ -255,7 +267,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, # Prepares vectors to store i and j indices of Jacobian entries. idx = 1 - num_entries = num_verts(lrs) * length(ns_i_idxs) + + num_entries = num_verts(lrs) * length(ns_i_idxs) + num_edges(lrs) * (length(trans_only_species) + length(trans_species)) i_idxs = Vector{Int}(undef, num_entries) j_idxs = Vector{Int}(undef, num_entries) @@ -301,7 +313,7 @@ function set_jac_transport_values!(jac_prototype, transport_rates, lrs) for (s, rates) in transport_rates, e in edge_iterator(lrs) idx_src = get_index(e[1], s, num_species(lrs)) idx_dst = get_index(e[2], s, num_species(lrs)) - val = get_transport_rate(rates, e, size(rates)==(1,1)) + val = get_transport_rate(rates, e, size(rates) == (1, 1)) # Term due to species leaving source vertex. jac_prototype[idx_src, idx_src] -= val @@ -326,7 +338,8 @@ function rebuild_lat_internals!(integrator) end # Function which rebuilds a `LatticeTransportODEFunction` functor for a new parameter set. -function rebuild_lat_internals!(lt_ofun::LatticeTransportODEFunction, ps_new, lrs::LatticeReactionSystem) +function rebuild_lat_internals!(lt_ofun::LatticeTransportODEFunction, ps_new, + lrs::LatticeReactionSystem) # Computes Jacobian properties. jac = !isnothing(lt_ofun.jac_transport) sparse = lt_ofun.sparse @@ -334,9 +347,10 @@ function rebuild_lat_internals!(lt_ofun::LatticeTransportODEFunction, ps_new, lr # Recreates the new parameters on the requisite form. ps_new = [(length(p) == 1) ? p[1] : p for p in deepcopy(ps_new)] ps_new = [p => p_val for (p, p_val) in zip(parameters(lrs), deepcopy(ps_new))] - vert_ps, edge_ps = lattice_process_p(ps_new, vertex_parameters(lrs), edge_parameters(lrs), lrs) + vert_ps, edge_ps = lattice_process_p(ps_new, vertex_parameters(lrs), + edge_parameters(lrs), lrs) ps_new = [vert_ps; edge_ps] - + # Creates the new transport rates and transport Jacobian part. transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) if !isnothing(lt_ofun.jac_transport) @@ -377,5 +391,5 @@ function replace_vec!(vec1, vec2) vec1[i] = v end foreach(idx -> deleteat!(vec1, idx), l1:-1:(l2 + 1)) - foreach(val -> push!(vec1, val), vec2[l1+1:l2]) -end \ No newline at end of file + foreach(val -> push!(vec1, val), vec2[(l1 + 1):l2]) +end diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 222ea4640b..43ae1fc807 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -17,7 +17,6 @@ function isedgeparameter(x, default = false) Symbolics.getmetadata(x, EdgeParameter, default) end - ### Transport Reaction Structures ### # A transport reaction. These are simple to handle, and should cover most types of spatial reactions. @@ -64,8 +63,8 @@ function make_transport_reaction(rateex, species) trxexpr = :(TransportReaction($rateex, $species)) # Appends `edgeparameter` metadata to all declared parameters. - for idx = 4:2:(2 + 2*length(parameters)) - insert!(pexprs.args, idx, :([edgeparameter=true])) + for idx in 4:2:(2 + 2 * length(parameters)) + insert!(pexprs.args, idx, :([edgeparameter = true])) end quote @@ -83,11 +82,12 @@ ModelingToolkit.parameters(tr::TransportReaction) = Symbolics.get_variables(tr.r spatial_species(tr::TransportReaction) = [tr.species] # Checks that a TransportReactions is valid for a given reaction system. -function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction; edge_parameters=[]) +function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction; + edge_parameters = []) # Checks that the species exist in the reaction system. # (ODE simulation code becomes difficult if this is not required, # as non-spatial jacobian and f function generated from rs are of the wrong size). - if !any(isequal(tr.species), species(rs)) + if !any(isequal(tr.species), species(rs)) error("Currently, species used in TransportReactions must have previously been declared within the non-spatial ReactionSystem. This is not the case for $(tr.species).") end @@ -148,7 +148,6 @@ function hash(tr::TransportReaction, h::UInt) Base.hash(tr.species, h) end - ### Utility ### # Loops through a rate and extracts all parameters. diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index e242ba3935..80a490f912 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -18,7 +18,7 @@ end function lattice_process_u0(u0_in, u0_syms::Vector, lrs::LatticeReactionSystem) # u0 values can be given in various forms. This converts it to a Vector{Pair{Symbolics,...}} form. # Top-level vector: Maps each species to its value(s). - u0 = lattice_process_input(u0_in, u0_syms) + u0 = lattice_process_input(u0_in, u0_syms) # Species' initial condition values can be given in different forms (also depending on the lattice). # This converts each species's values to a Vector. In it, for species with uniform initial conditions, @@ -27,22 +27,22 @@ function lattice_process_u0(u0_in, u0_syms::Vector, lrs::LatticeReactionSystem) u0 = vertex_value_map(u0, lrs) # Converts the initial condition to a single Vector (with one value for each species and vertex). - return expand_component_values([entry[2] for entry in u0], num_verts(lrs)) + return expand_component_values([entry[2] for entry in u0], num_verts(lrs)) end # From a parameter input, split it into vertex parameters and edge parameters. # Store these in the desired internal format. -function lattice_process_p(ps_in, ps_vertex_syms::Vector, - ps_edge_syms::Vector, lrs::LatticeReactionSystem) +function lattice_process_p(ps_in, ps_vertex_syms::Vector, + ps_edge_syms::Vector, lrs::LatticeReactionSystem) # p values can be given in various forms. This converts it to a Vector{Pair{Symbolics,...}} form. # Top-level vector: Maps each parameter to its value(s). # Second-level: Contains either a vector (vertex parameters) or a sparse matrix (edge parameters). # For uniform parameters these have size 1/(1,1). Else, they have size num_verts/(num_verts,num_verts). - ps = lattice_process_input(ps_in, [ps_vertex_syms; ps_edge_syms]) + ps = lattice_process_input(ps_in, [ps_vertex_syms; ps_edge_syms]) # Split the parameter vector into one for vertex parameters and one for edge parameters. # Next, convert their values to the correct form (vectors for vert_ps and sparse matrices for edge_ps). - vert_ps, edge_ps = split_parameters(ps, ps_vertex_syms, ps_edge_syms) + vert_ps, edge_ps = split_parameters(ps, ps_vertex_syms, ps_edge_syms) vert_ps = vertex_value_map(vert_ps, lrs) edge_ps = edge_value_map(edge_ps, lrs) @@ -71,9 +71,9 @@ function lattice_process_input(input, syms::Vector) end # Splits parameters into vertex and edge parameters. -function split_parameters(ps, p_vertex_syms::Vector, p_edge_syms::Vector) - vert_ps = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] - edge_ps = [p for p in ps if any(isequal(p[1]), p_edge_syms)] +function split_parameters(ps, p_vertex_syms::Vector, p_edge_syms::Vector) + vert_ps = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] + edge_ps = [p for p in ps if any(isequal(p[1]), p_edge_syms)] return vert_ps, edge_ps end @@ -99,7 +99,7 @@ function vertex_value_form(values, lrs::LatticeReactionSystem, sym::BasicSymboli # For the case where the i'th value of the vector corresponds to the value in the i'th vertex. # This is the only (non-uniform) case possible for graph grids. - if (length(values) != num_verts(lrs)) + if (length(values) != num_verts(lrs)) throw(ArgumentError("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertices ($(num_verts(lrs))).")) end return values @@ -110,20 +110,20 @@ function vertex_value_form(values, lrs::LatticeReactionSystem, sym::BasicSymboli end # Converts values to the correct vector form for a Cartesian grid lattice. -function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::CartesianGridRej{N,T}, - sym::BasicSymbolic) where {N,T} +function vertex_value_form(values::AbstractArray, num_verts::Int64, + lattice::CartesianGridRej{N, T}, sym::BasicSymbolic) where {N, T} if size(values) != lattice.dims throw(ArgumentError("The values for $sym did not have the same format as the lattice. Expected a $(lattice.dims) array, got one of size $(size(values))")) end - if (length(values) != num_verts) + if (length(values) != num_verts) throw(ArgumentError("You have provided ($(length(values))) values for $sym. This is not equal to the number of vertices ($(num_verts)).")) end return [values[flat_idx] for flat_idx in 1:num_verts] end # Converts values to the correct vector form for a masked grid lattice. -function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::Array{Bool,T}, - sym::BasicSymbolic) where {T} +function vertex_value_form(values::AbstractArray, num_verts::Int64, + lattice::Array{Bool, T}, sym::BasicSymbolic) where {T} if size(values) != size(lattice) throw(ArgumentError("The values for $sym did not have the same format as the lattice. Expected a $(size(lattice)) array, got one of size $(size(values))")) end @@ -132,13 +132,13 @@ function vertex_value_form(values::AbstractArray, num_verts::Int64, lattice::Arr # Loops through the lattice and the values, adding these to the return_values. return_values = Vector{typeof(values[1])}(undef, num_verts) cur_idx = 0 - for (idx,val) = enumerate(values) + for (idx, val) in enumerate(values) lattice[idx] || continue return_values[cur_idx += 1] = val end # Checks that the correct number of values was provided, and returns the values. - if (length(return_values) != num_verts) + if (length(return_values) != num_verts) throw(ArgumentError("You have provided ($(length(return_values))) values for $sym. This is not equal to the number of vertices ($(num_verts)).")) end return return_values @@ -155,7 +155,7 @@ end function edge_value_form(values, lrs::LatticeReactionSystem, sym) # If the value is a scalar (i.e. uniform across the lattice), return it in sparse matrix form. (values isa SparseMatrixCSC) || (return sparse([1], [1], [values])) - + # Error checks. if nnz(values) != num_edges(lrs) throw(ArgumentError("You have provided ($(nnz(values))) values for $sym. This is not equal to the number of edges ($(num_edges(lrs))).")) @@ -173,18 +173,19 @@ end # The species is represented by its index (in species(lrs). # If the rate is uniform across all edges, the transportation rate will be a size (1,1) sparse matrix. # Else, the rate will be a size (num_verts,num_verts) sparse matrix. -function make_sidxs_to_transrate_map(vert_ps::Vector{Pair{R,Vector{T}}}, - edge_ps::Vector{Pair{S,SparseMatrixCSC{T, Int64}}}, - lrs::LatticeReactionSystem) where {R,S,T} +function make_sidxs_to_transrate_map(vert_ps::Vector{Pair{R, Vector{T}}}, + edge_ps::Vector{Pair{S, SparseMatrixCSC{T, Int64}}}, + lrs::LatticeReactionSystem) where {R, S, T} # Creates a dictionary with each parameter's value(s). p_val_dict = Dict(vcat(vert_ps, edge_ps)) # First, compute a map from species in their symbolics form to their values. # Next, convert to map from species index to values. transport_rates_speciesmap = compute_all_transport_rates(p_val_dict, lrs) - return Pair{Int64,SparseMatrixCSC{T, Int64}}[ - speciesmap(reactionsystem(lrs))[spat_rates[1]] => spat_rates[2] for spat_rates in transport_rates_speciesmap - ] + return Pair{Int64, SparseMatrixCSC{T, Int64}}[ + speciesmap(reactionsystem(lrs))[spat_rates[1]] => spat_rates[2] + for spat_rates in transport_rates_speciesmap + ] end # Computes the transport rates for all species with transportation rates. Output is a map @@ -192,10 +193,11 @@ end function compute_all_transport_rates(p_val_dict, lrs::LatticeReactionSystem) # For all species with transportation, compute their transportation rate (across all edges). # This is a vector, pairing each species to these rates. - unsorted_rates = [s => compute_transport_rates(s, p_val_dict, lrs) for s in spatial_species(lrs)] - + unsorted_rates = [s => compute_transport_rates(s, p_val_dict, lrs) + for s in spatial_species(lrs)] + # Sorts all the species => rate pairs according to their species index in species(lrs). - return sort(unsorted_rates; by = rate -> findfirst(isequal(rate[1]), species(lrs))) + return sort(unsorted_rates; by = rate -> findfirst(isequal(rate[1]), species(lrs))) end # For the expression describing the rate of transport (likely only a single parameter, e.g. `D`), @@ -211,9 +213,9 @@ function compute_transport_rates(s::BasicSymbolic, p_val_dict, lrs::LatticeReact # If all these parameters are spatially uniform, the rates become a size (1,1) sparse matrix. # Else, the rates become a size (num_verts,num_verts) sparse matrix. - if all(size(p_val_dict[p]) == (1,1) for p in relevant_ps) + if all(size(p_val_dict[p]) == (1, 1) for p in relevant_ps) relevant_p_vals = [get_edge_value(p_val_dict[p], 1 => 1) for p in relevant_ps] - return sparse([1],[1],rate_law_func(relevant_p_vals...)) + return sparse([1], [1], rate_law_func(relevant_p_vals...)) else transport_rates = spzeros(num_verts(lrs), num_verts(lrs)) for e in edge_iterator(lrs) @@ -243,14 +245,17 @@ end # Gets the index in the u array of species s in vertex vert (when there are num_species species). get_index(vert::Int64, s::Int64, num_species::Int64) = (vert - 1) * num_species + s # Gets the indices in the u array of all species in vertex vert (when there are num_species species). -get_indexes(vert::Int64, num_species::Int64) = ((vert - 1) * num_species + 1):(vert * num_species) +function get_indexes(vert::Int64, num_species::Int64) + return ((vert - 1) * num_species + 1):(vert * num_species) +end # Returns the value of a parameter in an edge. For vertex parameters, use their values in the source. -function get_edge_value(values::Vector{T}, edge::Pair{Int64,Int64}) where {T} +function get_edge_value(values::Vector{T}, edge::Pair{Int64, Int64}) where {T} return (length(values) == 1) ? values[1] : values[edge[1]] end -function get_edge_value(values::SparseMatrixCSC{T, Int64}, edge::Pair{Int64,Int64}) where {T} - return (size(values) == (1,1)) ? values[1,1] : values[edge[1],edge[2]] +function get_edge_value(values::SparseMatrixCSC{T, Int64}, + edge::Pair{Int64, Int64}) where {T} + return (size(values) == (1, 1)) ? values[1, 1] : values[edge[1], edge[2]] end # Returns the value of an initial condition of vertex parameter in a vertex. @@ -259,14 +264,15 @@ function get_vertex_value(values::Vector{T}, vert_idx::Int64) where {T} end # Finds the transport rate of a parameter along a specific edge. -function get_transport_rate(transport_rate::SparseMatrixCSC{T, Int64}, edge::Pair{Int64,Int64}, - t_rate_idx_types::Bool) where {T} - return t_rate_idx_types ? transport_rate[1,1] : transport_rate[edge[1],edge[2]] +function get_transport_rate(transport_rate::SparseMatrixCSC{T, Int64}, + edge::Pair{Int64, Int64}, t_rate_idx_types::Bool) where {T} + return t_rate_idx_types ? transport_rate[1, 1] : transport_rate[edge[1], edge[2]] end # For a `LatticeTransportODEFunction`, updates its stored parameters (in `mtk_ps`) so that they # the heterogeneous parameters' values correspond to the values in the specified vertex. -function update_mtk_ps!(lt_ofun::LatticeTransportODEFunction, all_ps::Vector{T}, vert::Int64) where {T} +function update_mtk_ps!(lt_ofun::LatticeTransportODEFunction, all_ps::Vector{T}, + vert::Int64) where {T} for (setp, idx) in zip(lt_ofun.p_setters, lt_ofun.heterogeneous_vert_p_idxs) setp(lt_ofun.mtk_ps, all_ps[idx][vert]) end @@ -293,8 +299,8 @@ function compute_vertex_value(exp, lrs::LatticeReactionSystem; u = [], ps = []) if all(length(value_dict[sym]) == 1 for sym in relevant_syms) return [exp_func([value_dict[sym][1] for sym in relevant_syms]...)] end - return [exp_func([get_vertex_value(value_dict[sym], vert_idx) for sym in relevant_syms]...) - for vert_idx in 1:num_verts(lrs)] + return [exp_func([get_vertex_value(value_dict[sym], vert_idx) for sym in relevant_syms]...) + for vert_idx in 1:num_verts(lrs)] end ### System Property Checks ### @@ -304,5 +310,5 @@ end function has_spatial_vertex_component(exp, ps) relevant_syms = Symbolics.get_variables(exp) value_dict = Dict(filter(p -> p[2] isa Vector, ps)) - return any(length(value_dict[sym]) > 1 for sym in relevant_syms) + return any(length(value_dict[sym]) > 1 for sym in relevant_syms) end diff --git a/test/spatial_modelling/lattice_reaction_systems_jumps.jl b/test/spatial_modelling/lattice_reaction_systems_jumps.jl index 904bad1173..fe32701b11 100644 --- a/test/spatial_modelling/lattice_reaction_systems_jumps.jl +++ b/test/spatial_modelling/lattice_reaction_systems_jumps.jl @@ -139,7 +139,6 @@ let # Check that higher p gives higher mean. for i = 1:5 sol = solve(jprob, SSAStepper(); saveat = 1.) - println() @test mean(getindex.(sol.u, 1)) < mean(getindex.(sol.u, 2)) < mean(getindex.(sol.u, 3)) < mean(getindex.(sol.u, 4)) end end From c70e716a9fb3e90c680f379ddc086fe3c2e4cc56 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 8 Jul 2024 21:40:43 -0400 Subject: [PATCH 422/446] format update --- .../lattice_jump_systems.jl | 8 ++++---- .../lattice_reaction_systems.jl | 13 +++++++------ .../spatial_ODE_systems.jl | 16 ++++++++-------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 97cd86b1ad..826a48856f 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -19,16 +19,16 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, # vert_ps values are vectors. Here, index (i) is a parameter's value in vertex i. # edge_ps values are sparse matrices. Here, index (i,j) is a parameter's value in the edge from vertex i to vertex j. # Uniform vertex/edge parameters store only a single value (a length 1 vector, or size 1x1 sparse matrix). - vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), - lrs) + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), + edge_parameters(lrs), lrs) # Returns a DiscreteProblem (which basically just stores the processed input). return DiscreteProblem(u0, tspan, [vert_ps; edge_ps], args...; kwargs...) end # Builds a spatial JumpProblem from a DiscreteProblem containing a `LatticeReactionSystem`. -function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator,args...; - combinatoric_ratelaws = get_combinatoric_ratelaws(reactionsystem(lrs)), +function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; + combinatoric_ratelaws = get_combinatoric_ratelaws(reactionsystem(lrs)), name = nameof(reactionsystem(lrs)), kwargs...) # Error checks. if !isnothing(dprob.f.sys) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index e26cc6051f..7f9e2923cf 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -48,7 +48,8 @@ struct LatticeReactionSystem{Q, R, S, T} <: MT.AbstractTimeDependentSystem edge_iterator::T function LatticeReactionSystem(rs::ReactionSystem{Q}, spatial_reactions::Vector{R}, - lattice::S, num_verts::Int64, num_edges::Int64, edge_iterator::T) where {Q, R, S, T} + lattice::S, num_verts::Int64, num_edges::Int64, + edge_iterator::T) where {Q, R, S, T} # Error checks. if !(R <: AbstractSpatialReaction) throw(ArgumentError("The second argument must be a vector of AbstractSpatialReaction subtypes.")) @@ -121,7 +122,7 @@ end # Creates a LatticeReactionSystem from a CartesianGrid lattice (cartesian grid) or a Boolean Array # lattice (masked grid). These two are quite similar, so much code can be reused in a single interface. -function LatticeReactionSystem(rs, srs, lattice::GridLattice{N, T}; +function LatticeReactionSystem(rs, srs, lattice::GridLattice{N, T}; diagonal_connections = false) where {N, T} # Error checks. (N > 3) && error("Grids of higher dimension than 3 is currently not supported.") @@ -144,7 +145,7 @@ function LatticeReactionSystem(rs, srs, lattice::GridLattice{N, T}; edge_iterator = Vector{Pair{Int64, Int64}}(undef, num_edges) for (flat_idx, grid_idx) in enumerate(flat_to_grid_idx) for neighbour_grid_idx in get_neighbours(lattice, grid_idx, g_size; - diagonal_connections) + diagonal_connections) cur_vert += 1 edge_iterator[cur_vert] = flat_idx => grid_to_flat_idx[neighbour_grid_idx...] end @@ -270,7 +271,7 @@ edge_iterator(lrs::LatticeReactionSystem) = getfield(lrs, :edge_iterator) Returns `true` if all spatial reactions in `lrs` are `TransportReaction`s. """ -function is_transport_system(lrs::LatticeReactionSystem) +function is_transport_system(lrs::LatticeReactionSystem) return all(sr -> sr isa TransportReaction, spatial_reactions(lrs)) end @@ -296,7 +297,7 @@ has_masked_lattice(lrs::LatticeReactionSystem) = lattice(lrs) isa Array{Bool, N} Returns `true` if `lrs` was created using a cartesian or masked grid lattice. Otherwise, returns `false`. """ -function has_grid_lattice(lrs::LatticeReactionSystem) +function has_grid_lattice(lrs::LatticeReactionSystem) return has_cartesian_lattice(lrs) || has_masked_lattice(lrs) end @@ -445,7 +446,7 @@ function make_edge_p_values(lrs::LatticeReactionSystem, make_edge_p_value::Funct # If not, then the sparse matrix simply becomes empty in that position. values[e[1], e[2]] = eps() - values[e[1], e[2]] = make_edge_p_value(flat_to_grid_idx[e[1]], + values[e[1], e[2]] = make_edge_p_value(flat_to_grid_idx[e[1]], flat_to_grid_idx[e[2]]) end diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 1d31a0ff35..9879e27221 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -66,7 +66,7 @@ struct LatticeTransportODEFunction{P, Q, R, S, T} function LatticeTransportODEFunction(ofunc::P, ps::Vector{<:Pair}, lrs::LatticeReactionSystem, sparse::Bool, - jac_transport::Union{Nothing, Matrix{S}, SparseMatrixCSC{S, Int64}}, + jac_transport::Union{Nothing, Matrix{S}, SparseMatrixCSC{S, Int64}}, transport_rates::Vector{Pair{Int64, SparseMatrixCSC{S, Int64}}}) where {P, S} # Computes `LatticeTransportODEFunction` functor fields. heterogeneous_vert_p_idxs = make_heterogeneous_vert_p_idxs(ps, lrs) @@ -75,9 +75,9 @@ struct LatticeTransportODEFunction{P, Q, R, S, T} lrs) # Creates and returns the `LatticeTransportODEFunction` functor. - new{P, typeof(mtk_ps), typeof(p_setters), S, typeof(jac_transport)}(ofunc, + new{P, typeof(mtk_ps), typeof(p_setters), S, typeof(jac_transport)}(ofunc, num_verts(lrs), num_species(lrs), heterogeneous_vert_p_idxs, mtk_ps, p_setters, - transport_rates, t_rate_idx_types, leaving_rates, Catalyst.edge_iterator(lrs), + transport_rates, t_rate_idx_types, leaving_rates, Catalyst.edge_iterator(lrs), jac_transport, sparse, lrs) end end @@ -88,7 +88,7 @@ end function make_heterogeneous_vert_p_idxs(ps, lrs) p_dict = Dict(ps) return findall((p_dict[p] isa Vector) && (length(p_dict[p]) > 1) - for p in parameters(lrs)) + for p in parameters(lrs)) end # Creates the MTKParameters structure and `p_setters` vector (which are used to manage @@ -110,7 +110,7 @@ function make_t_types_and_leaving_rates(transport_rates, lrs) for (s_idx, tr_pair) in enumerate(transport_rates) for e in Catalyst.edge_iterator(lrs) # Updates the exit rate for species s_idx from vertex e.src. - leaving_rates[s_idx, e[1]] += get_transport_rate(tr_pair[2], e, + leaving_rates[s_idx, e[1]] += get_transport_rate(tr_pair[2], e, t_rate_idx_types[s_idx]) end end @@ -196,8 +196,8 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, # edge_ps values are sparse matrices. Here, index (i,j) is a parameter's value in the edge from vertex i to vertex j. # Uniform vertex/edge parameters store only a single value (a length 1 vector, or size 1x1 sparse matrix). # In the `ODEProblem` vert_ps and edge_ps are merged (but for building the ODEFunction, they are separate). - vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), - lrs) + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), + edge_parameters(lrs), lrs) # Creates the ODEFunction. ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse, name, include_zero_odes, @@ -347,7 +347,7 @@ function rebuild_lat_internals!(lt_ofun::LatticeTransportODEFunction, ps_new, # Recreates the new parameters on the requisite form. ps_new = [(length(p) == 1) ? p[1] : p for p in deepcopy(ps_new)] ps_new = [p => p_val for (p, p_val) in zip(parameters(lrs), deepcopy(ps_new))] - vert_ps, edge_ps = lattice_process_p(ps_new, vertex_parameters(lrs), + vert_ps, edge_ps = lattice_process_p(ps_new, vertex_parameters(lrs), edge_parameters(lrs), lrs) ps_new = [vert_ps; edge_ps] From caab94620a1ed32ac7221449934859abc329b3c9 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 9 Jul 2024 14:22:27 -0400 Subject: [PATCH 423/446] cache incidence mat --- src/network_analysis.jl | 50 +++++++++-------------------------------- 1 file changed, 11 insertions(+), 39 deletions(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 68b70e8dba..8e9e341e8a 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -277,8 +277,7 @@ incidencematgraph(sir) function incidencematgraph(rn::ReactionSystem) nps = get_networkproperties(rn) if Graphs.nv(nps.incidencegraph) == 0 - isempty(nps.incidencemat) && - error("Please call reactioncomplexes(rn) first to construct the incidence matrix.") + isempty(nps.incidencemat) && reactioncomplexes(rn) nps.incidencegraph = incidencematgraph(nps.incidencemat) end nps.incidencegraph @@ -329,17 +328,12 @@ Given the incidence graph of a reaction network, return a vector of the connected components of the graph (i.e. sub-groups of reaction complexes that are connected in the incidence graph). -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - For example, ```julia sir = @reaction_network SIR begin β, S + I --> 2I ν, I --> R end -complexes,incidencemat = reactioncomplexes(sir) linkageclasses(sir) ``` gives @@ -371,17 +365,12 @@ Here the deficiency, ``\delta``, of a network with ``n`` reaction complexes, \delta = n - \ell - s ``` -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - For example, ```julia sir = @reaction_network SIR begin β, S + I --> 2I ν, I --> R end -rcs,incidencemat = reactioncomplexes(sir) δ = deficiency(sir) ``` """ @@ -419,17 +408,12 @@ end Find subnetworks corresponding to each linkage class of the reaction network. -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - For example, ```julia sir = @reaction_network SIR begin β, S + I --> 2I ν, I --> R end -complexes,incidencemat = reactioncomplexes(sir) subnetworks(sir) ``` """ @@ -456,17 +440,12 @@ end Calculates the deficiency of each sub-reaction network within `network`. -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - For example, ```julia sir = @reaction_network SIR begin β, S + I --> 2I ν, I --> R end -rcs,incidencemat = reactioncomplexes(sir) linkage_deficiencies = linkagedeficiencies(sir) ``` """ @@ -487,17 +466,12 @@ end Given a reaction network, returns if the network is reversible or not. -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - For example, ```julia sir = @reaction_network SIR begin β, S + I --> 2I ν, I --> R end -rcs,incidencemat = reactioncomplexes(sir) isreversible(sir) ``` """ @@ -511,30 +485,28 @@ end Determine if the reaction network with the given subnetworks is weakly reversible or not. -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - For example, ```julia sir = @reaction_network SIR begin β, S + I --> 2I ν, I --> R end -rcs,incidencemat = reactioncomplexes(sir) subnets = subnetworks(rn) isweaklyreversible(rn, subnets) ``` """ function isweaklyreversible(rn::ReactionSystem, subnets) - im = get_networkproperties(rn).incidencemat - isempty(im) && - error("Error, please call reactioncomplexes(rn::ReactionSystem) to ensure the incidence matrix has been cached.") - sparseig = issparse(im) + nps = get_networkproperties(rn) + isempty(nps.incidencemat) && reactioncomplexes(rn) + imat = nps.incidencemat + sparseig = issparse(imat) + for subnet in subnets - nps = get_networkproperties(subnet) - isempty(nps.incidencemat) && reactioncomplexes(subnet; sparse = sparseig) + subnps = get_networkproperties(subnet) + isempty(subnps.incidencemat) && reactioncomplexes(subnet; sparse = sparseig) end + + # A network is weakly reversible if all of its subnetworks are strongly connected all(Graphs.is_strongly_connected ∘ incidencematgraph, subnets) end @@ -902,6 +874,6 @@ function satisfiesdeficiencyone(rn::ReactionSystem) complexes, D = reactioncomplexes(rn) lcs = linkageclasses(rn); tslcs = terminallinkageclasses(rn) - δ_l .<= 1 && sum(δ_l) = δ && length(lcs) == length(tslcs) + δ_l .<= 1 && (sum(δ_l) == δ) && length(lcs) == length(tslcs) end From a69357b8ecdf73097c2098521985a864bbb233ab Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 9 Jul 2024 14:25:15 -0400 Subject: [PATCH 424/446] reset to master --- src/Catalyst.jl | 2 +- src/network_analysis.jl | 19 ----- src/reactionsystem.jl | 3 - test/network_analysis/network_properties.jl | 79 --------------------- 4 files changed, 1 insertion(+), 102 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index cec9cca52f..f37b67a7f5 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -127,7 +127,7 @@ export @reaction_network, @network_component, @reaction, @species include("network_analysis.jl") export reactioncomplexmap, reactioncomplexes, incidencemat export complexstoichmat -export complexoutgoingmat, incidencematgraph, linkageclasses, stronglinkageclasses, terminallinkageclasses, deficiency, subnetworks +export complexoutgoingmat, incidencematgraph, linkageclasses, deficiency, subnetworks export linkagedeficiencies, isreversible, isweaklyreversible export conservationlaws, conservedquantities, conservedequations, conservationlaw_constants diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 8e9e341e8a..e9f5af1160 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -858,22 +858,3 @@ function treeweight(t::SimpleDiGraph, g::SimpleDiGraph, distmx::Matrix) end prod end - -### DEFICIENCY ONE - -""" - satisfiesdeficiencyone(rn::ReactionSystem) - - Check if a reaction network obeys the conditions of the deficiency one theorem, which ensures that there is only one equilibrium for every positive stoichiometric compatibility class. -""" - -function satisfiesdeficiencyone(rn::ReactionSystem) - δ = deficiency(rn) - δ_l = linkagedeficiencies(rn) - - complexes, D = reactioncomplexes(rn) - lcs = linkageclasses(rn); tslcs = terminallinkageclasses(rn) - - δ_l .<= 1 && (sum(δ_l) == δ) && length(lcs) == length(tslcs) -end - diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 33c5dab180..b504710eee 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -78,7 +78,6 @@ Base.@kwdef mutable struct NetworkProperties{I <: Integer, V <: BasicSymbolic{Re isempty::Bool = true netstoichmat::Union{Matrix{Int}, SparseMatrixCSC{Int, Int}} = Matrix{Int}(undef, 0, 0) conservationmat::Matrix{I} = Matrix{I}(undef, 0, 0) - cyclemat::Matrix{I} = Matrix{I}(undef, 0, 0) col_order::Vector{Int} = Int[] rank::Int = 0 nullity::Int = 0 @@ -94,8 +93,6 @@ Base.@kwdef mutable struct NetworkProperties{I <: Integer, V <: BasicSymbolic{Re complexoutgoingmat::Union{Matrix{Int}, SparseMatrixCSC{Int, Int}} = Matrix{Int}(undef, 0, 0) incidencegraph::Graphs.SimpleDiGraph{Int} = Graphs.DiGraph() linkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0) - stronglinkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0) - terminallinkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0) deficiency::Int = 0 end #! format: on diff --git a/test/network_analysis/network_properties.jl b/test/network_analysis/network_properties.jl index baee75e32a..db5b9f5893 100644 --- a/test/network_analysis/network_properties.jl +++ b/test/network_analysis/network_properties.jl @@ -325,82 +325,3 @@ let rates = Dict(zip(parameters(rn), k)) @test Catalyst.iscomplexbalanced(rn, rates) == true end - -### STRONG LINKAGE CLASS TESTS - -let - rn = @reaction_network begin - (k1, k2), A <--> B + C - k3, B + C --> D - k4, D --> E - (k5, k6), E <--> 2F - k7, 2F --> D - (k8, k9), D + E <--> G - end - - rcs, D = reactioncomplexes(rn) - slcs = stronglinkageclasses(rn) - tslcs = terminallinkageclasses(rn) - @test length(slcs) == 3 - @test length(tslcs) == 2 - @test issubset([[1,2], [3,4,5], [6,7]], slcs) - @test issubset([[3,4,5], [6,7]], tslcs) -end - -let - rn = @reaction_network begin - (k1, k2), A <--> B + C - k3, B + C --> D - k4, D --> E - (k5, k6), E <--> 2F - k7, 2F --> D - (k8, k9), D + E --> G - end - - rcs, D = reactioncomplexes(rn) - slcs = stronglinkageclasses(rn) - tslcs = terminallinkageclasses(rn) - @test length(slcs) == 4 - @test length(tslcs) == 2 - @test issubset([[1,2], [3,4,5], [6], [7]], slcs) - @test issubset([[3,4,5], [7]], tslcs) -end - -let - rn = @reaction_network begin - (k1, k2), A <--> B + C - (k3, k4), B + C <--> D - k5, D --> E - (k6, k7), E <--> 2F - k8, 2F --> D - (k9, k10), D + E <--> G - end - - rcs, D = reactioncomplexes(rn) - slcs = stronglinkageclasses(rn) - tslcs = terminallinkageclasses(rn) - @test length(slcs) == 2 - @test length(tslcs) == 2 - @test issubset([[1,2,3,4,5], [6,7]], slcs) - @test issubset([[1,2,3,4,5], [6,7]], tslcs) -end - -let - rn = @reaction_network begin - (k1, k2), A <--> 2B - k3, A --> C + D - (k4, k5), C + D <--> E - k6, 2B --> F - (k7, k8), F <--> 2G - (k9, k10), 2G <--> H - k11, H --> F - end - - rcs, D = reactioncomplexes(rn) - slcs = stronglinkageclasses(rn) - tslcs = terminallinkageclasses(rn) - @test length(slcs) == 3 - @test length(tslcs) == 2 - @test issubset([[1,2], [3,4], [5,6,7]], slcs) - @test issubset([[3,4], [5,6,7]], tslcs) -end From f1d1b9b21f3a83f0fccb7f87e7349e9503609b2f Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 9 Jul 2024 14:49:34 -0400 Subject: [PATCH 425/446] tests --- test/reactionsystem_core/reactionsystem.jl | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index 514dbc136f..538ce0f174 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -816,3 +816,42 @@ end let @test_nowarn Catalyst.reactionsystem_uptodate_check() end + +# Test that functions using the incidence matrix properly cache it +let + rn = @reaction_network begin + k1, A --> B + k2, B --> C + k3, C --> A + end + + nps = Catalyst.get_networkproperties(rn) + @test isempty(nps.incidencemat) == true + + img = incidencematgraph(rn) + @test size(nps.incidencemat) == (3,3) + + Catalyst.reset!(nps) + lcs = linkageclasses(rn) + @test size(nps.incidencemat) == (3,3) + + Catalyst.reset!(nps) + sns = subnetworks(rn) + @test size(nps.incidencemat) == (3,3) + + Catalyst.reset!(nps) + δ = deficiency(rn) + @test size(nps.incidencemat) == (3,3) + + Catalyst.reset!(nps) + δ_l = linkagedeficiencies(rn) + @test size(nps.incidencemat) == (3,3) + + Catalyst.reset!(nps) + rev = isreversible(rn) + @test size(nps.incidencemat) == (3,3) + + Catalyst.reset!(nps) + weakrev = isweaklyreversible(rn, sns) + @test size(nps.incidencemat) == (3,3) +end From 37a14d04af70fbda69218c0f481f52ec0316176e Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 9 Jul 2024 16:55:46 -0400 Subject: [PATCH 426/446] add solution interfacing --- src/Catalyst.jl | 4 +- .../lattice_jump_systems.jl | 14 +- .../lattice_solution_interfacing.jl | 121 +++++++++++ .../spatial_ODE_systems.jl | 51 ++++- test/runtests.jl | 1 + .../lattice_reaction_systems_jumps.jl | 24 ++- .../lattice_solution_interfacing.jl | 196 ++++++++++++++++++ 7 files changed, 402 insertions(+), 9 deletions(-) create mode 100644 src/spatial_reaction_systems/lattice_solution_interfacing.jl create mode 100644 test/spatial_modelling/lattice_solution_interfacing.jl diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 7c431a90c5..7d40cf695c 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -171,7 +171,7 @@ include("spatial_reaction_systems/spatial_reactions.jl") export TransportReaction, TransportReactions, @transport_reaction export isedgeparameter -# Lattice reaction systems +# Lattice reaction systems. include("spatial_reaction_systems/lattice_reaction_systems.jl") export LatticeReactionSystem export spatial_species, vertex_parameters, edge_parameters @@ -179,6 +179,8 @@ export CartesianGrid, CartesianGridReJ # (Implemented in JumpProcesses) export has_cartesian_lattice, has_masked_lattice, has_grid_lattice, has_graph_lattice, grid_dims, grid_size export make_edge_p_values, make_directed_edge_values +include("spatial_reaction_systems/lattice_solution_interfacing.jl") +export get_lrs_vals # Specific spatial problem types. include("spatial_reaction_systems/spatial_ODE_systems.jl") diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 826a48856f..1754aea0cf 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -122,7 +122,8 @@ end ### Extra ### -# Temporary. Awaiting implementation in SII, or proper implementation withinCatalyst (with more general functionality). +# Temporary. Awaiting implementation in SII, or proper implementation within Catalyst (with +# more general functionality). function int_map(map_in, sys) return [ModelingToolkit.variable_index(sys, pair[1]) => pair[2] for pair in map_in] end @@ -141,3 +142,14 @@ end # majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) # return ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) # end + + +### Problem & Integrator Rebuilding ### + +# Currently not implemented. +function rebuild_lat_internals!(dprob::DiscreteProblem) + error("Modification and/or rebuilding of `DiscreteProblem`s is currently not supported. Please create a new problem instead.") +end +function rebuild_lat_internals!(jprob::JumpProblem) + error("Modification and/or rebuilding of `JumpProblem`s is currently not supported. Please create a new problem instead.") +end diff --git a/src/spatial_reaction_systems/lattice_solution_interfacing.jl b/src/spatial_reaction_systems/lattice_solution_interfacing.jl new file mode 100644 index 0000000000..006b8e2217 --- /dev/null +++ b/src/spatial_reaction_systems/lattice_solution_interfacing.jl @@ -0,0 +1,121 @@ +### Rudimentary Interfacing Function ### +# A single function, `get_lrs_vals`, which contain all interfacing functionality. However, +# long-term it should be replaced with a sleeker interface. Ideally as MTK-wider support for +# lattice problems and solutions are introduced. + +""" + get_lrs_vals(sol, sp, lrs::LatticeReactionSystem; t = nothing) + +A function for retrieving the solution of a `LatticeReactionSystem`-based simulation on various +desired forms. Generally, for `LatticeReactionSystem`s, the values in `sol` is ordered in a +way which is not directly interpretable by the user. Furthermore, the normal Catalyst interface +for solutions (e.g. `sol[:X]`) does not work for these solutions. Hence this function is used instead. + +The output is a vector, which in each position contain sp's value (either at a time step of time, +depending on the input `t`). Its shape depends on the lattice (using a similar form as heterogeneous +initial conditions). I.e. for a NxM cartesian grid, the values are NxM matrices. For a masked grid, +the values are sparse matrices. For a graph lattice, the values are vectors (where the value in +the n'th position corresponds to sp's value in the n'th vertex). + +Arguments: +- `sol`: The solution from which we wish to retrieve some values. +- `sp`: The species which values we wish to retrieve. Can be either a symbol (e.g. `:X`) or a symbolic +variable (e.g. `X`). +- `lrs`: The `LatticeReactionSystem` which was simulated to generate the solution. +- `t = nothing`: If `nothing`, we simply returns the solution across all saved timesteps. If `t` +instead is a vector (or range of values), returns the solutions interpolated at these timepoints. + +Notes: +- The `get_lrs_vals` is not optimised for performance. However, it should still be quite performant, +but there might be some limitations if called a very large number of times. +- Long-term it is likely that this function gets replaced with a sleeker interface. + +Example: +```julia +using Catalyst, OrdinaryDiffEq + +# Prepare `LatticeReactionSystem`s. +rs = @reaction_network begin + (k1,k2), X1 <--> X2 +end +tr = @transport_reaction D X1 +lrs = LatticeReactionSystem(rs, [tr], CartesianGrid((2,2))) + +# Create problems. +u0 = [:X1 => 1, :X2 => 2] +tspan = (0.0, 10.0) +ps = [:k1 => 1, :k2 => 2.0, :D => 0.1] + +oprob = ODEProblem(lrs1, u0, tspan, ps) +osol = solve(oprob1, Tsit5()) +get_lrs_vals(osol, :X1, lrs) # Returns the value of X1 at each timestep. +get_lrs_vals(osol, :X1, lrs; t = 0.0:10.0) # Returns the value of X1 at times 0.0, 1.0, ..., 10.0 +``` +""" +function get_lrs_vals(sol, sp, lrs::LatticeReactionSystem; t = nothing) + # Figures out which species we wish to fetch information about. + (sp isa Symbol) && (sp = Catalyst._symbol_to_var(lrs, sp)) + sp_idx = findfirst(isequal(sp), species(lrs)) + sp_tot = length(species(lrs)) + + # Extracts the lattice and calls the next function. Masked grids (Array of Bools) are converted + # to sparse array using the same template size as we wish to shape the data to. + lattice = Catalyst.lattice(lrs) + if has_masked_lattice(lrs) + if grid_dims(lrs) == 3 + error("The `get_lrs_vals` function is not defined for systems based on 3d sparse arrays. Please raise an issue at the Catalyst GitHub site if this is something which would be useful to you.") + end + lattice = sparse(lattice) + end + get_lrs_vals(sol, lattice, t, sp_idx, sp_tot) +end + +# Function which handles the input in the case where `t` is `nothing` (i.e. return `sp`s value +# across all sample points). +function get_lrs_vals(sol, lattice, t::Nothing, sp_idx, sp_tot) + # ODE simulations contain, in each data point, all values in a single vector. Jump simulations + # instead in a matrix (NxM, where N is the number of species and M the number of vertices). We + # must consider each case separately. + if sol.prob isa ODEProblem + return [reshape_vals(vals[sp_idx:sp_tot:end], lattice) for vals in sol.u] + elseif sol.prob isa DiscreteProblem + return [reshape_vals(vals[sp_idx,:], lattice) for vals in sol.u] + else + error("Unknown type of solution provided to `get_lrs_vals`. Only ODE or Jump solutions are supported.") + end +end + +# Function which handles the input in the case where `t` is a range of values (i.e. return `sp`s +# value at all designated time points. +function get_lrs_vals(sol, lattice, t::AbstractVector{T}, sp_idx, sp_tot) where {T <: Number} + if (minimum(t) < sol.t[1]) || (maximum(t) > sol.t[end]) + error("The range of the t values provided for sampling, ($(minimum(t)),$(maximum(t))) is not fully within the range of the simulation time span ($(sol.t[1]),$(sol.t[end])).") + end + + # ODE simulations contain, in each data point, all values in a single vector. Jump simulations + # instead in a matrix (NxM, where N is the number of species and M the number of vertices). We + # must consider each case separately. + if sol.prob isa ODEProblem + return [reshape_vals(sol(ti)[sp_idx:sp_tot:end], lattice) for ti in t] + elseif sol.prob isa DiscreteProblem + return [reshape_vals(sol(ti)[sp_idx,:], lattice) for ti in t] + else + error("Unknown type of solution provided to `get_lrs_vals`. Only ODE or Jump solutions are supported.") + end +end + +# Functions which in each sample point reshapes the vector of values to the correct form (depending +# on the type of lattice used). +function reshape_vals(vals, lattice::CartesianGridRej{N, T}) where {N,T} + return reshape(vals, lattice.dims...) +end +function reshape_vals(vals, lattice::AbstractSparseArray{Bool, Int64, 1}) + return SparseVector(lattice.n, lattice.nzind, vals) +end +function reshape_vals(vals, lattice::AbstractSparseArray{Bool, Int64, 2}) + return SparseMatrixCSC(lattice.m, lattice.n, lattice.colptr, lattice.rowval, vals) +end +function reshape_vals(vals, lattice::DiGraph) + return vals +end + diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 9879e27221..49893ad20e 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -243,10 +243,10 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{R, V jac_transport, transport_rates) J = (jac ? f : nothing) - # Extracts the `Symbol` form for species and parameters. Creates and returns the `ODEFunction`. - syms = MT.getname.(species(lrs)) - paramsyms = MT.getname.(parameters(lrs)) - return ODEFunction(f; jac = J, jac_prototype, syms, paramsyms) + # Extracts the `Symbol` form for parameters (but not species). Creates and returns the `ODEFunction`. + paramsyms = [MT.getname(p) for p in parameters(lrs)] + sys = SciMLBase.SymbolCache([], paramsyms, []) + return ODEFunction(f; jac = J, jac_prototype, sys) end # Builds a jacobian prototype. @@ -325,7 +325,48 @@ end ### Functor Updating Functionality ### -# Function for rebuilding a `LatticeReactionSystem` `ODEProblem` after it has been updated. +""" + rebuild_lat_internals!(sciml_struct) + +Rebuilds the internal functions for simulating a LatticeReactionSystem. WHenever a problem or +integrator have had its parameter values updated, thus function should be called for the update to +be taken into account. For ODE simulations, `rebuild_lat_internals!` needs only to be called when +- An edge parameter have been updated. +- When a parameter with spatially homogeneous values have been given spatially heterogeneous values +(or vice versa). + +Arguments: +- `sciml_struct`: The problem (e.g. an `ODEProblem`) or an integrator which we wish to rebuild. + +Notes: +- Currently does not work for `DiscreteProblem`s, `JumpProblem`s, or their integrators. +- The function is not build with performance in mind, so avoid calling it multiple times in +performance-critical applications. + +Example: +```julia +# Creates an initial `ODEProblem` +rs = @reaction_network begin + (k1,k2), X1 <--> X2 +end +tr = @transport_reaction D X1 +grid = CartesianGrid((2,2)) +lrs = LatticeReactionSystem(rs, [tr], grid) + +u0 = [:X1 => 2, :X2 => [5 6; 7 8]] +tspan = (0.0, 10.0) +ps = [:k1 => 1.5, :k2 => [1.0 1.5; 2.0 3.5], :D => 0.1] + +oprob = ODEProblem(lrs, u0, tspan, ps) + +# Updates parameter values. +oprob.ps[:ks] = [2.0 2.5; 3.0 4.5] +oprob.ps[:D] = 0.05 + +# Rebuilds `ODEProblem` to make changes have an effect. +rebuild_lat_internals!(oprob) +``` +""" function rebuild_lat_internals!(oprob::ODEProblem) rebuild_lat_internals!(oprob.f.f, oprob.p, oprob.f.f.lrs) end diff --git a/test/runtests.jl b/test/runtests.jl index 6479ae2234..4197706e0d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -72,5 +72,6 @@ using SafeTestsets, Test @time @safetestset "Spatial Lattice Variants" begin include("spatial_modelling/lattice_reaction_systems_lattice_types.jl") end @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_ODEs.jl") end @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_modelling/lattice_reaction_systems_jumps.jl") end + @time @safetestset "Jump Solution Interfacing" begin include("spatial_modelling/lattice_solution_interfacing.jl") end end # @time diff --git a/test/spatial_modelling/lattice_reaction_systems_jumps.jl b/test/spatial_modelling/lattice_reaction_systems_jumps.jl index fe32701b11..f2db71569f 100644 --- a/test/spatial_modelling/lattice_reaction_systems_jumps.jl +++ b/test/spatial_modelling/lattice_reaction_systems_jumps.jl @@ -1,8 +1,7 @@ ### Preparations ### # Fetch packages. -using JumpProcesses -using Random, Statistics, SparseArrays, Test +using JumpProcesses, Statistics, SparseArrays, Test # Fetch test networks. include("../spatial_test_networks.jl") @@ -204,6 +203,27 @@ end ### JumpProblem & Integrator Interfacing ### +# Currently not supported, check that corresponding functions yields errors. +let + # Prepare `LatticeReactionSystem`. + rs = @reaction_network begin + (k1,k2), X1 <--> X2 + end + tr = @transport_reaction D X1 + grid = CartesianGrid((2,2)) + lrs = LatticeReactionSystem(rs, [tr], grid) + + # Create problems. + u0 = [:X1 => 2, :X2 => [5 6; 7 8]] + tspan = (0.0, 10.0) + ps = [:k1 => 1.5, :k2 => [1.0 1.5; 2.0 3.5], :D => 0.1] + dprob = DiscreteProblem(lrs, u0, tspan, ps) + jprob = JumpProblem(lrs, dprob, NSM()) + + # Checks that rebuilding errors. + @test_throws Exception rebuild_lat_internals!(dprob) + @test_throws Exception rebuild_lat_internals!(jprob) +end ### Other Tests ### diff --git a/test/spatial_modelling/lattice_solution_interfacing.jl b/test/spatial_modelling/lattice_solution_interfacing.jl new file mode 100644 index 0000000000..8bead8ce4a --- /dev/null +++ b/test/spatial_modelling/lattice_solution_interfacing.jl @@ -0,0 +1,196 @@ +### Preparations ### + +# Fetch packages. +using Catalyst, Graphs, JumpProcesses, OrdinaryDiffEq, SparseArrays, Test + +### `get_lrs_vals` Tests ### + +# Basic test. For simulations without effect of system, check that solution correspond to known +# initial condition throughout solution. +# Checks using both `t` sampling` and normal time step sampling. +# Checks for both ODE and jump simulations. +# Checks for all lattice types. +let + # Prepare `LatticeReactionSystem`s. + rs = @reaction_network begin + (k1,k2), X1 <--> X2 + end + tr = @transport_reaction D X1 + lrs1 = LatticeReactionSystem(rs, [tr], CartesianGrid((2,))) + lrs2 = LatticeReactionSystem(rs, [tr], CartesianGrid((2,3))) + lrs3 = LatticeReactionSystem(rs, [tr], CartesianGrid((2,3,2))) + lrs4 = LatticeReactionSystem(rs, [tr], [true, true, false, true]) + lrs5 = LatticeReactionSystem(rs, [tr], [true false; true true]) + lrs6 = LatticeReactionSystem(rs, [tr], cycle_graph(4)) + + # Create problem inputs. + u0_1 = Dict([:X1 => 0, :X2 => [1, 2]]) + u0_2 = Dict([:X1 => 0, :X2 => [1 2 3; 4 5 6]]) + u0_3 = Dict([:X1 => 0, :X2 => fill(1, 2, 3, 2)]) + u0_4 = Dict([:X1 => 0, :X2 => sparse([1, 2, 0, 3])]) + u0_5 = Dict([:X1 => 0, :X2 => sparse([1 0; 2 3])]) + u0_6 = Dict([:X1 => 0, :X2 => [1, 2, 3, 4]]) + tspan = (0.0, 1.0) + ps = [:k1 => 0.0, :k2 => 0.0, :D => 0.0] + + # Loops through a lattice cases and check that they are correct. + for (u0,lrs) in zip([u0_1, u0_2, u0_3, u0_4, u0_5, u0_6], [lrs1, lrs2, lrs3, lrs4, lrs5, lrs6]) + # Simulates ODE version and checks `get_lrs_vals` on its solution. + oprob = ODEProblem(lrs, u0, tspan, ps) + osol = solve(oprob, Tsit5(), saveat = 0.5) + @test get_lrs_vals(osol, :X1, lrs) == get_lrs_vals(osol, :X1, lrs; t = 0.0:0.5:1.0) + @test all(all(val == Float64(u0[:X1]) for val in vals) for vals in get_lrs_vals(osol, :X1, lrs)) + @test get_lrs_vals(osol, :X2, lrs) == get_lrs_vals(osol, :X2, lrs; t = 0.0:0.5:1.0) == fill(u0[:X2], 3) + + # Simulates jump version and checks `get_lrs_vals` on its solution. + dprob = DiscreteProblem(lrs, u0, tspan, ps) + jprob = JumpProblem(lrs, dprob, NSM()) + jsol = solve(jprob, SSAStepper(), saveat = 0.5) + @test get_lrs_vals(jsol, :X1, lrs) == get_lrs_vals(jsol, :X1, lrs; t = 0.0:0.5:1.0) + @test all(all(val == Float64(u0[:X1]) for val in vals) for vals in get_lrs_vals(jsol, :X1, lrs)) + @test get_lrs_vals(jsol, :X2, lrs) == get_lrs_vals(jsol, :X2, lrs; t = 0.0:0.5:1.0) == fill(u0[:X2], 3) + end +end + +# Checks on simulations where the system changes in time. +# Checks that solution have correct initial condition and end point (steady state). +# Checks that solution is monotonously increasing/decreasing (it should be for this problem). +let + # Prepare `LatticeReactionSystem`s. + rs = @reaction_network begin + (p,d), 0 <--> X + end + tr = @transport_reaction D X + lrs = LatticeReactionSystem(rs, [tr], CartesianGrid((2,))) + + # Prepares a corresponding ODEProblem. + u0 = [:X => [1.0, 3.0]] + tspan = (0.0, 50.0) + ps = [:p => 2.0, :d => 1.0, :D => 0.01] + oprob = ODEProblem(lrs, u0, tspan, ps) + + # Simulates the ODE. Checks that the start/end points are correct. + # Check that the first vertex is monotonously increasing in values, and that the second one is + # monotonously decreasing. The non evenly spaced `saveat` is so that non-monotonicity is + # not produced due to numeric errors. + saveat = [0.0, 1.0, 5.0, 10.0, 50.0] + sol = solve(oprob, Vern7(); abstol = 1e-8, reltol = 1e-8) + vals = get_lrs_vals(sol, :X, lrs) + @test vals[1] == [1.0, 3.0] + @test vals[end] ≈ [2.0, 2.0] + for i = 1:(length(saveat) - 1) + @test vals[i][1] < vals[i + 1][1] + @test vals[i][2] > vals[i + 1][2] + end +end + +# Checks interpolation when sampling at time point. Check that value at `t` is inbetween the +# sample points. Does so by checking that in simulation which is monotonously decreasing/increasing. +let + # Prepare `LatticeReactionSystem`s. + rs = @reaction_network begin + (p,d), 0 <--> X + end + tr = @transport_reaction D X + lrs = LatticeReactionSystem(rs, [tr], CartesianGrid((2,))) + + # Solved a corresponding ODEProblem. + u0 = [:X => [1.0, 3.0]] + tspan = (0.0, 1.0) + ps = [:p => 2.0, :d => 1.0, :D => 0.0] + oprob = ODEProblem(lrs, u0, tspan, ps) + + # Solves and check the interpolation of t. + sol = solve(oprob, Tsit5(); saveat = 1.0) + t5_vals = get_lrs_vals(sol, :X, lrs; t = [0.5])[1] + @test sol.u[1][1] < t5_vals[1] < sol.u[2][1] + @test sol.u[1][2] > t5_vals[2] > sol.u[2][2] +end + +### Error Tests ### + +# Checks that attempting to sample `t` outside tspan range yields an error. +let + # Prepare `LatticeReactionSystem`s. + rs = @reaction_network begin + (p,d), 0 <--> X + end + tr = @transport_reaction D X + lrs = LatticeReactionSystem(rs, [tr], CartesianGrid((2,))) + + # Solved a corresponding ODEProblem. + u0 = [:X => 1.0] + tspan = (1.0, 2.0) + ps = [:p => 2.0, :d => 1.0, :D => 1.0] + oprob = ODEProblem(lrs, u0, tspan, ps) + + # Solves and check the interpolation of t. + sol = solve(oprob, Tsit5(); saveat = 1.0) + @test_throws Exception get_lrs_vals(sol, :X, lrs; t = [0.0]) + @test_throws Exception get_lrs_vals(sol, :X, lrs; t = [3.0]) +end + +# Checks that attempting to sample `t` outside tspan range yields an error. +let + # Prepare `LatticeReactionSystem`s. + rs = @reaction_network begin + (p,d), 0 <--> X + end + tr = @transport_reaction D X + lrs = LatticeReactionSystem(rs, [tr], CartesianGrid((2,))) + + # Solved a corresponding ODEProblem. + u0 = [:X => 1.0] + tspan = (1.0, 2.0) + ps = [:p => 2.0, :d => 1.0, :D => 1.0] + oprob = ODEProblem(lrs, u0, tspan, ps) + + # Solves and check the interpolation of t. + sol = solve(oprob, Tsit5(); saveat = 1.0) + @test_throws Exception get_lrs_vals(sol, :X, lrs; t = [0.0]) + @test_throws Exception get_lrs_vals(sol, :X, lrs; t = [3.0]) +end + +# Checks that applying `get_lrs_vals` to a 3d masked lattice yields an error. +let + # Prepare `LatticeReactionSystem`s. + rs = @reaction_network begin + (p,d), 0 <--> X + end + tr = @transport_reaction D X + lrs = LatticeReactionSystem(rs, [tr], rand([false, true], 2, 3, 4)) + + # Solved a corresponding ODEProblem. + u0 = [:X => 1.0] + tspan = (1.0, 2.0) + ps = [:p => 2.0, :d => 1.0, :D => 1.0] + oprob = ODEProblem(lrs, u0, tspan, ps) + + # Solves and check the interpolation of t. + sol = solve(oprob, Tsit5(); saveat = 1.0) + @test_throws Exception get_lrs_vals(sol, :X, lrs) +end + +### Other Tests ### + +# Checks that `get_lrs_vals` works for all types of symbols. +let + t = default_t() + @species X(t) + @parameters d + @named rs = ReactionSystem([Reaction(d, [X], [])], t) + rs = complete(rs) + tr = @transport_reaction D X + lrs = LatticeReactionSystem(rs, [tr], CartesianGrid(2,)) + + # Solved a corresponding ODEProblem. + u0 = [:X => 1.0] + tspan = (0.0, 1.0) + ps = [:d => 1.0, :D => 0.1] + oprob = ODEProblem(lrs, u0, tspan, ps) + + # Solves and check the interpolation of t. + sol = solve(oprob, Tsit5(); saveat = 1.0) + @test get_lrs_vals(sol, X, lrs) == get_lrs_vals(sol, rs.X, lrs) == get_lrs_vals(sol, :X, lrs) + @test get_lrs_vals(sol, X, lrs; t = 0.0:0.5:1.0) == get_lrs_vals(sol, rs.X, lrs; t = 0.0:0.5:1.0) == get_lrs_vals(sol, :X, lrs; t = 0.0:0.5:1.0) +end \ No newline at end of file From e19d67d462d7542fae1441e395234ba15c821d44 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 9 Jul 2024 17:13:08 -0400 Subject: [PATCH 427/446] format, improve writing --- .../lattice_jump_systems.jl | 7 ++--- .../lattice_solution_interfacing.jl | 28 +++++++++---------- .../spatial_ODE_systems.jl | 18 ++++++------ .../spatial_reactions.jl | 6 ++-- src/spatial_reaction_systems/utility.jl | 8 +++--- .../lattice_reaction_systems.jl | 4 +-- .../lattice_reaction_systems_ODEs.jl | 10 +++---- .../lattice_reaction_systems_jumps.jl | 6 ++-- .../lattice_reaction_systems_lattice_types.jl | 2 +- .../lattice_solution_interfacing.jl | 10 +++---- test/spatial_modelling/spatial_reactions.jl | 2 +- 11 files changed, 50 insertions(+), 51 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_jump_systems.jl b/src/spatial_reaction_systems/lattice_jump_systems.jl index 1754aea0cf..ebbe367cf1 100644 --- a/src/spatial_reaction_systems/lattice_jump_systems.jl +++ b/src/spatial_reaction_systems/lattice_jump_systems.jl @@ -37,7 +37,7 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator # Computes hopping constants and mass action jumps (requires some internal juggling). # Currently, the resulting JumpProblem does not depend on parameters (no way to incorporate these). - # Hence the parameters of this one does not actually matter. If at some point JumpProcess can + # Hence the parameters of this one do not actually matter. If at some point JumpProcess can # handle parameters this can be updated and improved. # The non-spatial DiscreteProblem have a u0 matrix with entries for all combinations of species and vertexes. hopping_constants = make_hopping_constants(dprob, lrs) @@ -54,7 +54,7 @@ end # Creates the hopping constants from a discrete problem and a lattice reaction system. function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) # Creates the all_diff_rates vector, containing for each species, its transport rate across all edges. - # If transport rate is uniform for one species, the vector have a single element, else one for each edge. + # If the transport rate is uniform for one species, the vector has a single element, else one for each edge. spatial_rates_dict = Dict(compute_all_transport_rates(Dict(dprob.p), lrs)) all_diff_rates = [haskey(spatial_rates_dict, s) ? spatial_rates_dict[s] : [0.0] for s in species(lrs)] @@ -77,7 +77,7 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end # Creates a SpatialMassActionJump struct from a (spatial) DiscreteProblem and a LatticeReactionSystem. -# Could implementation a version which, if all reaction's rates are uniform, returns a MassActionJump. +# Could implement a version which, if all reactions' rates are uniform, returns a MassActionJump. # Not sure if there is any form of performance improvement from that though. Likely not the case. function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) # Creates a vector, storing which reactions have spatial components. @@ -143,7 +143,6 @@ end # return ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) # end - ### Problem & Integrator Rebuilding ### # Currently not implemented. diff --git a/src/spatial_reaction_systems/lattice_solution_interfacing.jl b/src/spatial_reaction_systems/lattice_solution_interfacing.jl index 006b8e2217..3a286a8a02 100644 --- a/src/spatial_reaction_systems/lattice_solution_interfacing.jl +++ b/src/spatial_reaction_systems/lattice_solution_interfacing.jl @@ -1,7 +1,7 @@ ### Rudimentary Interfacing Function ### -# A single function, `get_lrs_vals`, which contain all interfacing functionality. However, -# long-term it should be replaced with a sleeker interface. Ideally as MTK-wider support for -# lattice problems and solutions are introduced. +# A single function, `get_lrs_vals`, which contains all interfacing functionality. However, +# long-term it should be replaced with a sleeker interface. Ideally as MTK-wide support for +# lattice problems and solutions is introduced. """ get_lrs_vals(sol, sp, lrs::LatticeReactionSystem; t = nothing) @@ -11,7 +11,7 @@ desired forms. Generally, for `LatticeReactionSystem`s, the values in `sol` is o way which is not directly interpretable by the user. Furthermore, the normal Catalyst interface for solutions (e.g. `sol[:X]`) does not work for these solutions. Hence this function is used instead. -The output is a vector, which in each position contain sp's value (either at a time step of time, +The output is a vector, which in each position contains sp's value (either at a time step of time, depending on the input `t`). Its shape depends on the lattice (using a similar form as heterogeneous initial conditions). I.e. for a NxM cartesian grid, the values are NxM matrices. For a masked grid, the values are sparse matrices. For a graph lattice, the values are vectors (where the value in @@ -22,8 +22,8 @@ Arguments: - `sp`: The species which values we wish to retrieve. Can be either a symbol (e.g. `:X`) or a symbolic variable (e.g. `X`). - `lrs`: The `LatticeReactionSystem` which was simulated to generate the solution. -- `t = nothing`: If `nothing`, we simply returns the solution across all saved timesteps. If `t` -instead is a vector (or range of values), returns the solutions interpolated at these timepoints. +- `t = nothing`: If `nothing`, we simply return the solution across all saved time steps. If `t` +instead is a vector (or range of values), returns the solutions interpolated at these time points. Notes: - The `get_lrs_vals` is not optimised for performance. However, it should still be quite performant, @@ -48,7 +48,7 @@ ps = [:k1 => 1, :k2 => 2.0, :D => 0.1] oprob = ODEProblem(lrs1, u0, tspan, ps) osol = solve(oprob1, Tsit5()) -get_lrs_vals(osol, :X1, lrs) # Returns the value of X1 at each timestep. +get_lrs_vals(osol, :X1, lrs) # Returns the value of X1 at each time step. get_lrs_vals(osol, :X1, lrs; t = 0.0:10.0) # Returns the value of X1 at times 0.0, 1.0, ..., 10.0 ``` """ @@ -61,7 +61,7 @@ function get_lrs_vals(sol, sp, lrs::LatticeReactionSystem; t = nothing) # Extracts the lattice and calls the next function. Masked grids (Array of Bools) are converted # to sparse array using the same template size as we wish to shape the data to. lattice = Catalyst.lattice(lrs) - if has_masked_lattice(lrs) + if has_masked_lattice(lrs) if grid_dims(lrs) == 3 error("The `get_lrs_vals` function is not defined for systems based on 3d sparse arrays. Please raise an issue at the Catalyst GitHub site if this is something which would be useful to you.") end @@ -79,7 +79,7 @@ function get_lrs_vals(sol, lattice, t::Nothing, sp_idx, sp_tot) if sol.prob isa ODEProblem return [reshape_vals(vals[sp_idx:sp_tot:end], lattice) for vals in sol.u] elseif sol.prob isa DiscreteProblem - return [reshape_vals(vals[sp_idx,:], lattice) for vals in sol.u] + return [reshape_vals(vals[sp_idx, :], lattice) for vals in sol.u] else error("Unknown type of solution provided to `get_lrs_vals`. Only ODE or Jump solutions are supported.") end @@ -87,7 +87,8 @@ end # Function which handles the input in the case where `t` is a range of values (i.e. return `sp`s # value at all designated time points. -function get_lrs_vals(sol, lattice, t::AbstractVector{T}, sp_idx, sp_tot) where {T <: Number} +function get_lrs_vals( + sol, lattice, t::AbstractVector{T}, sp_idx, sp_tot) where {T <: Number} if (minimum(t) < sol.t[1]) || (maximum(t) > sol.t[end]) error("The range of the t values provided for sampling, ($(minimum(t)),$(maximum(t))) is not fully within the range of the simulation time span ($(sol.t[1]),$(sol.t[end])).") end @@ -98,15 +99,15 @@ function get_lrs_vals(sol, lattice, t::AbstractVector{T}, sp_idx, sp_tot) where if sol.prob isa ODEProblem return [reshape_vals(sol(ti)[sp_idx:sp_tot:end], lattice) for ti in t] elseif sol.prob isa DiscreteProblem - return [reshape_vals(sol(ti)[sp_idx,:], lattice) for ti in t] + return [reshape_vals(sol(ti)[sp_idx, :], lattice) for ti in t] else error("Unknown type of solution provided to `get_lrs_vals`. Only ODE or Jump solutions are supported.") end end -# Functions which in each sample point reshapes the vector of values to the correct form (depending +# Functions which in each sample point reshape the vector of values to the correct form (depending # on the type of lattice used). -function reshape_vals(vals, lattice::CartesianGridRej{N, T}) where {N,T} +function reshape_vals(vals, lattice::CartesianGridRej{N, T}) where {N, T} return reshape(vals, lattice.dims...) end function reshape_vals(vals, lattice::AbstractSparseArray{Bool, Int64, 1}) @@ -118,4 +119,3 @@ end function reshape_vals(vals, lattice::DiGraph) return vals end - diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 49893ad20e..3dced1e573 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -26,7 +26,7 @@ struct LatticeTransportODEFunction{P, Q, R, S, T} mtk_ps::Q """ Stores a SymbolicIndexingInterface `setp` function for each heterogeneous vertex parameter (i.e. - vertex parameter which value is not identical across the lattice). The `setp` function at index + vertex parameter whose value is not identical across the lattice). The `setp` function at index i of `p_setters` corresponds to the parameter in index i of `heterogeneous_vert_p_idxs`. """ p_setters::R @@ -82,7 +82,7 @@ struct LatticeTransportODEFunction{P, Q, R, S, T} end end -# `LatticeTransportODEFunction` helper functions (re used by rebuild function later on). +# `LatticeTransportODEFunction` helper functions (re-used by rebuild function later on). # Creates a vector with the heterogeneous vertex parameters' indexes in the full parameter vector. function make_heterogeneous_vert_p_idxs(ps, lrs) @@ -226,7 +226,7 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{R, V ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) - # Depending on Jacobian and sparsity options, computes the Jacobian transport matrix and prototype. + # Depending on Jacobian and sparsity options, compute the Jacobian transport matrix and prototype. if !sparse && !jac jac_transport = nothing jac_prototype = nothing @@ -249,7 +249,7 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Pair{R, V return ODEFunction(f; jac = J, jac_prototype, sys) end -# Builds a jacobian prototype. +# Builds a Jacobian prototype. # If requested, populate it with the constant values of the Jacobian's transportation part. function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, transport_rates::Vector{Pair{Int64, SparseMatrixCSC{T, Int64}}}, @@ -328,11 +328,11 @@ end """ rebuild_lat_internals!(sciml_struct) -Rebuilds the internal functions for simulating a LatticeReactionSystem. WHenever a problem or -integrator have had its parameter values updated, thus function should be called for the update to +Rebuilds the internal functions for simulating a LatticeReactionSystem. Wenever a problem or +integrator has had its parameter values updated, this function should be called for the update to be taken into account. For ODE simulations, `rebuild_lat_internals!` needs only to be called when -- An edge parameter have been updated. -- When a parameter with spatially homogeneous values have been given spatially heterogeneous values +- An edge parameter has been updated. +- When a parameter with spatially homogeneous values has been given spatially heterogeneous values (or vice versa). Arguments: @@ -340,7 +340,7 @@ Arguments: Notes: - Currently does not work for `DiscreteProblem`s, `JumpProblem`s, or their integrators. -- The function is not build with performance in mind, so avoid calling it multiple times in +- The function is not built with performance in mind, so avoid calling it multiple times in performance-critical applications. Example: diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 43ae1fc807..204d94992a 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -99,15 +99,15 @@ function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReacti # Checks that the species does not exist in the system with different metadata. if any(isequal(tr.species, s) && !isequivalent(tr.species, s) for s in species(rs)) - error("A transport reaction used a species, $(tr.species), with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") + error("A transport reaction used a species, $(tr.species), with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and use it during transport reaction creation.") end # No `for` loop, just weird formatting by the formatter. if any(isequal(rs_p, tr_p) && !isequivalent(rs_p, tr_p) for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)) - error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") + error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and use it during transport reaction creation.") end - # Checks that no edge parameter occur among rates of non-spatial reactions. + # Checks that no edge parameter occurs among rates of non-spatial reactions. # No `for` loop, just weird formatting by the formatter. if any(!isempty(intersect(Symbolics.get_variables(r.rate), edge_parameters)) for r in reactions(rs)) diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 80a490f912..e00f753d8c 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -269,7 +269,7 @@ function get_transport_rate(transport_rate::SparseMatrixCSC{T, Int64}, return t_rate_idx_types ? transport_rate[1, 1] : transport_rate[edge[1], edge[2]] end -# For a `LatticeTransportODEFunction`, updates its stored parameters (in `mtk_ps`) so that they +# For a `LatticeTransportODEFunction`, update its stored parameters (in `mtk_ps`) so that they # the heterogeneous parameters' values correspond to the values in the specified vertex. function update_mtk_ps!(lt_ofun::LatticeTransportODEFunction, all_ps::Vector{T}, vert::Int64) where {T} @@ -278,7 +278,7 @@ function update_mtk_ps!(lt_ofun::LatticeTransportODEFunction, all_ps::Vector{T}, end end -# For an expression, computes its values using the provided state and parameter vectors. +# For an expression, compute its values using the provided state and parameter vectors. # The expression is assumed to be valid in vertexes (and can have vertex parameter and state components). # If at least one component is non-uniform, output is a vector of length equal to the number of vertexes. # If all components are uniform, the output is a length one vector. @@ -289,7 +289,7 @@ function compute_vertex_value(exp, lrs::LatticeReactionSystem; u = [], ps = []) throw(ArgumentError("An edge parameter was encountered in expressions: $exp. Here, only vertex-based components are expected.")) end - # Creates a Function that computes the expressions value for a parameter set. + # Creates a Function that computes the expression value for a parameter set. exp_func = drop_expr(@RuntimeGeneratedFunction(build_function(exp, relevant_syms...))) # Creates a dictionary with the value(s) for all edge parameters. @@ -305,7 +305,7 @@ end ### System Property Checks ### -# For a Symbolic expression, and a parameter set, checks if any relevant parameters have a +# For a Symbolic expression, and a parameter set, check if any relevant parameters have a # spatial component. Filters out any parameters that are edge parameters. function has_spatial_vertex_component(exp, ps) relevant_syms = Symbolics.get_variables(exp) diff --git a/test/spatial_modelling/lattice_reaction_systems.jl b/test/spatial_modelling/lattice_reaction_systems.jl index 52b0ac916b..f36a0a5d4c 100644 --- a/test/spatial_modelling/lattice_reaction_systems.jl +++ b/test/spatial_modelling/lattice_reaction_systems.jl @@ -298,7 +298,7 @@ end ### Tests Edge Value Computation Helper Functions ### -# Checks that computes the correct values across various types of grids. +# Checks that we compute the correct values across various types of grids. let # Prepares the model and the function that determines the edge values. rn = @reaction_network begin @@ -323,7 +323,7 @@ let end end -# Checks that all species ends up in the correct place in in a pure flow system (checking various dimensions). +# Checks that all species end up in the correct place in a pure flow system (checking various dimensions). let # Prepares a system with a single species which is transported only. rn = @reaction_network begin diff --git a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl index 92d905e169..26da7ba3fc 100644 --- a/test/spatial_modelling/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/lattice_reaction_systems_ODEs.jl @@ -14,7 +14,7 @@ rng = StableRNG(12345) # Sets defaults t = default_t() -### Tests Simulations Don't Error ### +### Tests Simulations Do Not Error ### let for grid in [small_1d_cartesian_grid, small_1d_masked_grid, small_1d_graph_grid] for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] @@ -186,7 +186,7 @@ let @test all(isapprox.(ss, solve(oprob_sparse_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; rtol = 0.0001)) end -# Compares Catalyst-generated to hand written one for the brusselator for a line of cells. +# Compares Catalyst-generated to hand-written one for the Brusselator for a line of cells. let function spatial_brusselator_f(du, u, p, t) # Non-spatial @@ -534,7 +534,7 @@ end # Checks that the `rebuild_lat_internals!` function is correctly applied to an ODEProblem. let - # Creates a brusselator `LatticeReactionSystem`. + # Creates a Brusselator `LatticeReactionSystem`. lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_2, very_small_2d_cartesian_grid) # Checks for all combinations of Jacobian and sparsity. @@ -572,7 +572,7 @@ end # Checks that the `rebuild_lat_internals!` function is correctly applied to an integrator. # Does through by applying it within a callback, and compare to simulations without callback. -# To keep test faster, only checks for `jac = sparse = true`. +# To keep test faster, only check for `jac = sparse = true` only. let # Prepares problem inputs. lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_2, very_small_2d_cartesian_grid) @@ -624,7 +624,7 @@ end ### Tests Special Cases ### -# Create network using either graphs or di-graphs. +# Create networks using either graphs or di-graphs. let lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) diff --git a/test/spatial_modelling/lattice_reaction_systems_jumps.jl b/test/spatial_modelling/lattice_reaction_systems_jumps.jl index f2db71569f..576e543f40 100644 --- a/test/spatial_modelling/lattice_reaction_systems_jumps.jl +++ b/test/spatial_modelling/lattice_reaction_systems_jumps.jl @@ -88,7 +88,7 @@ end ### SpatialMassActionJump Testing ### -# Checks that the correct structure;s is produced. +# Checks that the correct structures are produced. let # Network for reference: # A, ∅ → X @@ -113,7 +113,7 @@ let # @test isequal(to_int(getfield.(reactions(reactionsystem(lrs)), :netstoich)), jprob.massaction_jump.net_stoch) # @test isequal(to_int(Pair.(getfield.(reactions(reactionsystem(lrs)), :substrates),getfield.(reactions(reactionsystem(lrs)), :substoich))), jprob.massaction_jump.net_stoch) - # Checks that problem can be simulated. + # Checks that problems can be simulated. @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end @@ -203,7 +203,7 @@ end ### JumpProblem & Integrator Interfacing ### -# Currently not supported, check that corresponding functions yields errors. +# Currently not supported, check that corresponding functions yield errors. let # Prepare `LatticeReactionSystem`. rs = @reaction_network begin diff --git a/test/spatial_modelling/lattice_reaction_systems_lattice_types.jl b/test/spatial_modelling/lattice_reaction_systems_lattice_types.jl index bde1bb14f8..dbdc233b89 100644 --- a/test/spatial_modelling/lattice_reaction_systems_lattice_types.jl +++ b/test/spatial_modelling/lattice_reaction_systems_lattice_types.jl @@ -9,7 +9,7 @@ include("../spatial_test_networks.jl") ### Run Tests ### -# Test errors when attempting to create networks with dimension > 3. +# Test errors when attempting to create networks with dimensions > 3. let @test_throws Exception LatticeReactionSystem(brusselator_system, brusselator_srs_1, CartesianGrid((5, 5, 5, 5))) @test_throws Exception LatticeReactionSystem(brusselator_system, brusselator_srs_1, fill(true, 5, 5, 5, 5)) diff --git a/test/spatial_modelling/lattice_solution_interfacing.jl b/test/spatial_modelling/lattice_solution_interfacing.jl index 8bead8ce4a..238700a51f 100644 --- a/test/spatial_modelling/lattice_solution_interfacing.jl +++ b/test/spatial_modelling/lattice_solution_interfacing.jl @@ -5,8 +5,8 @@ using Catalyst, Graphs, JumpProcesses, OrdinaryDiffEq, SparseArrays, Test ### `get_lrs_vals` Tests ### -# Basic test. For simulations without effect of system, check that solution correspond to known -# initial condition throughout solution. +# Basic test. For simulations without change in system, check that the solution corresponds to known +# initial condition throughout the solution. # Checks using both `t` sampling` and normal time step sampling. # Checks for both ODE and jump simulations. # Checks for all lattice types. @@ -33,7 +33,7 @@ let tspan = (0.0, 1.0) ps = [:k1 => 0.0, :k2 => 0.0, :D => 0.0] - # Loops through a lattice cases and check that they are correct. + # Loops through all lattice cases and check that they are correct. for (u0,lrs) in zip([u0_1, u0_2, u0_3, u0_4, u0_5, u0_6], [lrs1, lrs2, lrs3, lrs4, lrs5, lrs6]) # Simulates ODE version and checks `get_lrs_vals` on its solution. oprob = ODEProblem(lrs, u0, tspan, ps) @@ -53,7 +53,7 @@ let end # Checks on simulations where the system changes in time. -# Checks that solution have correct initial condition and end point (steady state). +# Checks that a solution has correct initial condition and end point (steady state). # Checks that solution is monotonously increasing/decreasing (it should be for this problem). let # Prepare `LatticeReactionSystem`s. @@ -84,7 +84,7 @@ let end end -# Checks interpolation when sampling at time point. Check that value at `t` is inbetween the +# Checks interpolation when sampling at time point. Check that values at `t` is in between the # sample points. Does so by checking that in simulation which is monotonously decreasing/increasing. let # Prepare `LatticeReactionSystem`s. diff --git a/test/spatial_modelling/spatial_reactions.jl b/test/spatial_modelling/spatial_reactions.jl index 395c264444..e764c619a3 100644 --- a/test/spatial_modelling/spatial_reactions.jl +++ b/test/spatial_modelling/spatial_reactions.jl @@ -120,7 +120,7 @@ let # @test isequal(tr_3, tr_macro_3) end -# Checks that the `hash` functions works for `TransportReaction`s. +# Checks that the `hash` functions work for `TransportReaction`s. let tr1 = @transport_reaction D1 X tr2 = @transport_reaction D1 X From 2c3d3b8ab2a57c909d5eca744dc938630ce4695b Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Wed, 10 Jul 2024 12:29:44 -0400 Subject: [PATCH 428/446] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a5cee020a8..5199e2ab2c 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ large-scale simulations through auto-vectorization and parallelism. Symbolic `ReactionSystem`s can be used to generate ModelingToolkit-based models, allowing the easy simulation and parameter estimation of mass action ODE models, Chemical Langevin SDE models, stochastic chemical kinetics jump process models, and more. -Generated models can be used with solvers throughout the broader -[SciML](https://sciml.ai) ecosystem, including higher-level SciML packages (e.g. +Generated models can be used with solvers throughout the broader Julia and +[SciML](https://sciml.ai) ecosystems, including higher-level SciML packages (e.g. for sensitivity analysis, parameter estimation, machine learning applications, etc). @@ -205,4 +205,4 @@ could cite our work: pages = {1-19}, number = {10}, } -``` \ No newline at end of file +``` From b8b47ef7bdbe65939aef76bdeb97ba442e1f6a20 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Wed, 10 Jul 2024 16:40:37 -0400 Subject: [PATCH 429/446] doc tweaks --- README.md | 46 +++++----- docs/src/assets/Project.toml | 72 --------------- docs/src/index.md | 53 ++++++----- .../introduction_to_catalyst.md | 89 +++++++++++-------- .../model_creation/compositional_modeling.md | 8 +- docs/src/model_creation/dsl_basics.md | 24 ++--- 6 files changed, 118 insertions(+), 174 deletions(-) delete mode 100644 docs/src/assets/Project.toml diff --git a/README.md b/README.md index 5199e2ab2c..85cc3bcb36 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ documentation](https://docs.sciml.ai/Catalyst/stable/). The [in-development documentation](https://docs.sciml.ai/Catalyst/dev/) describes unreleased features in the current master branch. -An overview of the package, its features, and comparative benchmarking (as of version 13) can also +An overview of the package, its features, and comparative benchmarking (as of version 13) can also be found in its corresponding research paper, [Catalyst: Fast and flexible modeling of reaction networks](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1011530). ## Features @@ -71,7 +71,7 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model - [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](https://docs.sciml.ai/Catalyst/stable/model_creation/model_visualisation/#visualisation_graphs) (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). - Model steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using [SteadyStateDiffEq.jl](https://github.com/SciML/SteadyStateDiffEq.jl), or by numerically solving steady-state nonlinear equations using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). - [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute bifurcation diagrams](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/bifurcation_diagrams/) of model steady states (including finding periodic orbits). -- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute model [basins of attraction](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_basins_of_attraction) and [Lyapunov spectrums](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_lyapunov_exponents). +- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute model [basins of attraction](@ref dynamical_systems_basins_of_attraction), [Lyapunov spectrums](@ref dynamical_systems_lyapunov_exponents), and other dynamical system properties. - [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used to [perform structural identifiability analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/structural_identifiability/). - [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](https://sebapersson.github.io/PEtab.jl/stable/Define_in_julia/). - [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform [global sensitivity analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/global_sensitivity_analysis/) of model behaviors. @@ -88,7 +88,7 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model ## Illustrative example #### Deterministic ODE simulation of Michaelis-Menten enzyme kinetics -Here we show a simple example where a model is created using the Catalyst DSL, and then simulated as +Here we show a simple example where a model is created using the Catalyst DSL, and then simulated as an ordinary differential equation. ```julia @@ -129,14 +129,15 @@ plot(jump_sol; lw = 2) ![Jump simulation](docs/src/assets/readme_jump_plot.svg) -## Elaborate example -In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we -instead show how various Catalyst features can compose to create a much more advanced model. Our -model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth -factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ -($G \to Gᴾ$) is promoted by sunlight (modeled as the cyclic sinusoid $kₐ (sin(t) + 1)$), which -phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($Vₘ$) -it undergoes cell division. First, we declare our model: +## More elaborate example +In the above example, we used basic Catalyst workflows to simulate a simple +model. Here we instead show how various Catalyst features can compose to create +a much more advanced model. Our model describes how the volume of a cell ($V$) +is affected by a growth factor ($G$). The growth factor only promotes growth +while in its phosphorylated form ($G^P$). The phosphorylation of $G$ ($G \to G^P$) +is promoted by sunlight (modeled as the cyclic sinusoid $k_a (\sin(t) + 1)$), +which phosphorylates the growth factor (producing $G^P$). When the cell reaches a +critical volume ($V_m$) it undergoes cell division. First, we declare our model: ```julia using Catalyst cell_model = @reaction_network begin @@ -151,22 +152,22 @@ cell_model = @reaction_network begin kᵢ/V, Gᴾ --> G end ``` -We now study the system as a Chemical Langevin Dynamics SDE model, which can be generated as follows +We now study the system as a Chemical Langevin Dynamics SDE model, which can be generated as follows ```julia u0 = [:V => 25.0, :G => 50.0, :Gᴾ => 0.0] tspan = (0.0, 20.0) ps = [:Vₘ => 50.0, :g => 0.3, :kₚ => 100.0, :kᵢ => 60.0] sprob = SDEProblem(cell_model, u0, tspan, ps) ``` -This produces the following equations: +This problem encodes the following stochastic differential equation model: ```math \begin{align*} -dG(t) &= - \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) + \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt - \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) + \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) \\ -dGᴾ(t) &= \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) - \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt + \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) - \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) \\ -dV(t) &= \left(g \cdot Gᴾ(t)\right) dt +dG(t) &= - \left( \frac{k_p(\sin(t)+1)}{V(t)} G(t) + \frac{k_i}{V(t)} G^P(t) \right) dt - \sqrt{\frac{k_p (\sin(t)+1)}{V(t)} G(t)} \, dW_1(t) + \sqrt{\frac{k_i}{V(t)} G^P(t)} \, dW_2(t) \\ +dG^P(t) &= \left( \frac{k_p(\sin(t)+1)}{V(t)} G(t) - \frac{k_i}{V(t)} G^P(t) \right) dt + \sqrt{\frac{k_p (\sin(t)+1)}{V(t)} G(t)} \, dW_1(t) - \sqrt{\frac{k_i}{V(t)} G^P(t)} \, dW_2(t) \\ +dV(t) &= \left(g \, G^P(t)\right) dt \end{align*} ``` -where the $dW_1(t)$ and $dW_2(t)$ terms represent independent Brownian Motions, encoding the noise added by the Chemical Langevin Equation. Finally, we can simulate and plot the results. +where the $dW_1(t)$ and $dW_2(t)$ terms represent independent Brownian Motions, encoding the noise added by the Chemical Langevin Equation. Finally, we can simulate and plot the results. ```julia using StochasticDiffEq, Plots sol = solve(sprob, EM(); dt = 0.05) @@ -181,15 +182,14 @@ Some features we used here: - The model simulation was [plotted using Plots.jl](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_plotting/). ## Getting help or getting involved -Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/), -the [Julia Slack](https://julialang.slack.com) channels \#sciml-bridged and \#sciml-sysbio, and the -[Julia Zulip sciml-bridged channel](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged). +Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/) and +the [Julia Slack](https://julialang.slack.com) channels \#sciml-bridged and \#sciml-sysbio. For bugs or feature requests, [open an issue](https://github.com/SciML/Catalyst.jl/issues). ## Supporting and citing Catalyst.jl -The software in this ecosystem was developed as part of academic research. If you would like to help -support it, please star the repository as such metrics may help us secure funding in the future. If -you use Catalyst as part of your research, teaching, or other activities, we would be grateful if you +The software in this ecosystem was developed as part of academic research. If you would like to help +support it, please star the repository as such metrics may help us secure funding in the future. If +you use Catalyst as part of your research, teaching, or other activities, we would be grateful if you could cite our work: ``` @article{CatalystPLOSCompBio2023, diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml deleted file mode 100644 index 7d86a95a90..0000000000 --- a/docs/src/assets/Project.toml +++ /dev/null @@ -1,72 +0,0 @@ -[deps] -BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -BifurcationKit = "0f109fa4-8a5d-4b75-95aa-f515264e7665" -CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" -Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83" -DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -DiffEqParamEstim = "1130ab10-4a5a-5621-a13d-e4788d82bd4c" -Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" -Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -DynamicalSystems = "61744808-ddfa-5f27-97ff-6e42cc95d634" -GlobalSensitivity = "af5da776-676b-467e-8baf-acd8249e4f0f" -HomotopyContinuation = "f213a82b-91d6-5c5d-acf7-10f1c761b327" -IncompleteLU = "40713840-3770-5561-ab4c-a76e7d0d7895" -JumpProcesses = "ccbc3e58-028d-4f4c-8cd5-9ae44345cda5" -Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" -LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae" -Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" -ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" -NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" -Optim = "429524aa-4258-5aef-a3af-852621145aeb" -Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba" -OptimizationBBO = "3e6eede4-6085-4f62-9a71-46d9bc1eb92b" -OptimizationNLopt = "4e6fcdb7-1186-4e1f-a706-475e75c168bb" -OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e" -OptimizationOptimisers = "42dfb2eb-d2b4-4451-abcd-913932933ac1" -OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" -Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -QuasiMonteCarlo = "8a4e6c94-4038-4cdc-81c3-7e6ffdb2a71b" -SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" -SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1" -SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" -StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" -SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f" -StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" -StructuralIdentifiability = "220ca800-aa68-49bb-acd8-6037fa93a544" -Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" - -[compat] -BenchmarkTools = "1.5" -BifurcationKit = "0.3.4" -CairoMakie = "0.12" -Catalyst = "14" -DataFrames = "1.6" -DiffEqParamEstim = "2.2" -Distributions = "0.25" -Documenter = "1.4.1" -DynamicalSystems = "3.3" -GlobalSensitivity = "2.6" -HomotopyContinuation = "2.9" -IncompleteLU = "0.2" -JumpProcesses = "9.11" -Latexify = "0.16" -LinearSolve = "2.30" -ModelingToolkit = "9.16.0" -NonlinearSolve = "3.12" -Optim = "1.9" -Optimization = "3.25" -OptimizationBBO = "0.2.1" -OptimizationNLopt = "0.2.1" -OptimizationOptimJL = "0.3.1" -OptimizationOptimisers = "0.2.1" -OrdinaryDiffEq = "6.80.1" -Plots = "1.40" -QuasiMonteCarlo = "0.3" -SciMLBase = "2.39" -SciMLSensitivity = "7.60" -SpecialFunctions = "2.4" -StaticArrays = "1.9" -SteadyStateDiffEq = "2.2" -StochasticDiffEq = "6.65" -StructuralIdentifiability = "0.5.8" -Symbolics = "5.30.1" diff --git a/docs/src/index.md b/docs/src/index.md index dda0e4ccc2..a8ea0531da 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -2,8 +2,7 @@ Catalyst.jl is a symbolic modeling package for analysis and high-performance simulation of chemical reaction networks. Catalyst defines symbolic -[`ReactionSystem`](@ref)s, -which can be created programmatically or easily +[`ReactionSystem`](@ref)s, which can be created programmatically or easily specified using Catalyst's domain-specific language (DSL). Leveraging [ModelingToolkit.jl](https://github.com/SciML/ModelingToolkit.jl) and [Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl), Catalyst enables @@ -11,8 +10,8 @@ large-scale simulations through auto-vectorization and parallelism. Symbolic `ReactionSystem`s can be used to generate ModelingToolkit-based models, allowing the easy simulation and parameter estimation of mass action ODE models, Chemical Langevin SDE models, stochastic chemical kinetics jump process models, and more. -Generated models can be used with solvers throughout the broader -[SciML](https://sciml.ai) ecosystem, including higher-level SciML packages (e.g. +Generated models can be used with solvers throughout the broader Julia and +[SciML](https://sciml.ai) ecosystems, including higher-level SciML packages (e.g. for sensitivity analysis, parameter estimation, machine learning applications, etc). @@ -41,12 +40,12 @@ etc). - [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](@ref visualisation_graphs) (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). - Model steady states can be [computed through homotopy continuation](@ref homotopy_continuation) using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by [forward ODE simulations](@ref steady_state_solving_simulation) using [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl, or by [numerically solving steady-state nonlinear equations](@ref steady_state_solving_nonlinear) using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). - [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute bifurcation diagrams](@ref bifurcation_diagrams) of model steady states (including finding periodic orbits). -- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute model [basins of attraction](@ref dynamical_systems_basins_of_attraction) and [Lyapunov spectrums](@ref dynamical_systems_lyapunov_exponents). +- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute model [basins of attraction](@ref dynamical_systems_basins_of_attraction), [Lyapunov spectrums](@ref dynamical_systems_lyapunov_exponents), and other dynamical system properties. - [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used to [perform structural identifiability analysis](@ref structural_identifiability). - [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](https://sebapersson.github.io/PEtab.jl/stable/Define_in_julia/). - [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform [global sensitivity analysis](@ref global_sensitivity_analysis) of model behaviors. - [SciMLSensitivity.jl](https://github.com/SciML/SciMLSensitivity.jl) can be used to compute local sensitivities of functions containing forward model simulations. - + #### [Features of packages built upon Catalyst](@id doc_index_features_other_packages) - Catalyst [`ReactionSystem`](@ref)s can be [imported from SBML files](@ref model_file_import_export_sbml) via [SBMLImporter.jl](https://github.com/SciML/SBMLImporter.jl) and [SBMLToolkit.jl](https://github.com/SciML/SBMLToolkit.jl), and [from BioNetGen .net files](@ref model_file_import_export_sbml_rni_net) and various stoichiometric matrix network representations using [ReactionNetworkImporters.jl](https://github.com/SciML/ReactionNetworkImporters.jl). - [MomentClosure.jl](https://github.com/augustinas1/MomentClosure.jl) allows generation of symbolic ModelingToolkit `ODESystem`s that represent moment closure approximations to moments of the Chemical Master Equation, from reaction networks defined in Catalyst. @@ -99,12 +98,12 @@ Pkg.add("Plots") ``` is also needed. -A more throughout guide for setting up Catalyst and installing Julia packages can be found [here](@ref catalyst_for_new_julia_users_packages). +A more thorough guide for setting up Catalyst and installing Julia packages can be found [here](@ref catalyst_for_new_julia_users_packages). ## [Illustrative example](@id doc_index_example) #### [Deterministic ODE simulation of Michaelis-Menten enzyme kinetics](@id doc_index_example_ode) -Here we show a simple example where a model is created using the Catalyst DSL, and then simulated as +Here we show a simple example where a model is created using the Catalyst DSL, and then simulated as an ordinary differential equation. ```@example home_simple_example @@ -143,14 +142,15 @@ jump_sol = solve(jprob, SSAStepper(); seed = 1234) # hide plot(jump_sol; lw = 2) ``` -## [Elaborate example](@id doc_index_elaborate_example) -In the above example, we used basic Catalyst-based workflows to simulate a simple model. Here we -instead show how various Catalyst features can compose to create a much more advanced model. Our -model describes how the volume of a cell ($V$) is affected by a growth factor ($G$). The growth -factor only promotes growth while in its phosphorylated form ($Gᴾ$). The phosphorylation of $G$ -($G \to Gᴾ$) is promoted by sunlight (modeled as the cyclic sinusoid $kₐ (sin(t) + 1)$), which -phosphorylates the growth factor (producing $Gᴾ$). When the cell reaches a critical volume ($Vₘ$) -it undergoes cell division. First, we declare our model: +## [More elaborate example](@id doc_index_elaborate_example) +In the above example, we used basic Catalyst workflows to simulate a simple +model. Here we instead show how various Catalyst features can compose to create +a much more advanced model. Our model describes how the volume of a cell ($V$) +is affected by a growth factor ($G$). The growth factor only promotes growth +while in its phosphorylated form ($G^P$). The phosphorylation of $G$ ($G \to G^P$) +is promoted by sunlight (modeled as the cyclic sinusoid $k_a (\sin(t) + 1)$), +which phosphorylates the growth factor (producing $G^P$). When the cell reaches a +critical volume ($V_m$) it undergoes cell division. First, we declare our model: ```@example home_elaborate_example using Catalyst cell_model = @reaction_network begin @@ -165,19 +165,19 @@ cell_model = @reaction_network begin kᵢ/V, Gᴾ --> G end ``` -We now study the system as a Chemical Langevin Dynamics SDE model, which can be generated as follows +We now study the system as a Chemical Langevin Dynamics SDE model, which can be generated as follows ```@example home_elaborate_example u0 = [:V => 25.0, :G => 50.0, :Gᴾ => 0.0] tspan = (0.0, 20.0) ps = [:Vₘ => 50.0, :g => 0.3, :kₚ => 100.0, :kᵢ => 60.0] sprob = SDEProblem(cell_model, u0, tspan, ps) ``` -This produces the following equations: +This problem encodes the following stochastic differential equation model: ```math \begin{align*} -dG(t) &= - \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) + \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt - \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) + \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) \\ -dGᴾ(t) &= \left( \frac{kₚ*(sin(t)+1)}{V(t)} G(t) - \frac{kᵢ}{V(t)} Gᴾ(t) \right) dt + \sqrt{\frac{kₚ*(sin(t)+1)}{V(t)} G(t)} dW_1(t) - \sqrt{\frac{kᵢ}{V(t)} Gᴾ(t)} dW_2(t) \\ -dV(t) &= \left(g \cdot Gᴾ(t)\right) dt +dG(t) &= - \left( \frac{k_p(\sin(t)+1)}{V(t)} G(t) + \frac{k_i}{V(t)} G^P(t) \right) dt - \sqrt{\frac{k_p (\sin(t)+1)}{V(t)} G(t)} \, dW_1(t) + \sqrt{\frac{k_i}{V(t)} G^P(t)} \, dW_2(t) \\ +dG^P(t) &= \left( \frac{k_p(\sin(t)+1)}{V(t)} G(t) - \frac{k_i}{V(t)} G^P(t) \right) dt + \sqrt{\frac{k_p (\sin(t)+1)}{V(t)} G(t)} \, dW_1(t) - \sqrt{\frac{k_i}{V(t)} G^P(t)} \, dW_2(t) \\ +dV(t) &= \left(g \, G^P(t)\right) dt \end{align*} ``` where the $dW_1(t)$ and $dW_2(t)$ terms represent independent Brownian Motions, encoding the noise added by the Chemical Langevin Equation. Finally, we can simulate and plot the results. @@ -189,15 +189,14 @@ plot(sol; xguide = "Time (au)", lw = 2) ``` ## [Getting Help](@id doc_index_help) -Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/), -the [Julia Slack](https://julialang.slack.com) channels \#sciml-bridged and \#sciml-sysbio, and the -[Julia Zulip sciml-bridged channel](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged). +Catalyst developers are active on the [Julia Discourse](https://discourse.julialang.org/) and +the [Julia Slack](https://julialang.slack.com) channels \#sciml-bridged and \#sciml-sysbio. For bugs or feature requests, [open an issue](https://github.com/SciML/Catalyst.jl/issues). ## [Supporting and Citing Catalyst.jl](@id doc_index_citation) -The software in this ecosystem was developed as part of academic research. If you would like to help -support it, please star the repository as such metrics may help us secure funding in the future. If -you use Catalyst as part of your research, teaching, or other activities, we would be grateful if you +The software in this ecosystem was developed as part of academic research. If you would like to help +support it, please star the repository as such metrics may help us secure funding in the future. If +you use Catalyst as part of your research, teaching, or other activities, we would be grateful if you could cite our work: ``` @article{CatalystPLOSCompBio2023, diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index 848fac71a9..d46d5a9397 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -1,29 +1,42 @@ # [Introduction to Catalyst](@id introduction_to_catalyst) In this tutorial we provide an introduction to using Catalyst to specify chemical reaction networks, and then to solve ODE, jump, and SDE models -generated from them[1]. At the end we show what mathematical rate laws and +generated from them [1]. At the end we show what mathematical rate laws and transition rate functions (i.e. intensities or propensities) are generated by Catalyst for ODE, SDE and jump process models. -Let's start by using the Catalyst [`@reaction_network`](@ref) macro -to specify a simple chemical reaction network: the well-known repressilator. - -We first import the basic packages we'll need: +We begin by installing Catalyst and any needed packages into a new environment. +This step can be skipped if you have already installed them in your current, +active environment: +```julia +using Pkg + +# name of the environment +Pkg.activate("catalyst_introduction") + +# packages we will use in this tutorial +Pkg.add("Catalyst") +Pkg.add("OrdinaryDiffEq") +Pkg.add("Plots") +Pkg.add("Latexify") +Pkg.add("JumpProcesses") +Pkg.add("StochasticDiffEq") +``` +We next load the basic packages we'll need for our first example: ```@example tut1 -# If not already installed, first hit "]" within a Julia REPL. Then type: -# add Catalyst OrdinaryDiffEq Plots Latexify - using Catalyst, OrdinaryDiffEq, Plots, Latexify ``` -We now construct the reaction network. The basic types of arrows and predefined -rate laws one can use are discussed in detail within the tutorial, [The Reaction -DSL](@ref dsl_description). Here, we use a mix of first order, zero order, and repressive Hill -function rate laws. Note, $\varnothing$ corresponds to the empty state, and is -used for zeroth order production and first order degradation reactions: +Let's start by using the Catalyst [`@reaction_network`](@ref) macro to specify a +simple chemical reaction network: the well-known repressilator. We first construct +the reaction network. The basic types of arrows and predefined rate laws one can +use are discussed in detail within the tutorial, [The Reaction DSL](@ref +dsl_description). Here, we use a mix of first order, zero order, and repressive +Hill function rate laws. Note, $\varnothing$ corresponds to the empty state, and +is used for zeroth order production and first order degradation reactions: ```@example tut1 -repressilator = @reaction_network Repressilator begin +rn = @reaction_network Repressilator begin hillr(P₃,α,K,n), ∅ --> m₁ hillr(P₁,α,K,n), ∅ --> m₂ hillr(P₂,α,K,n), ∅ --> m₃ @@ -37,37 +50,37 @@ repressilator = @reaction_network Repressilator begin μ, P₂ --> ∅ μ, P₃ --> ∅ end -show(stdout, MIME"text/plain"(), repressilator) # hide +show(stdout, MIME"text/plain"(), rn) # hide ``` showing that we've created a new network model named `Repressilator` with the listed chemical species and unknowns. [`@reaction_network`](@ref) returns a -[`ReactionSystem`](@ref), which we saved in the `repressilator` variable. It can +[`ReactionSystem`](@ref), which we saved in the `rn` variable. It can be converted to a variety of other mathematical models represented as `ModelingToolkit.AbstractSystem`s, or analyzed in various ways using the [Catalyst.jl API](@ref api). For example, to see the chemical species, parameters, and reactions we can use ```@example tut1 -species(repressilator) +species(rn) ``` ```@example tut1 -parameters(repressilator) +parameters(rn) ``` and ```@example tut1 -reactions(repressilator) +reactions(rn) ``` We can also use Latexify to see the corresponding reactions in Latex, which shows what the `hillr` terms mathematically correspond to ```julia -latexify(repressilator) +latexify(rn) ``` ```@example tut1 -repressilator #hide +rn #hide ``` Assuming [Graphviz](https://graphviz.org/) is installed and command line accessible, within a Jupyter notebook we can also graph the reaction network by ```julia -g = Graph(repressilator) +g = Graph(rn) ``` giving @@ -96,8 +109,8 @@ Let's now use our `ReactionSystem` to generate and solve a corresponding mass action ODE model. We first convert the system to a `ModelingToolkit.ODESystem` by ```@example tut1 -repressilator = complete(repressilator) -odesys = convert(ODESystem, repressilator) +rn = complete(rn) +odesys = convert(ODESystem, rn) ``` (Here Latexify is used automatically to display `odesys` in Latex within Markdown documents or notebook environments like Pluto.jl.) @@ -117,12 +130,10 @@ nothing # hide Alternatively, we can use ModelingToolkit-based symbolic species variables to specify these mappings like ```@example tut1 -t = default_t() -@parameters α K n δ γ β μ -@species m₁(t) m₂(t) m₃(t) P₁(t) P₂(t) P₃(t) -psymmap = (α => .5, K => 40, n => 2, δ => log(2)/120, - γ => 5e-3, β => 20*log(2)/120, μ => log(2)/60) -u₀symmap = [m₁ => 0., m₂ => 0., m₃ => 0., P₁ => 20., P₂ => 0., P₃ => 0.] +psymmap = (rn.α => .5, rn.K => 40, rn.n => 2, rn.δ => log(2)/120, + rn.γ => 5e-3, rn.β => 20*log(2)/120, rn.μ => log(2)/60) +u₀symmap = [rn.m₁ => 0., rn.m₂ => 0., rn.m₃ => 0., rn.P₁ => 20., + rn.P₂ => 0., rn.P₃ => 0.] nothing # hide ``` Knowing these mappings we can set up the `ODEProblem` we want to solve: @@ -132,11 +143,11 @@ Knowing these mappings we can set up the `ODEProblem` we want to solve: tspan = (0., 10000.) # create the ODEProblem we want to solve -oprob = ODEProblem(repressilator, u₀map, tspan, pmap) +oprob = ODEProblem(rn, u₀map, tspan, pmap) nothing # hide ``` -By passing `repressilator` directly to the `ODEProblem`, Catalyst has to -(internally) call `convert(ODESystem, repressilator)` again to generate the +By passing `rn` directly to the `ODEProblem`, Catalyst has to +(internally) call `convert(ODESystem, rn)` again to generate the symbolic ODEs. We could instead pass `odesys` directly like ```@example tut1 odesys = complete(odesys) @@ -149,13 +160,13 @@ underlying problem. !!! note When passing `odesys` to `ODEProblem` we needed to use the symbolic variable-based parameter mappings, `u₀symmap` and `psymmap`, while when - directly passing `repressilator` we could use either those or the + directly passing `rn` we could use either those or the `Symbol`-based mappings, `u₀map` and `pmap`. `Symbol`-based mappings can always be converted to `symbolic` mappings using [`symmap_to_varmap`](@ref). !!! note - Above we have used `repressilator = complete(repressilator)` and `odesys = complete(odesys)` to mark these systems as *complete*, indicating to Catalyst and ModelingToolkit that these models are finalized. This must be done before any system is given as input to a `convert` call or some problem type. `ReactionSystem` models created through the `@reaction_network` DSL (which is introduced elsewhere, and primarily used throughout these documentation) are always marked as complete when generated. Hence `complete` does not need to be called on them. Symbolically generated `ReactionSystem`s, `ReactionSystem`s generated via the `@network_component` macro, and any ModelingToolkit system generated by `convert` always needs to be manually marked as `complete` as we do for `odesys` above. An expanded description on *completeness* can be found [here](@ref completeness_note). + Above we have used `rn = complete(rn)` and `odesys = complete(odesys)` to mark these systems as *complete*, indicating to Catalyst and ModelingToolkit that these models are finalized. This must be done before any system is given as input to a `convert` call or some problem type. `ReactionSystem` models created through the `@reaction_network` DSL (which is introduced elsewhere, and primarily used throughout these documentation) are always marked as complete when generated. Hence `complete` does not need to be called on them. Symbolically generated `ReactionSystem`s, `ReactionSystem`s generated via the `@network_component` macro, and any ModelingToolkit system generated by `convert` always needs to be manually marked as `complete` as we do for `odesys` above. An expanded description on *completeness* can be found [here](@ref completeness_note). At this point we are all set to solve the ODEs. We can now use any ODE solver from within the @@ -164,7 +175,7 @@ package. We'll use the recommended default explicit solver, `Tsit5()`, and then plot the solutions: ```@example tut1 -sol = solve(oprob, Tsit5(), saveat=10.) +sol = solve(oprob, Tsit5(), saveat=10.0) plot(sol) ``` We see the well-known oscillatory behavior of the repressilator! For more on the @@ -188,15 +199,15 @@ using JumpProcesses u₀map = [:m₁ => 0, :m₂ => 0, :m₃ => 0, :P₁ => 20, :P₂ => 0, :P₃ => 0] # next we create a discrete problem to encode that our species are integer-valued: -dprob = DiscreteProblem(repressilator, u₀map, tspan, pmap) +dprob = DiscreteProblem(rn, u₀map, tspan, pmap) # now, we create a JumpProblem, and specify Gillespie's Direct Method as the solver: -jprob = JumpProblem(repressilator, dprob, Direct()) +jprob = JumpProblem(rn, dprob, Direct()) # now, let's solve and plot the jump process: sol = solve(jprob, SSAStepper()) plot(sol) -plot(sol, plotdensity = 1000, fmt = :png) # hide +plot(sol, density = 10000, fmt = :png) # hide ``` We see that oscillations remain, but become much noisier. Note, in constructing diff --git a/docs/src/model_creation/compositional_modeling.md b/docs/src/model_creation/compositional_modeling.md index 1f0bfac0cd..ca0807299c 100644 --- a/docs/src/model_creation/compositional_modeling.md +++ b/docs/src/model_creation/compositional_modeling.md @@ -27,7 +27,8 @@ We can test whether a system is complete using the `ModelingToolkit.iscomplete` ```@example ex0 ModelingToolkit.iscomplete(degradation_component) ``` -To mark a system as complete, after which is should be considered as representing a finalized model, use the `complete` function +To mark a system as complete, after which it should be considered as +representing a finalized model, use the `complete` function ```@example ex0 degradation_component_complete = complete(degradation_component) ModelingToolkit.iscomplete(degradation_component_complete) @@ -35,8 +36,9 @@ ModelingToolkit.iscomplete(degradation_component_complete) ## Compositional modeling tooling Catalyst supports two ModelingToolkit interfaces for composing multiple -[`ReactionSystem`](@ref)s together into a full model. The first mechanism for -extending a system is the `extend` command +[`ReactionSystem`](@ref)s together into a full model. The first mechanism allows +for extending an existing system by merging in a second system via the `extend` +command ```@example ex1 using Catalyst basern = @network_component rn1 begin diff --git a/docs/src/model_creation/dsl_basics.md b/docs/src/model_creation/dsl_basics.md index 83c6d59172..409e3b1f95 100644 --- a/docs/src/model_creation/dsl_basics.md +++ b/docs/src/model_creation/dsl_basics.md @@ -9,7 +9,7 @@ using Catalyst ``` ### [Quick-start summary](@id dsl_description_quick_start) -The DSL is initiated through the `@reaction_network` macro, which is followed by one line for each reaction. Each reaction consists of a *rate*, followed lists first of the substrates and next of the products. E.g. a [Michaelis-Menten enzyme kinetics system](@ref basic_CRN_library_mm) can be written as +The DSL is initiated through the `@reaction_network` macro, which is followed by one line for each reaction. Each reaction consists of a *rate*, followed lists first of the substrates and next of the products. E.g. a [Michaelis-Menten enzyme kinetics system](@ref basic_CRN_library_mm) can be written as ```@example dsl_basics_intro rn = @reaction_network begin (kB,kD), S + E <--> SE @@ -93,8 +93,8 @@ Reactants whose stoichiometries are not defined are assumed to have stoichiometr Stoichiometries can be combined with `()` to define them for multiple reactants. Here, the following (mock) model declares the same reaction twice, both with and without this notation: ```@example dsl_basics rn6 = @reaction_network begin - k, 2X + 3(Y + 2Z) --> 5(V + W) - k, 2X + 3Y + 6Z --> 5V + 5W + k, 2X + 3(Y + 2Z) --> 5(V + W) + k, 2X + 3Y + 6Z --> 5V + 5W end ``` @@ -186,7 +186,7 @@ rn12 = @reaction_network begin ((pX, pY, pZ),d), (0, Y0, Z0) <--> (X, Y, Z1+Z2) end ``` -However, like for the above model, bundling reactions too zealously can reduce (rather than improve) a model's readability. +However, like for the above model, bundling reactions too zealously can reduce (rather than improve) a model's readability. ## [Non-constant reaction rates](@id dsl_description_nonconstant_rates) So far we have assumed that all reaction rates are constant (being either a number of a parameter). Non-constant rates that depend on one (or several) species are also possible. More generally, the rate can be any valid expression of parameters and species. @@ -216,8 +216,8 @@ We can confirm that this generates the same ODE: latexify(rn_13_alt; form = :ode) ``` Here, while these models will generate identical ODE, SDE, and jump simulations, the chemical reaction network models themselves are not equivalent. Generally, as pointed out in the two notes below, using the second form is preferable. -!!! warn - While `rn_13` and `rn_13_alt` will generate equivalent simulations, for jump simulations, the first model will have reduced performance (which generally are more performant when rates are constant). +!!! warning + While `rn_13` and `rn_13_alt` will generate equivalent simulations, for jump simulations, the first model will have reduced performance as it generates a less performant representation of the system in JumpProcesses. It is generally recommended to write pure mass action reactions such that there is just a single constant within the rate constant expression for optimal performance of jump process simulations. !!! danger Catalyst automatically infers whether quantities appearing in the DSL are species or parameters (as described [here](@ref dsl_advanced_options_declaring_species_and_parameters)). Generally, anything that does not appear as a reactant is inferred to be a parameter. This means that if you want to model a reaction activated by a species (e.g. `kp*A, 0 --> P`), but that species does not occur as a reactant, it will be interpreted as a parameter. This can be handled by [manually declaring the system species](@ref dsl_advanced_options_declaring_species_and_parameters). @@ -232,7 +232,7 @@ end ``` ### [Using functions in rates](@id dsl_description_nonconstant_rates_functions) -It is possible for the rate to contain Julia functions. These can either be functions from Julia's standard library: +It is possible for the rate to contain Julia functions. These can either be functions from Julia's standard library: ```@example dsl_basics rn_16 = @reaction_network begin d, A --> 0 @@ -281,8 +281,12 @@ rn_15 = @reaction_network begin end ``` -!!! warn - Jump simulations cannot be performed for models with time-dependent rates without additional considerations. +!!! warning + Models with explicit time-dependent rates require additional steps to correctly + convert to stochastic chemical kinetics jump process representations. See + [here](https://github.com/SciML/Catalyst.jl/issues/636#issuecomment-1500311639) + for guidance on manually creating such representations. Enabling + Catalyst to handle this seamlessly is work in progress. ## [Non-standard stoichiometries](@id dsl_description_stoichiometries) @@ -327,7 +331,7 @@ end Catalyst uses `-->`, `<-->`, and `<--` to denote forward, bi-directional, and backwards reactions, respectively. Several unicode representations of these arrows are available. Here, - `>`, `→`, `↣`, `↦`, `⇾`, `⟶`, `⟼`, `⥟`, `⥟`, `⇀`, and `⇁` can be used to represent forward reactions. - `↔`, `⟷`, `⇄`, `⇆`, `⇌`, `⇋`, , and `⇔` can be used to represent bi-directional reactions. -- `<`, `←`, `↢`, `↤`, `⇽`, `⟵`, `⟻`, `⥚`, `⥞`, `↼`, , and `↽` can be used to represent backwards reactions. +- `<`, `←`, `↢`, `↤`, `⇽`, `⟵`, `⟻`, `⥚`, `⥞`, `↼`, , and `↽` can be used to represent backwards reactions. E.g. the production/degradation system can alternatively be written as: ```@example dsl_basics From a515321eb4448689112103c53e627f90a1bf10af Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Thu, 11 Jul 2024 09:52:10 -0400 Subject: [PATCH 430/446] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 85cc3bcb36..bc431716ca 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Catalyst.jl -[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/) -[![API Stable](https://img.shields.io/badge/API-stable-blue.svg)](https://docs.sciml.ai/Catalyst/stable/api/catalyst_api/) -[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://docs.sciml.ai/Catalyst/dev/) -[![API Dev](https://img.shields.io/badge/API-dev-blue.svg)](https://docs.sciml.ai/Catalyst/dev/api/catalyst_api/) -[![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) +[![Latest Release (for users)](https://img.shields.io/badge/docs-latest_release_(for_users)-blue.svg)](https://docs.sciml.ai/Catalyst/stable/) +[![API Latest Release (for users)](https://img.shields.io/badge/API-latest_release_(for_users)-blue.svg)](https://docs.sciml.ai/Catalyst/stable/api/catalyst_api/) +[![Master (for developers)](https://img.shields.io/badge/docs-master_branch_(for_devs)-blue.svg)](https://docs.sciml.ai/Catalyst/dev/) +[![API Master (for developers](https://img.shields.io/badge/API-master_branch_(for_devs)-blue.svg)](https://docs.sciml.ai/Catalyst/dev/api/catalyst_api/) + [![Build Status](https://github.com/SciML/Catalyst.jl/workflows/CI/badge.svg)](https://github.com/SciML/Catalyst.jl/actions?query=workflow%3ACI) [![codecov.io](https://codecov.io/gh/SciML/Catalyst.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/SciML/Catalyst.jl) From 9544511e230c2127e02f1d47828003d2cc98367c Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 11 Jul 2024 11:00:15 -0400 Subject: [PATCH 431/446] up --- src/network_analysis.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index b8166c0360..621bbe8977 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -392,7 +392,8 @@ function terminallinkageclasses(rn::ReactionSystem) end -# Check whether a given linkage class in a reaction network is terminal, i.e. all outgoing reactions from complexes in the linkage class produce a complex also in hte linkage class +# Helper function for terminallinkageclasses. Given a linkage class and a reaction network, say whether the linkage class is terminal, +# i.e. all outgoing reactions from complexes in the linkage class produce a complex also in the linkage class function isterminal(lc::Vector, rn::ReactionSystem) imat = incidencemat(rn) From ed8fda6ca8fddbe7ec0d992a04566af076cfd109 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 11 Jul 2024 11:07:00 -0400 Subject: [PATCH 432/446] formatter --- src/network_analysis.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index 621bbe8977..03c648edd8 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -391,7 +391,6 @@ function terminallinkageclasses(rn::ReactionSystem) nps.terminallinkageclasses end - # Helper function for terminallinkageclasses. Given a linkage class and a reaction network, say whether the linkage class is terminal, # i.e. all outgoing reactions from complexes in the linkage class produce a complex also in the linkage class function isterminal(lc::Vector, rn::ReactionSystem) From 7009b98eacce42b9f013ddfea592e9bec242f93c Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Thu, 11 Jul 2024 11:18:34 -0400 Subject: [PATCH 433/446] doc updates --- HISTORY.md | 271 +++++++++++++++++++------------- docs/src/api.md | 1 + docs/src/v14_migration_guide.md | 41 +++-- src/dsl.jl | 3 +- 4 files changed, 198 insertions(+), 118 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 753403e7af..ff92e9b5f5 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,18 +4,44 @@ ## Catalyst 14.0 -#### Breaking changes -Catalyst v14 was prompted by the (breaking) release of ModelingToolkit v9, which introduced several breaking changes to Catalyst. A summary of these (and how to handle them) can be found [here](https://docs.sciml.ai/Catalyst/stable/v14_migration_guide/). These are briefly summarised in the following bullet points: -- `ReactionSystem`s must now be marked *complete* before they are exposed to most forms of simulation and analysis. With the exception of `ReactionSystem`s created through the `@reaction_network` macro, all `ReactionSystem`s are *not* marked complete upon construction. The `complete` function can be used to mark `ReactionSystem`s as complete. To construct a `ReactionSystem` that is not marked complete via the DSL the new `@network_component` macro can be used. -- The `states` function has been replaced with `unknowns`. The `get_states` function has been replaced with `get_unknowns`. -- Support for most units (with the exception of `s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`) has currently been dropped by ModelingToolkit, and hence they are unavailable via Catalyst too. Its is expected that eventually support for relevant chemical units such as molar will return to ModelingToolkit (and should then immediately work in Catalyst too). -- Problem parameter values are now accessed through `prob.ps[p]` (rather than `prob[p]`). -- A significant bug prevents the safe application of the `remake` function on problems for which `remove_conserved = true` was used when updating the values of initial conditions. Instead, the values of each conserved constant must be directly specified. -- The `reactionparams`, `numreactionparams`, and `reactionparamsmap` functions have been deprecated and removed. -- To be more consistent with ModelingToolkit's immutability requirement for systems, we have removed API functions that mutate `ReactionSystem`s such as `addparam!`, `addreaction!`, `addspecies`, `@add_reactions`, and `merge!`. Please use `ModelingToolkit.extend` and `ModelingToolkit.compose` to generate new merged and/or composed `ReactionSystem`s from multiple component systems. +#### Breaking changes +Catalyst v14 was prompted by the (breaking) release of ModelingToolkit v9, which +introduced several breaking changes to Catalyst. A summary of these (and how to +handle them) can be found +[here](https://docs.sciml.ai/Catalyst/stable/v14_migration_guide/). These are +briefly summarised in the following bullet points: +- `ReactionSystem`s must now be marked *complete* before they are exposed to + most forms of simulation and analysis. With the exception of `ReactionSystem`s + created through the `@reaction_network` macro, all `ReactionSystem`s are *not* + marked complete upon construction. The `complete` function can be used to mark + `ReactionSystem`s as complete. To construct a `ReactionSystem` that is not + marked complete via the DSL the new `@network_component` macro can be used. +- The `states` function has been replaced with `unknowns`. The `get_states` + function has been replaced with `get_unknowns`. +- Support for most units (with the exception of `s`, `m`, `kg`, `A`, `K`, `mol`, + and `cd`) has currently been dropped by ModelingToolkit, and hence they are + unavailable via Catalyst too. Its is expected that eventually support for + relevant chemical units such as molar will return to ModelingToolkit (and + should then immediately work in Catalyst too). +- Problem parameter values are now accessed through `prob.ps[p]` (rather than + `prob[p]`). +- ModelingToolkit currently does not support the safe application of the + `remake` function, or safe direct mutation, for problems for which + `remove_conserved = true` was used when updating the values of initial + conditions. Instead, the values of each conserved constant must be directly + specified. +- The `reactionparams`, `numreactionparams`, and `reactionparamsmap` functions + have been deprecated and removed. +- To be more consistent with ModelingToolkit's immutability requirement for + systems, we have removed API functions that mutate `ReactionSystem`s such as + `addparam!`, `addreaction!`, `addspecies`, `@add_reactions`, and `merge!`. + Please use `ModelingToolkit.extend` and `ModelingToolkit.compose` to generate + new merged and/or composed `ReactionSystem`s from multiple component systems. #### General changes -- The `default_t()` and `default_time_deriv()` functions are now the preferred approaches for creating the default time independent variable and its differential. i.e. +- The `default_t()` and `default_time_deriv()` functions are now the preferred + approaches for creating the default time independent variable and its + differential. i.e. ```julia # do t = default_t() @@ -25,110 +51,141 @@ Catalyst v14 was prompted by the (breaking) release of ModelingToolkit v9, which @variables t @species A(t) - It is now possible to add metadata to individual reactions, e.g. using: -```julia -rn = @reaction_network begin - @parameters η - k, 2X --> X2, [description="Dimerisation"] -end -getdescription(rn) -``` -a more detailed description can be found [here](https://docs.sciml.ai/Catalyst/dev/model_creation/dsl_advanced/#dsl_advanced_options_reaction_metadata). -- `SDEProblem` no longer takes the `noise_scaling` argument. Noise scaling is now handled through the `noise_scaling` metadata (described in more detail [here](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_introduction/#simulation_intro_SDEs_noise_saling)) -- Fields of the internal `Reaction` structure have been changed. `ReactionSystems`s saved using `serialize` on previous Catalyst versions cannot be loaded using this (or later) versions. -- A new function, `save_reactionsystem`, which permits the writing of `ReactionSystem` models to files, has been created. A thorough description of this function can be found [here](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#Saving-Catalyst-models-to,-and-loading-them-from,-Julia-files) -- Update how compounds are created. E.g. use -```julia -@variables t C(t) O(t) -@compound CO2 ~ C + 2O -``` -to create a compound species `CO2` that consists of `C` and 2 `O`. -- Added documentation for chemistry-related functionality (compound creation and reaction balancing). + ```julia + rn = @reaction_network begin + @parameters η + k, 2X --> X2, [description="Dimerisation"] + end + getdescription(rn) + ``` + a more detailed description can be found [here](https://docs.sciml.ai/Catalyst/dev/model_creation/dsl_advanced/#dsl_advanced_options_reaction_metadata). +- `SDEProblem` no longer takes the `noise_scaling` argument. Noise scaling is + now handled through the `noise_scaling` metadata (described in more detail + [here](https://docs.sciml.ai/Catalyst/stable/model_simulation/simulation_introduction/#simulation_intro_SDEs_noise_saling)) +- Fields of the internal `Reaction` structure have been changed. + `ReactionSystems`s saved using `serialize` on previous Catalyst versions + cannot be loaded using this (or later) versions. +- A new function, `save_reactionsystem`, which permits the writing of + `ReactionSystem` models to files, has been created. A thorough description of + this function can be found + [here](https://docs.sciml.ai/Catalyst/stable/model_creation/model_file_loading_and_export/#Saving-Catalyst-models-to,-and-loading-them-from,-Julia-files) +- Updated how compounds are created. E.g. use + ```julia + @variables t C(t) O(t) + @compound CO2 ~ C + 2O + ``` + to create a compound species `CO2` that consists of `C` and two `O`. +- Added documentation for chemistry-related functionality (compound creation and + reaction balancing). - Added function `isautonomous` to check if a `ReactionSystem` is autonomous. -- Added function `steady_state_stability` to compute stability for steady states. Example: -```julia -# Creates model. -rn = @reaction_network begin - (p,d), 0 <--> X -end -p = [:p => 1.0, :d => 0.5] +- Added function `steady_state_stability` to compute stability for steady + states. Example: + ```julia + # Creates model. + rn = @reaction_network begin + (p,d), 0 <--> X + end + p = [:p => 1.0, :d => 0.5] -# Finds (the trivial) steady state, and computes stability. -steady_state = [2.0] -steady_state_stability(steady_state, rn, p) -``` -Here, `steady_state_stability` takes an optional argument `tol = 10*sqrt(eps())`, which is used to check that the real part of all eigenvalues are at least `tol` away from zero. Eigenvalues within `tol` of zero indicate that stability may not be reliably calculated. -- Added a DSL option, `@combinatoric_ratelaws`, which can be used to toggle whether to use combinatorial rate laws within the DSL (this feature was already supported for programmatic modelling). Example: -```julia -# Creates model. -rn = @reaction_network begin - @combinatoric_ratelaws false - (kB,kD), 2X <--> X2 -end -``` -- Added a DSL option, `@observables` for [creating observables](https://docs.sciml.ai/Catalyst/stable/model_creation/dsl_advanced/#dsl_advanced_options_observables) (this feature was already supported for programmatic modelling). -- Added DSL options `@continuous_events` and `@discrete_events` to add events to a model as part of its creation (this feature was already supported for programmatic modelling). Example: -```julia -rn = @reaction_network begin - @continuous_events begin - [X ~ 1.0] => [X ~ X + 1.0] - end - d, X --> 0 -end -``` -- Added DSL option `@equations` to add (algebraic or differential) equations to a model as part of its creation (this feature was already supported for programmatic modelling). Example: -```julia -rn = @reaction_network begin - @equations begin - D(V) ~ 1 - V - end - (p/V,d/V), 0 <--> X -end -``` -- Coupled reaction network + differential equation (or algebraic differential equation) systems can now be converted to `SDESystem`s and `NonlinearSystem`s. - -#### Structural identifiability extension -- Added CatalystStructuralIdentifiabilityExtension, which permits StructuralIdentifiability.jl function to be applied directly to Catalyst systems. E.g. use -```julia -using Catalyst, StructuralIdentifiability -goodwind_oscillator = @reaction_network begin - (mmr(P,pₘ,1), dₘ), 0 <--> M - (pₑ*M,dₑ), 0 <--> E - (pₚ*E,dₚ), 0 <--> P -end -assess_identifiability(goodwind_oscillator; measured_quantities=[:M]) -``` -to assess (global) structural identifiability for all parameters and variables of the `goodwind_oscillator` model (under the presumption that we can measure `M` only). -- Automatically handles conservation laws for structural identifiability problems (eliminates these internally to speed up computations). -- A more detailed of how this extension works can be found [here](https://docs.sciml.ai/Catalyst/stable/inverse_problems/structural_identifiability/). + # Finds (the trivial) steady state, and computes stability. + steady_state = [2.0] + steady_state_stability(steady_state, rn, p) + ``` + Here, `steady_state_stability` takes an optional keyword argument `tol = + 10*sqrt(eps())`, which is used to check that the real part of all eigenvalues + are at least `tol` away from zero. Eigenvalues within `tol` of zero indicate + that stability may not be reliably calculated. +- Added a DSL option, `@combinatoric_ratelaws`, which can be used to toggle + whether to use combinatorial rate laws within the DSL (this feature was + already supported for programmatic modelling). Example: + ```julia + # Creates model. + rn = @reaction_network begin + @combinatoric_ratelaws false + (kB,kD), 2X <--> X2 + end + ``` +- Added a DSL option, `@observables` for [creating + observables](https://docs.sciml.ai/Catalyst/stable/model_creation/dsl_advanced/#dsl_advanced_options_observables) + (this feature was already supported for programmatic modelling). +- Added DSL options `@continuous_events` and `@discrete_events` to add events to + a model as part of its creation (this feature was already supported for + programmatic modelling). Example: + ```julia + rn = @reaction_network begin + @continuous_events begin + [X ~ 1.0] => [X ~ X + 1.0] + end + d, X --> 0 + end + ``` +- Added DSL option `@equations` to add (algebraic or differential) equations to + a model as part of its creation (this feature was already supported for + programmatic modelling). Example: + ```julia + rn = @reaction_network begin + @equations begin + D(V) ~ 1 - V + end + (p/V,d/V), 0 <--> X + end + ``` + couples the ODE $dV/dt = 1 - V$ to the reaction system. +- Coupled reaction networks and differential equation (or algebraic differential + equation) systems can now be converted to `SDESystem`s and `NonlinearSystem`s. -#### Bifurcation analysis extension -- Add a CatalystBifurcationKitExtension, permitting BifurcationKit's `BifurcationProblem`s to be created from Catalyst reaction networks. Example usage: -```julia -using Catalyst -wilhelm_2009_model = @reaction_network begin - k1, Y --> 2X - k2, 2X --> X + Y - k3, X + Y --> Y - k4, X --> 0 - k5, 0 --> X -end +#### Structural identifiability extension +- Added CatalystStructuralIdentifiabilityExtension, which permits + StructuralIdentifiability.jl to be applied directly to Catalyst systems. E.g. + use + ```julia + using Catalyst, StructuralIdentifiability + goodwind_oscillator = @reaction_network begin + (mmr(P,pₘ,1), dₘ), 0 <--> M + (pₑ*M,dₑ), 0 <--> E + (pₚ*E,dₚ), 0 <--> P + end + assess_identifiability(goodwind_oscillator; measured_quantities=[:M]) + ``` + to assess (global) structural identifiability for all parameters and variables + of the `goodwind_oscillator` model (under the presumption that we can measure + `M` only). +- Automatically handles conservation laws for structural identifiability + problems (eliminates these internally to speed up computations). +- A more detailed of how this extension works can be found + [here](https://docs.sciml.ai/Catalyst/stable/inverse_problems/structural_identifiability/). + +#### Bifurcation analysis extension +- Add a CatalystBifurcationKitExtension, permitting BifurcationKit's + `BifurcationProblem`s to be created from Catalyst reaction networks. Example + usage: + ```julia + using Catalyst + wilhelm_2009_model = @reaction_network begin + k1, Y --> 2X + k2, 2X --> X + Y + k3, X + Y --> Y + k4, X --> 0 + k5, 0 --> X + end -using BifurcationKit -bif_par = :k1 -u_guess = [:X => 5.0, :Y => 2.0] -p_start = [:k1 => 4.0, :k2 => 1.0, :k3 => 1.0, :k4 => 1.5, :k5 => 1.25] -plot_var = :X -bprob = BifurcationProblem(wilhelm_2009_model, u_guess, p_start, bif_par; plot_var = plot_var) + using BifurcationKit + bif_par = :k1 + u_guess = [:X => 5.0, :Y => 2.0] + p_start = [:k1 => 4.0, :k2 => 1.0, :k3 => 1.0, :k4 => 1.5, :k5 => 1.25] + plot_var = :X + bprob = BifurcationProblem(wilhelm_2009_model, u_guess, p_start, bif_par; plot_var = plot_var) -p_span = (2.0, 20.0) -opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000) + p_span = (2.0, 20.0) + opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000) -bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true) + bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true) -using Plots -plot(bif_dia; xguide = "k1", guide = "X") -``` -- Automatically handles elimination of conservation laws for computing bifurcation diagrams. + using Plots + plot(bif_dia; xguide = "k1", guide = "X") + ``` +- Automatically handles elimination of conservation laws for computing + bifurcation diagrams. - Updated Bifurcation documentation with respect to this new feature. ## Catalyst 13.5 diff --git a/docs/src/api.md b/docs/src/api.md index 8a5e543973..459dbd1c1b 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -77,6 +77,7 @@ plot(p1, p2, p3; layout = (3,1)) ```@docs @reaction_network +@network_component make_empty_network @reaction Reaction diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index ed47a2a439..a1e41b277a 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -16,7 +16,7 @@ A model's completeness depends on how it was created: - To *use the DSL to create models that are not marked as complete*, use the `@network_component` macro (which in all other aspects is identical to `@reaction_network`). - Models generated through the `compose` and `extend` functions are *not marked as complete*. -Furthermore, any systems generated through e.g. `convert(ODESystem, rs)` are *not marked as complete*. +Furthermore, any systems generated through e.g. `convert(ODESystem, rs)` are *not marked as complete*. Complete models can be generated from incomplete models through the `complete` function. Here is a workflow where we take completeness into account in the simulation of a simple birth-death process. ```@example v14_migration_1 @@ -64,8 +64,30 @@ sol = solve(oprob) plot(sol) ``` +Note, that if we had instead used the [`@reaction_network`](@ref) DSL macro to build +our model, i.e. +```@example v14_migration_1 +rs2 = @reaction_network rs begin + p, ∅ --> X + d, X --> ∅ +end +``` +then the model is automatically marked as complete +```@example v14_migration_1 +Catalyst.iscomplete(rs2) +``` +In contrast, if we used the [`@network_component`](@ref) DSL macro to build our +model it is not marked as complete, and is equivalent to our original definition of `rs` +```@example v14_migration_1 +rs3 = @network_component rs begin + p, ∅ --> X + d, X --> ∅ +end +Catalyst.iscomplete(rs3) +``` + ## Unknowns instead of states -Previously, "states" was used as a term for system variables (both species and non-species variables). MTKv9 has switched to using the term "unknowns" instead. This means that there have been a number of changes to function names (e.g. `states` => `unknowns` and `get_states` => `get_unknowns`). +Previously, "states" was used as a term for system variables (both species and non-species variables). MTKv9 has switched to using the term "unknowns" instead. This means that there have been a number of changes to function names (e.g. `states` => `unknowns` and `get_states` => `get_unknowns`). E.g. here we declare a `ReactionSystem` model containing both species and non-species unknowns: ```@example v14_migration_2 @@ -100,10 +122,10 @@ As part of its v9 update, ModelingToolkit changed how units were handled. This i While this should lead to long-term improvements, unfortunately, as part of the process support for most units was removed. Currently, only the main SI units are supported (`s`, `m`, `kg`, `A`, `K`, `mol`, and `cd`). Composite units (e.g. `N = kg/(m^2)`) are no longer supported. Furthermore, prefix units (e.g. `mm = m/1000`) are not supported either. This means that most units relevant to Catalyst (such as `µM`) cannot be used directly. While composite units can still be written out in full and used (e.g. `kg/(m^2)`) this is hardly user-friendly. -The maintainers of ModelingTOolkit have been notified of this issue. We are unsure when this will be fixed, however, we do not think it will be a permanent change. +The maintainers of ModelingToolkit have been notified of this issue. We are unsure when this will be fixed, however, we do not think it will be a permanent change. ## Removed support for system-mutating functions -According to the ModelingToolkit system API, systems should not be mutable. In accordance with this, the following functions have been deprecated: `addparam!`, `addreaction!`, `addspecies!`, `@add_reactions`, and `merge!`. Please use `ModelingToolkit.extend` and `ModelingToolkit.compose` to generate new merged and/or composed `ReactionSystems` from multiple component systems. +According to the ModelingToolkit system API, systems should not be mutable. In accordance with this, the following functions have been deprecated and removed: `addparam!`, `addreaction!`, `addspecies!`, `@add_reactions`, and `merge!`. Please use `ModelingToolkit.extend` and `ModelingToolkit.compose` to generate new merged and/or composed `ReactionSystems` from multiple component systems. It is still possible to add default values to a created `ReactionSystem`, i.e. the `setdefaults!` function is still supported. @@ -132,9 +154,9 @@ nothing # hide ``` !!! note - If you look at ModelingToolkit documentation, these defaults are instead retrieved using `using ModelingToolkit: t_nounits as t, D_nounits as D`. This will also work, however, in Catalyst we have opted to instead use `default_t` and `default_time_deriv` as our main approach. + If you look at ModelingToolkit documentation, these defaults are instead retrieved using `using ModelingToolkit: t_nounits as t, D_nounits as D`. This will also work, however, in Catalyst we have opted to instead use the functions `default_t()` and `default_time_deriv()` as our main approach. -## New interface for accessing problem/integrator/solution parameter (and species) value +## New interface for accessing problem/integrator/solution parameter (and species) values Previously, it was possible to directly index problems to query them for their parameter values. e.g. ```@example v14_migration_4 using Catalyst @@ -154,12 +176,11 @@ This is *no longer supported*. When you wish to query a problem (or integrator o oprob.ps[:p] ``` -Furthermore, a few new functions (`getp`, `getu`, `setp`, `setu`) have been introduced. These can improve performance when querying a structure for a value multiple times (especially for very large models). These are described in more detail [here](@ref simulation_structure_interfacing_functions). +Furthermore, a few new functions (`getp`, `getu`, `setp`, `setu`) have been introduced from [SymbolicIndexingInterface](https://github.com/SciML/SymbolicIndexingInterface.jl) to support efficient and systematic querying and/or updating of symbolic unknown/parameter values. Using these can *significantly* improve performance when querying or updating a value multiple times, for example within a callback. These are described in more detail [here](@ref simulation_structure_interfacing_functions). For more details on how to query various structures for parameter and species values, please read [this documentation page](@ref simulation_structure_interfacing). -## Other notes -Finally, here are some additional, minor, notes regarding the new update. +## Other changes #### Modification of problems with conservation laws broken While it is possible to update e.g. `ODEProblem`s using the [`remake`](@ref simulation_structure_interfacing_problems_remake) function, this is currently not possible if the `remove_conserved = true` option was used. E.g. while @@ -174,7 +195,7 @@ oprob = ODEProblem(rn, u0, (0.0, 10.0), ps; remove_conserved = true) solve(oprob) # hide ``` -is perfectly fine, attempting to then modify any initial conditions or the value of the conservation constant in `oprob` will silently fail: +is perfectly fine, attempting to then modify any initial conditions or the value of the conservation constant in `oprob` will likely silently fail: ```@example v14_migration_5 oprob_remade = remake(oprob; u0 = [:X1 => 5.0]) # NEVER do this. solve(oprob_remade) diff --git a/src/dsl.jl b/src/dsl.jl index 633ea7f64b..6148eb4814 100644 --- a/src/dsl.jl +++ b/src/dsl.jl @@ -175,7 +175,8 @@ end """ @network_component -As @reaction_network, but the output system is not complete. +Equivalent to `@reaction_network` except the generated `ReactionSystem` is not marked as +complete. """ macro network_component(name::Symbol, ex::Expr) make_reaction_system(MacroTools.striplines(ex); name = :($(QuoteNode(name)))) From 91e2bc22f6b96bd76437dce45402ce4c7e92bfab Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Thu, 11 Jul 2024 11:23:20 -0400 Subject: [PATCH 434/446] Update docs/src/v14_migration_guide.md --- docs/src/v14_migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/v14_migration_guide.md b/docs/src/v14_migration_guide.md index a1e41b277a..ed5074839b 100644 --- a/docs/src/v14_migration_guide.md +++ b/docs/src/v14_migration_guide.md @@ -64,7 +64,7 @@ sol = solve(oprob) plot(sol) ``` -Note, that if we had instead used the [`@reaction_network`](@ref) DSL macro to build +Note, if we had instead used the [`@reaction_network`](@ref) DSL macro to build our model, i.e. ```@example v14_migration_1 rs2 = @reaction_network rs begin From c92d09beeaf11a0982413094e2920704a2e79a74 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 11 Jul 2024 12:01:43 -0400 Subject: [PATCH 435/446] init --- docs/src/model_creation/dsl_advanced.md | 4 ++-- docs/src/model_creation/programmatic_CRN_construction.md | 2 +- docs/src/model_simulation/simulation_structure_interfacing.md | 4 ++-- .../steady_state_stability_computation.md | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/src/model_creation/dsl_advanced.md b/docs/src/model_creation/dsl_advanced.md index 3e42269b28..4edd069ba0 100644 --- a/docs/src/model_creation/dsl_advanced.md +++ b/docs/src/model_creation/dsl_advanced.md @@ -85,7 +85,7 @@ Generally, there are four main reasons for specifying species/parameters using t 3. To designate metadata for species/parameters (described [here](@ref dsl_advanced_options_species_and_parameters_metadata)). 4. To designate a species or parameters that do not occur in reactions, but are still part of the model (e.g a [parametric initial condition](@ref dsl_advanced_options_parametric_initial_conditions)) -!!! warn +!!! warning Catalyst's DSL automatically infer species and parameters from the input. However, it only does so for *quantities that appear in reactions*. Until now this has not been relevant. However, this tutorial will demonstrate cases where species/parameters that are not part of reactions are used. These *must* be designated using either the `@species` or `@parameters` options (or the `@variables` option, which is described [later](@ref constraint_equations)). ### [Setting default values for species and parameters](@id dsl_advanced_options_default_vals) @@ -496,7 +496,7 @@ sol = solve(oprob) plot(sol) ``` -!!! warn +!!! warning Just like when using `@parameters` and `@species`, `@unpack` will overwrite any variables in the current scope which share name with the imported quantities. ### [Interpolating variables into the DSL](@id dsl_advanced_options_symbolics_and_DSL_interpolation) diff --git a/docs/src/model_creation/programmatic_CRN_construction.md b/docs/src/model_creation/programmatic_CRN_construction.md index 5232db3886..d5d12911c8 100644 --- a/docs/src/model_creation/programmatic_CRN_construction.md +++ b/docs/src/model_creation/programmatic_CRN_construction.md @@ -61,7 +61,7 @@ system to be the same as the name of the variable storing the system. Alternatively, one can use the `name = :repressilator` keyword argument to the `ReactionSystem` constructor. -!!! warn +!!! warning All `ReactionSystem`s created via the symbolic interface (i.e. by calling `ReactionSystem` with some input, rather than using `@reaction_network`) are not marked as complete. To simulate them, they must first be marked as *complete*, indicating to Catalyst and ModelingToolkit that they represent finalized models. This can be done using the `complete` function, i.e. by calling `repressilator = complete(repressilator)`. An expanded description on *completeness* can be found [here](@ref completeness_note). We can check that this is the same model as the one we defined via the DSL as diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index d8c9463234..4f44863036 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -52,7 +52,7 @@ oprob[[:S₁, :S₂]] ``` Generally, when updating problems, it is often better to use the [`remake` function](@ref simulation_structure_interfacing_problems_remake) (especially when several values are updated). -!!! warn +!!! warning Indexing *should not* be used not modify `JumpProblem`s. Here, [remake](@ref simulation_structure_interfacing_problems_remake) should be used exclusively. A problem's time span can be accessed through the `tspan` field: @@ -196,5 +196,5 @@ oprob[two_state_model.X1 + two_state_model.X2] ``` This can be used to form symbolic expressions using model quantities when a model has been created using the DSL (as an alternative to @unpack). Alternatively, [creating an observable](@ref dsl_advanced_options_observables), and then interface using its `Symbol` representation, is also possible. -!!! warn +!!! warning With interfacing with a simulating structure using symbolic variables stored in a `ReactionSystem` model, ensure that the model is complete. \ No newline at end of file diff --git a/docs/src/steady_state_functionality/steady_state_stability_computation.md b/docs/src/steady_state_functionality/steady_state_stability_computation.md index 08e6fa69b2..c51b55cd2a 100644 --- a/docs/src/steady_state_functionality/steady_state_stability_computation.md +++ b/docs/src/steady_state_functionality/steady_state_stability_computation.md @@ -1,7 +1,7 @@ # [Steady state stability computation](@id steady_state_stability) After system steady states have been found using [HomotopyContinuation.jl](@ref homotopy_continuation), [NonlinearSolve.jl](@ref steady_state_solving), or other means, their stability can be computed using Catalyst's `steady_state_stability` function. Systems with conservation laws will automatically have these removed, permitting stability computation on systems with singular Jacobian. -!!! warn +!!! warning Catalyst currently computes steady state stabilities using the naive approach of checking whether a system's largest eigenvalue real part is negative. While more advanced stability computation methods exist (and would be a welcome addition to Catalyst), there is no direct plans to implement these. Furthermore, Catalyst uses a tolerance `tol = 10*sqrt(eps())` to determine whether a computed eigenvalue is far away enough from 0 to be reliably used. This threshold can be changed through the `tol` keyword argument. ## [Basic examples](@id steady_state_stability_basics) @@ -59,5 +59,5 @@ stabs_2 = [steady_state_stability(st, sa_loop, ps_2; ss_jac) for st in steady_st nothing # hide ``` -!!! warn +!!! warning For systems with [conservation laws](@ref homotopy_continuation_conservation_laws), `steady_state_jac` must be supplied a `u0` vector (indicating species concentrations for conservation law computation). This is required to eliminate the conserved quantities, preventing a singular Jacobian. These are supplied using the `u0` optional argument. \ No newline at end of file From 66277600a2f000eafe7365f3a5cb13fad8188308 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 11 Jul 2024 16:11:35 -0400 Subject: [PATCH 436/446] fixed reset --- src/reactionsystem.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index c45231f502..5944e895fc 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -119,6 +119,7 @@ function reset!(nps::NetworkProperties{I, V}) where {I, V} nps.isempty && return nps.netstoichmat = Matrix{Int}(undef, 0, 0) nps.conservationmat = Matrix{I}(undef, 0, 0) + nps.cyclemat = Matrix{Int}(undef, 0, 0) empty!(nps.col_order) nps.rank = 0 nps.nullity = 0 @@ -134,6 +135,8 @@ function reset!(nps::NetworkProperties{I, V}) where {I, V} nps.complexoutgoingmat = Matrix{Int}(undef, 0, 0) nps.incidencegraph = Graphs.DiGraph() empty!(nps.linkageclasses) + empty!(nps.stronglinkageclasses) + empty!(nps.terminallinkageclasses) nps.deficiency = 0 # this needs to be last due to setproperty! setting it to false From 6571e840a295a680a883673dc0818e5709b3abdb Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 11 Jul 2024 16:16:27 -0400 Subject: [PATCH 437/446] fix docstring --- src/network_analysis.jl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/network_analysis.jl b/src/network_analysis.jl index e9f5af1160..abab4f66ac 100644 --- a/src/network_analysis.jl +++ b/src/network_analysis.jl @@ -260,17 +260,12 @@ end Construct a directed simple graph where nodes correspond to reaction complexes and directed edges to reactions converting between two complexes. -Notes: -- Requires the `incidencemat` to already be cached in `rn` by a previous call to - `reactioncomplexes`. - For example, ```julia sir = @reaction_network SIR begin β, S + I --> 2I ν, I --> R end -complexes,incidencemat = reactioncomplexes(sir) incidencematgraph(sir) ``` """ @@ -498,8 +493,7 @@ isweaklyreversible(rn, subnets) function isweaklyreversible(rn::ReactionSystem, subnets) nps = get_networkproperties(rn) isempty(nps.incidencemat) && reactioncomplexes(rn) - imat = nps.incidencemat - sparseig = issparse(imat) + sparseig = issparse(nps.incidencemat) for subnet in subnets subnps = get_networkproperties(subnet) From 8dbb39833912dc993df04b3b164256911e619929 Mon Sep 17 00:00:00 2001 From: Sam Isaacson Date: Thu, 11 Jul 2024 18:54:49 -0400 Subject: [PATCH 438/446] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 0a302b5529..5d2088785a 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Catalyst" uuid = "479239e8-5488-4da2-87a7-35f2df7eef83" -version = "14.0.0-DEV" +version = "14.0.0" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" From 8b53a91d3729181227d6f51bd66ad6e3ede8f6c3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 11 Jul 2024 21:18:02 -0400 Subject: [PATCH 439/446] init --- README.md | 4 ++-- docs/src/index.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bc431716ca..18a432c9a1 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,9 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model - Support for [parallelization of all simulations](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation), including parallelization of [ODE simulations on GPUs](https://docs.sciml.ai/Catalyst/stable/model_simulation/ode_simulation_performance/#ode_simulation_performance_parallelisation_GPU) using [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). - [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX expressions](https://docs.sciml.ai/Catalyst/stable/model_creation/model_visualisation/#visualisation_latex) corresponding to generated mathematical models or the underlying set of reactions. - [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](https://docs.sciml.ai/Catalyst/stable/model_creation/model_visualisation/#visualisation_graphs) (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). -- Model steady states can be computed through homotopy continuation using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by forward ODE simulations using [SteadyStateDiffEq.jl](https://github.com/SciML/SteadyStateDiffEq.jl), or by numerically solving steady-state nonlinear equations using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). +- Model steady states can be [computed through homotopy continuation](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/homotopy_continuation/) using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by [forward ODE simulations](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/nonlinear_solve/#steady_state_solving_simulation) using [SteadyStateDiffEq.jl](https://github.com/SciML/SteadyStateDiffEq.jl), or by [numerically solving steady-state nonlinear equations](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/nonlinear_solve/#steady_state_solving_nonlinear) using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). - [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute bifurcation diagrams](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/bifurcation_diagrams/) of model steady states (including finding periodic orbits). -- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute model [basins of attraction](@ref dynamical_systems_basins_of_attraction), [Lyapunov spectrums](@ref dynamical_systems_lyapunov_exponents), and other dynamical system properties. +- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute model [basins of attraction]([@ref dynamical_systems_basins_of_attraction](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_basins_of_attraction)), [Lyapunov spectrums](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_lyapunov_exponents), and other dynamical system properties. - [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used to [perform structural identifiability analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/structural_identifiability/). - [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](https://sebapersson.github.io/PEtab.jl/stable/Define_in_julia/). - [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform [global sensitivity analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/global_sensitivity_analysis/) of model behaviors. diff --git a/docs/src/index.md b/docs/src/index.md index a8ea0531da..7ba02ee07f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -38,7 +38,7 @@ etc). - Support for [parallelization of all simulations](@ref ode_simulation_performance_parallelisation), including parallelization of [ODE simulations on GPUs](@ref ode_simulation_performance_parallelisation_GPU) using [DiffEqGPU.jl](https://github.com/SciML/DiffEqGPU.jl). - [Latexify](https://korsbo.github.io/Latexify.jl/stable/) can be used to [generate LaTeX expressions](@ref visualisation_latex) corresponding to generated mathematical models or the underlying set of reactions. - [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](@ref visualisation_graphs) (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). -- Model steady states can be [computed through homotopy continuation](@ref homotopy_continuation) using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by [forward ODE simulations](@ref steady_state_solving_simulation) using [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl, or by [numerically solving steady-state nonlinear equations](@ref steady_state_solving_nonlinear) using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). +- Model steady states can be [computed through homotopy continuation](@ref homotopy_continuation) using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by [forward ODE simulations](@ref steady_state_solving_simulation) using [SteadyStateDiffEq.jl)](https://github.com/SciML/SteadyStateDiffEq.jl), or by [numerically solving steady-state nonlinear equations](@ref steady_state_solving_nonlinear) using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). - [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute bifurcation diagrams](@ref bifurcation_diagrams) of model steady states (including finding periodic orbits). - [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute model [basins of attraction](@ref dynamical_systems_basins_of_attraction), [Lyapunov spectrums](@ref dynamical_systems_lyapunov_exponents), and other dynamical system properties. - [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used to [perform structural identifiability analysis](@ref structural_identifiability). From 154bdbf114319c253eaae27ff866e42a5ceb0f8c Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 12 Jul 2024 07:44:58 -0400 Subject: [PATCH 440/446] init --- .../simulation_structure_interfacing.md | 42 +++++++------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index d8c9463234..0d557be2f0 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -1,7 +1,7 @@ # [Interfacing problems, integrators, and solutions](@id simulation_structure_interfacing) When simulating a model, one begins with creating a [problem](https://docs.sciml.ai/DiffEqDocs/stable/basics/problem/). Next, a simulation is performed on the problem, during which the simulation's state is recorded through an [integrator](https://docs.sciml.ai/DiffEqDocs/stable/basics/integrator/). Finally, the simulation output is returned as a [solution](https://docs.sciml.ai/DiffEqDocs/stable/basics/solution/). This tutorial describes how to access (or modify) the state (or parameter) values of problem, integrator, and solution structures. -Generally, when we have a structure `simulation_struct` and want to interface with the unknown (or parameter) `x`, we use `simulation_struct[:x]` to access the value, and `simulation_struct[:x] = 5.0` to set it to a new value. For situations where a value is accessed (or changed) a large number of times, it can *improve performance* to first create a [specialised getter/setter function](@ref simulation_structure_interfacing_functions). +Generally, when we have a structure `simulation_struct` and want to interface with the unknown (or parameter) `x`, we use `simulation_struct[:x]` to access the value. For situations where a value is accessed (or changed) a large number of times, it can *improve performance* to first create a [specialised getter/setter function](@ref simulation_structure_interfacing_functions). ## [Interfacing problem objects](@id simulation_structure_interfacing_problems) @@ -35,26 +35,6 @@ To retrieve several species initial condition (or parameter) values, simply give oprob[[:S₁, :S₂]] ``` -We can change a species's initial condition value using a similar notation. Here we increase the initial concentration of $C$ (and also confirm that the new value is stored in an updated `oprob`): -```@example structure_indexing -oprob[:C] = 0.1 -oprob[:C] -``` -Again, parameter values can be changed using a similar notation, however, again requiring `oprob.ps` notation: -```@example structure_indexing -oprob.ps[:k₁] = 10.0 -oprob.ps[:k₁] -``` -Finally, vectors can be used to update multiple quantities simultaneously -```@example structure_indexing -oprob[[:S₁, :S₂]] = [0.5, 0.3] -oprob[[:S₁, :S₂]] -``` -Generally, when updating problems, it is often better to use the [`remake` function](@ref simulation_structure_interfacing_problems_remake) (especially when several values are updated). - -!!! warn - Indexing *should not* be used not modify `JumpProblem`s. Here, [remake](@ref simulation_structure_interfacing_problems_remake) should be used exclusively. - A problem's time span can be accessed through the `tspan` field: ```@example structure_indexing oprob.tspan @@ -64,8 +44,8 @@ oprob.tspan Here we have used an `ODEProblem`to demonstrate all interfacing functionality. However, identical workflows work for the other problem types. ### [Remaking problems using the `remake` function](@id simulation_structure_interfacing_problems_remake) -The `remake` function offers an (to indexing) alternative approach for updating problems. Unlike indexing, `remake` creates a new problem (rather than updating the old one). Furthermore, it permits the updating of several values simultaneously. The `remake` function takes the following inputs: -- The problem that is remakes. +To modify a problem, the `remake` function should be used. It takes an already created problem, and returns a new, updated, one (the input problem is unchanged). The `remake` function takes the following inputs: +- The problem that it remakes. - (optionally) `u0`: A vector with initial conditions that should be updated. The vector takes the same form as normal initial condition vectors, but does not need to be complete (in which case only a subset of the initial conditions are updated). - (optionally) `tspan`: An updated time span (using the same format as time spans normally are given in). - (optionally) `p`: A vector with parameters that should be updated. The vector takes the same form as normal parameter vectors, but does not need to be complete (in which case only a subset of the parameters are updated). @@ -74,22 +54,28 @@ Here we modify our problem to increase the initial condition concentrations of t ```@example structure_indexing using OrdinaryDiffEq oprob_new = remake(oprob; u0 = [:S₁ => 5.0, :S₂ => 2.5]) -oprob_new == oprob +oprob_new != oprob ``` -Here, we instead use `remake` to simultaneously update a all three fields: +Here, we instead use `remake` to simultaneously update all three fields: ```@example structure_indexing oprob_new_2 = remake(oprob; u0 = [:C => 0.2], tspan = (0.0, 20.0), p = [:k₁ => 2.0, :k₂ => 2.0]) nothing # hide ``` +Typically, when using `remake` to update a problem, the common workflow is to overwrite the old one with the output. E.g. to set the value of `k₁` to `5.0` in `oprob`, you would do: +```@example structure_indexing +oprob = remake(oprob; p = [:k₁ => 5.0]) +nothing # hide +``` + ## [Interfacing integrator objects](@id simulation_structure_interfacing_integrators) -During a simulation, the solution is stored in an integrator object. Here, we will describe how to interface with these. The almost exclusive circumstance when integrator-interfacing is relevant is when simulation events are implemented through callbacks. However, to demonstrate integrator indexing in this tutorial, we will create one through the `init` function (while circumstances where one might [want to use `init` function exist](https://docs.sciml.ai/DiffEqDocs/stable/basics/integrator/#Initialization-and-Stepping), since integrators are automatically created during simulations, these are rare). +During a simulation, the solution is stored in an integrator object. Here, we will describe how to interface with these. The almost exclusive circumstance when integrator-interfacing is relevant is when simulation events are implemented through [callbacks](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/). However, to demonstrate integrator indexing in this tutorial, we will create one through the `init` function (while circumstances where one might [want to use `init` function exist](https://docs.sciml.ai/DiffEqDocs/stable/basics/integrator/#Initialization-and-Stepping), since integrators are automatically created during simulations, these are rare). ```@example structure_indexing integrator = init(oprob) nothing # hide ``` -We can interface with our integrator using an identical syntax as [was used for problems](@ref simulation_structure_interfacing_problems) (with the exception that `remake` is not available). Here we update, and then check the values of, first the species $C$ and then the parameter $k₁$: +We can interface with our integrator using an identical syntax as [was used for problems](@ref simulation_structure_interfacing_problems). The primary exception is that there is no `remake` function for integrators. Instead, we can update species and parameter values using normal indexing. Here we update, and then check the values of, first the species $C$ and then the parameter $k₁$: ```@example structure_indexing integrator[:C] = 0.0 integrator[:C] @@ -180,7 +166,7 @@ tspan = (0.0, 1.0) ps = [k1 => 1.0, k2 => 2.0] oprob = ODEProblem(two_state_model, u0, tspan, ps) -oprob[X1] = 5.0 +oprob = remake(oprob; u0 = [X1 => 5.0]) oprob[X1] ``` Symbolic variables can be used to access or update species or parameters for all the cases when `Symbol`s can (including when using `remake` or e.g. `getu`). From c9934852ae7715b16b663eb5da3cc9e5126d1601 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 12 Jul 2024 09:44:42 -0400 Subject: [PATCH 441/446] Use OrdinaryDiffEq where remake was added --- docs/src/model_simulation/simulation_structure_interfacing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index 0d557be2f0..4752d9cbbb 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -150,7 +150,7 @@ get_S(oprob) ## [Interfacing using symbolic representations](@id simulation_structure_interfacing_symbolic_representation) When e.g. [programmatic modelling is used](@ref programmatic_CRN_construction), species and parameters can be represented as *symbolic variables*. These can be used to index a problem, just like symbol-based representations can. Here we create a simple [two-state model](@ref basic_CRN_library_two_states) programmatically, and use its symbolic variables to check, and update, an initial condition: ```@example structure_indexing_symbolic_variables -using Catalyst +using Catalyst, OrdinaryDiffEq t = default_t() @species X1(t) X2(t) @parameters k1 k2 @@ -183,4 +183,4 @@ oprob[two_state_model.X1 + two_state_model.X2] This can be used to form symbolic expressions using model quantities when a model has been created using the DSL (as an alternative to @unpack). Alternatively, [creating an observable](@ref dsl_advanced_options_observables), and then interface using its `Symbol` representation, is also possible. !!! warn - With interfacing with a simulating structure using symbolic variables stored in a `ReactionSystem` model, ensure that the model is complete. \ No newline at end of file + With interfacing with a simulating structure using symbolic variables stored in a `ReactionSystem` model, ensure that the model is complete. From 6c93d951c68d66b43eab891a4663c3058aeb64e0 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 12 Jul 2024 10:02:46 -0400 Subject: [PATCH 442/446] Update docs/src/model_simulation/simulation_structure_interfacing.md Co-authored-by: Sam Isaacson --- docs/src/model_simulation/simulation_structure_interfacing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index 4752d9cbbb..72af0fbfe1 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -183,4 +183,4 @@ oprob[two_state_model.X1 + two_state_model.X2] This can be used to form symbolic expressions using model quantities when a model has been created using the DSL (as an alternative to @unpack). Alternatively, [creating an observable](@ref dsl_advanced_options_observables), and then interface using its `Symbol` representation, is also possible. !!! warn - With interfacing with a simulating structure using symbolic variables stored in a `ReactionSystem` model, ensure that the model is complete. + When accessing a simulation structure using symbolic variables from a `ReactionSystem` model, such as `rn.A` for `rn` a `ReactionSystem` and `A` a species within it, ensure that the model is complete. From bbed9200e0dc1a57cd5baf687ef65448d0ef8f19 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 12 Jul 2024 10:02:52 -0400 Subject: [PATCH 443/446] Update docs/src/model_simulation/simulation_structure_interfacing.md Co-authored-by: Sam Isaacson --- docs/src/model_simulation/simulation_structure_interfacing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index 72af0fbfe1..1c37df3672 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -182,5 +182,5 @@ oprob[two_state_model.X1 + two_state_model.X2] ``` This can be used to form symbolic expressions using model quantities when a model has been created using the DSL (as an alternative to @unpack). Alternatively, [creating an observable](@ref dsl_advanced_options_observables), and then interface using its `Symbol` representation, is also possible. -!!! warn +!!! warning When accessing a simulation structure using symbolic variables from a `ReactionSystem` model, such as `rn.A` for `rn` a `ReactionSystem` and `A` a species within it, ensure that the model is complete. From 1b3cdd7f6d3aad240a6c89ec914ad39c95615141 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 12 Jul 2024 12:03:26 -0400 Subject: [PATCH 444/446] NPS fix --- src/reactionsystem.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index ffee08fcb9..9a24863bb8 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -78,6 +78,7 @@ Base.@kwdef mutable struct NetworkProperties{I <: Integer, V <: BasicSymbolic{Re isempty::Bool = true netstoichmat::Union{Matrix{Int}, SparseMatrixCSC{Int, Int}} = Matrix{Int}(undef, 0, 0) conservationmat::Matrix{I} = Matrix{I}(undef, 0, 0) + cyclemat::Matrix{I} = Matrix{I}(undef, 0, 0) col_order::Vector{Int} = Int[] rank::Int = 0 nullity::Int = 0 From 28be9ebc5a3a5b64a18c2339b98f85e3cfe1eab0 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 12 Jul 2024 12:57:43 -0400 Subject: [PATCH 445/446] NPS fix --- src/reactionsystem.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/reactionsystem.jl b/src/reactionsystem.jl index 9a24863bb8..7bd09f555b 100644 --- a/src/reactionsystem.jl +++ b/src/reactionsystem.jl @@ -94,6 +94,8 @@ Base.@kwdef mutable struct NetworkProperties{I <: Integer, V <: BasicSymbolic{Re complexoutgoingmat::Union{Matrix{Int}, SparseMatrixCSC{Int, Int}} = Matrix{Int}(undef, 0, 0) incidencegraph::Graphs.SimpleDiGraph{Int} = Graphs.DiGraph() linkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0) + stronglinkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0) + terminallinkageclasses::Vector{Vector{Int}} = Vector{Vector{Int}}(undef, 0) deficiency::Int = 0 end #! format: on From f549bc11f71e540e29d8d900c4d9d82bd52674d9 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 12 Jul 2024 22:09:23 -0400 Subject: [PATCH 446/446] up --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 18a432c9a1..761b2a26b3 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ be found in its corresponding research paper, [Catalyst: Fast and flexible model - [Graphviz](https://graphviz.org/) can be used to generate and [visualize reaction network graphs](https://docs.sciml.ai/Catalyst/stable/model_creation/model_visualisation/#visualisation_graphs) (reusing the Graphviz interface created in [Catlab.jl](https://algebraicjulia.github.io/Catlab.jl/stable/)). - Model steady states can be [computed through homotopy continuation](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/homotopy_continuation/) using [HomotopyContinuation.jl](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl) (which can find *all* steady states of systems with multiple ones), by [forward ODE simulations](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/nonlinear_solve/#steady_state_solving_simulation) using [SteadyStateDiffEq.jl](https://github.com/SciML/SteadyStateDiffEq.jl), or by [numerically solving steady-state nonlinear equations](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/nonlinear_solve/#steady_state_solving_nonlinear) using [NonlinearSolve.jl](https://github.com/SciML/NonlinearSolve.jl). - [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) can be used to [compute bifurcation diagrams](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/bifurcation_diagrams/) of model steady states (including finding periodic orbits). -- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute model [basins of attraction]([@ref dynamical_systems_basins_of_attraction](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_basins_of_attraction)), [Lyapunov spectrums](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_lyapunov_exponents), and other dynamical system properties. +- [DynamicalSystems.jl](https://github.com/JuliaDynamics/DynamicalSystems.jl) can be used to compute model [basins of attraction](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_basins_of_attraction), [Lyapunov spectrums](https://docs.sciml.ai/Catalyst/stable/steady_state_functionality/dynamical_systems/#dynamical_systems_lyapunov_exponents), and other dynamical system properties. - [StructuralIdentifiability.jl](https://github.com/SciML/StructuralIdentifiability.jl) can be used to [perform structural identifiability analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/structural_identifiability/). - [Optimization.jl](https://github.com/SciML/Optimization.jl), [DiffEqParamEstim.jl](https://github.com/SciML/DiffEqParamEstim.jl), and [PEtab.jl](https://github.com/sebapersson/PEtab.jl) can all be used to [fit model parameters to data](https://sebapersson.github.io/PEtab.jl/stable/Define_in_julia/). - [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) can be used to perform [global sensitivity analysis](https://docs.sciml.ai/Catalyst/stable/inverse_problems/global_sensitivity_analysis/) of model behaviors.