From 799640d27a08187ac7a71c92958b59ab7a1f6298 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Fri, 12 Jun 2026 14:43:03 +0100 Subject: [PATCH 1/8] init with new doc page --- docs/pages.jl | 1 + docs/src/inverse_problems/udes.md | 100 ++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 docs/src/inverse_problems/udes.md diff --git a/docs/pages.jl b/docs/pages.jl index 9e80f95acf..7cdc4dd979 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -67,6 +67,7 @@ pages = Any[ "inverse_problems/likelihood_profiler.md", "inverse_problems/global_sensitivity_analysis.md", "inverse_problems/turing_ode_param_fitting.md", + "inverse_problems/udes.md", "Examples" => Any[ "inverse_problems/examples/ode_fitting_oscillation.md" ] diff --git a/docs/src/inverse_problems/udes.md b/docs/src/inverse_problems/udes.md new file mode 100644 index 0000000000..a085ee9299 --- /dev/null +++ b/docs/src/inverse_problems/udes.md @@ -0,0 +1,100 @@ +# [Fitting universal differential equations](@id udes) +```@raw html +
Environment setup and package installation +``` +The following code sets up an environment for running the code on this page. +```julia +using Pkg +Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. +Pkg.add("Catalyst") +Pkg.add("DataFrames") +Pkg.add("Distributions") +Pkg.add("Lux") +Pkg.add("ModelingToolkitNeuralNets") +Pkg.add("OrdinaryDiffEq") +Pkg.add("Optimisers") +Pkg.add("PEtab") +Pkg.add("Plots") +``` +```@raw html +
+``` +```@raw html +
Quick-start example +``` +The following code provides a brief example of how to perform parameter fitting using the [Optimization.jl](https://github.com/SciML/Optimization.jl) package. +```julia +# Declare the model (a mutual activation loop). +using Catalyst + +# Generate synthetic data to which we will apply the fitting procedure. +using Distributions, OrdinaryDiffEq, Plots + +# Create a UDE using Lu and ModelingToolkitNeuralNets. +using ModelingToolkitNeuralNets, Lux + +# Fit the model using PEtab. +using PEtab, Optimisers + +# Plot the fitted simulation and recovered activation functions. +plot() +plot() +``` +```@raw html +
+``` + \ +Default stuff open meues with env and copy example. + +## Fitting a rate-based UDE + +Short introduction to rate-based UDEs goes here. + +Generate synthetic data. + +Declare UDE + +Fit using PEtab + +Plotting the results + + + +--- + +## [Citations](@id petab_citations) + +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 Lux.jl and PEtab.jl packages: + +```bibtex +@software{pal2025lux, + author = {Pal, Avik}, + title = {{Lux: Explicit Parameterization of Deep Neural Networks in Julia}}, + publisher = {Zenodo}, + year = {2025}, + doi = {10.5281/zenodo.7808903}, + url = {https://doi.org/10.5281/zenodo.7808903} +} +``` +```bibtex +@article{PEtabBioinformatics2025, + title={PEtab.jl: advancing the efficiency and utility of dynamic modelling}, + author={Persson, Sebastian and Fr{\"o}hlich, Fabian and Grein, Stephan and Loman, Torkel and Ognissanti, Damiano and Hasselgren, Viktor and Hasenauer, Jan and Cvijovic, Marija}, + journal={Bioinformatics}, + volume={41}, + number={9}, + pages={btaf497}, + year={2025}, + publisher={Oxford University Press} +} +``` + +--- + +## References + +[^1]: + [Rackauckas, R et al. _Universal differential equations for scientific machine learning_, arXiv (2020).](https://arxiv.org/abs/2001.04385) + +[^2]: + [Loman, T and R, Baker. _Functional and parametric identifiability for universal differential equations applied to chemical reaction networks_, arXiv (2025).](https://arxiv.org/abs/2510.14140) From 6cc250a255c27a29b31f2c0cfc3fa719515131cd Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 15 Jun 2026 17:34:22 +0100 Subject: [PATCH 2/8] init --- Project.toml | 15 ++----- README.md | 2 +- .../finite_state_projection_simulation.md | 11 +++-- docs/src/api/core_api.md | 2 +- docs/src/faqs.md | 6 +-- docs/src/index.md | 4 +- .../catalyst_for_new_julia_users.md | 6 +-- .../introduction_to_catalyst.md | 4 +- .../behaviour_optimisation.md | 9 ++-- .../examples/ode_fitting_oscillation.md | 8 ++-- .../global_sensitivity_analysis.md | 15 ++++--- .../inverse_problems/likelihood_profiler.md | 8 ++-- .../optimization_ode_param_fitting.md | 19 ++++---- .../petab_ode_param_fitting.md | 8 ++-- .../turing_ode_param_fitting.md | 13 +++--- docs/src/model_creation/conservation_laws.md | 6 +-- .../model_creation/coupled_non_crn_models.md | 16 +++---- docs/src/model_creation/dsl_advanced.md | 13 +++--- docs/src/model_creation/events.md | 8 ++-- .../examples/basic_CRN_library.md | 24 +++++------ .../examples/hodgkin_huxley_equation.md | 4 +- .../examples/noise_modelling_approaches.md | 4 +- .../programmatic_generative_linear_pathway.md | 6 +-- .../model_creation/functional_parameters.md | 10 ++--- .../model_file_loading_and_export.md | 6 +-- .../parametric_stoichiometry.md | 6 +-- .../reactionsystem_content_accessing.md | 6 +-- .../model_simulation/ensemble_simulations.md | 4 +- .../interactive_brusselator_simulation.md | 4 +- .../examples/periodic_events_simulation.md | 4 +- .../model_simulation/hybrid_simulations.md | 8 ++-- .../ode_simulation_performance.md | 43 ++++++++----------- .../simulation_introduction.md | 18 ++++---- .../model_simulation/simulation_plotting.md | 4 +- .../simulation_structure_interfacing.md | 8 ++-- .../discrete_space_reaction_systems.md | 2 +- .../discrete_space_simulation_plotting.md | 6 +-- ..._space_simulation_structure_interaction.md | 4 +- .../spatial_ode_simulations.md | 2 +- .../examples/bifurcationkit_codim2.md | 12 +++--- .../bifurcationkit_periodic_orbits.md | 8 ++-- .../nonlinear_solve.md | 6 +-- docs/unpublished/pdes.md | 2 +- .../brownians_and_jumps_composition.jl | 2 +- .../component_based_model_creation.jl | 2 +- test/dsl/dsl_advanced_model_construction.jl | 2 +- test/dsl/dsl_options.jl | 2 +- test/extensions/Project.toml | 8 +--- test/extensions/dspace_simulation_plotting.jl | 2 +- test/extensions/stability_computation.jl | 2 +- test/miscellaneous_tests/api.jl | 2 +- test/network_analysis/conservation_laws.jl | 2 +- .../coupled_equation_crn_systems.jl | 2 +- test/reactionsystem_core/events.jl | 2 +- .../functional_parameters.jl | 2 +- .../parameter_type_designation.jl | 2 +- test/reactionsystem_core/reactionsystem.jl | 2 +- .../symbolic_stoichiometry.jl | 2 +- test/simulation_and_solving/hybrid_models.jl | 2 +- .../jacobian_construction.jl | 2 +- test/simulation_and_solving/simulate_ODEs.jl | 2 +- test/simulation_and_solving/simulate_jumps.jl | 2 +- .../simulation_and_solving/solve_nonlinear.jl | 2 +- .../dspace_reaction_systems.jl | 2 +- .../dspace_reaction_systems_ODEs.jl | 2 +- .../dspace_reaction_systems_space_types.jl | 2 +- .../dspace_simulation_struct_interfacing.jl | 2 +- test/upstream/mtk_problem_inputs.jl | 2 +- test/upstream/mtk_structure_indexing.jl | 2 +- 69 files changed, 208 insertions(+), 224 deletions(-) diff --git a/Project.toml b/Project.toml index ede0c1f3ae..128e2dfacb 100644 --- a/Project.toml +++ b/Project.toml @@ -65,12 +65,8 @@ MacroTools = "0.5.5" Makie = "0.22.1, 0.23, 0.24" ModelingToolkitBase = "1.17" NetworkLayout = "0.4.7" -OrdinaryDiffEqBDF = "1, 2" OrdinaryDiffEqCore = "3.22, 4" -OrdinaryDiffEqDefault = "1, 2" -OrdinaryDiffEqRosenbrock = "1, 2" -OrdinaryDiffEqTsit5 = "1, 2" -OrdinaryDiffEqVerner = "1, 2" +OrdinaryDiffEq = "6, 7" Parameters = "0.12, 0.13" Reexport = "1.0" RuntimeGeneratedFunctions = "0.5.12" @@ -93,12 +89,8 @@ ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" -OrdinaryDiffEqBDF = "6ad6398a-0878-4a85-9266-38940aa047c8" OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8" -OrdinaryDiffEqDefault = "50262376-6c5a-4cf5-baba-aaf4f84d72d7" -OrdinaryDiffEqRosenbrock = "43230ef6-c299-4910-a778-202eb28ce4ce" -OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" -OrdinaryDiffEqVerner = "79d7bb75-1356-48c1-b8c0-6832512096c2" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" @@ -112,7 +104,6 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["DataInterpolations", "DiffEqCallbacks", "DiffEqNoiseProcess", "DomainSets", - "ExplicitImports", "Logging", "NonlinearSolve", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", - "OrdinaryDiffEqRosenbrock", "OrdinaryDiffEqTsit5", "OrdinaryDiffEqVerner", + "ExplicitImports", "Logging", "NonlinearSolve", "OrdinaryDiffEqCore", "OrdinaryDiffEq", "Pkg", "Plots", "Random", "SafeTestsets", "StableRNGs", "StaticArrays", "Statistics", "SteadyStateDiffEq", "StochasticDiffEq", "Test"] diff --git a/README.md b/README.md index 1adb78a7d2..878c5d66b4 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ an ordinary differential equation. ```julia # Fetch required packages. -using Catalyst, OrdinaryDiffEqDefault, Plots +using Catalyst, OrdinaryDiffEq, Plots # Create model. model = @reaction_network begin diff --git a/docs/old_files/unused_tutorials/finite_state_projection_simulation.md b/docs/old_files/unused_tutorials/finite_state_projection_simulation.md index 1f48d029f1..01ca471da3 100644 --- a/docs/old_files/unused_tutorials/finite_state_projection_simulation.md +++ b/docs/old_files/unused_tutorials/finite_state_projection_simulation.md @@ -9,8 +9,7 @@ Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted Pkg.add("Catalyst") Pkg.add("FiniteStateProjection") Pkg.add("JumpProcesses") -Pkg.add("OrdinaryDiffEqDefault") -Pkg.add("OrdinaryDiffEqRosenbrock") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") Pkg.add("SteadyStateDiffEq") ``` @@ -44,7 +43,7 @@ oprob = ODEProblem(fsp_sys, u0, tspan, ps) # Simulate ODE (it can be quite large, so consider performance options). # Plot solution as a heatmap at a specific time point. -using OrdinaryDiffEqRosenbrock, Plots +using OrdinaryDiffEq, Plots osol = solve(oprob, Rodas5P()) heatmap(0:19, 0:19, osol(50.0); xguide = "Y", yguide = "X") ``` @@ -110,7 +109,7 @@ We also plot the full distribution using the `bar` function. Finally, the initia Now, we can finally create an `ODEProblem` using our `FSPSystem`, initial conditions, and the parameters declared previously. We can simulate this `ODEProblem` like any other ODE. ```@example state_projection_one_species -using OrdinaryDiffEqDefault +using OrdinaryDiffEq oprob = ODEProblem(fsp_sys, u0, tspan, ps) osol = solve(oprob) nothing # hide @@ -151,7 +150,7 @@ nothing # hide Finally, we can simulate the model just like in the 1-dimensional case. As we are simulating an ODE with $25⋅25 = 625$ states, we need to make some considerations regarding performance. In this case, we will simply specify the `Rodas5P()` ODE solver (more extensive advice on performance can be found [here](@ref ode_simulation_performance)). Here, we perform a simulation with a long time span ($t = 100.0$), aiming to find the system's steady state distribution. Next, we plot it using the `heatmap` function. ```@example state_projection_multi_species using Plots # hide -using OrdinaryDiffEqRosenbrock +using OrdinaryDiffEq oprob = ODEProblem(fsp_sys, u0, 100.0, ps) osol = solve(oprob, Rodas5P()) heatmap(0:24, 0:24, osol[end]; xguide = "X₂", yguide = "X") @@ -163,7 +162,7 @@ heatmap(0:24, 0:24, osol[end]; xguide = "X₂", yguide = "X") ## [Finite state projection steady state simulations](@id state_projection_steady_state_sim) Previously, we have shown how the [SteadyStateDiffEq.jl](https://github.com/SciML/SteadyStateDiffEq.jl) package can be used to [find an ODE's steady state through forward simulation](@ref steady_state_stability). The same interface can be used for ODEs generated through FiniteStateProjection. Below, we use this to find the steady state of the dimerisation example studied in the last example. ```@example state_projection_multi_species -using SteadyStateDiffEq, OrdinaryDiffEqRosenbrock +using SteadyStateDiffEq, OrdinaryDiffEq ssprob = SteadyStateProblem(fsp_sys, u0, ps) sssol = solve(ssprob, DynamicSS(Rodas5P())) heatmap(0:24, 0:24, sssol; xguide = "X₂", yguide = "X") diff --git a/docs/src/api/core_api.md b/docs/src/api/core_api.md index d843df48fe..6adc9c72e0 100644 --- a/docs/src/api/core_api.md +++ b/docs/src/api/core_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, OrdinaryDiffEqTsit5, StochasticDiffEq, JumpProcesses, Plots +using Catalyst, OrdinaryDiffEq, StochasticDiffEq, JumpProcesses, Plots t = default_t() @parameters β γ @species S(t) I(t) R(t) diff --git a/docs/src/faqs.md b/docs/src/faqs.md index 01cb984cb9..1515be8865 100644 --- a/docs/src/faqs.md +++ b/docs/src/faqs.md @@ -5,7 +5,7 @@ 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, OrdinaryDiffEqNonlinearSolve, OrdinaryDiffEqTsit5, Plots +using Catalyst, OrdinaryDiffEq, Plots rn = @reaction_network ABtoC begin (k₊,k₋), A + B <--> C end @@ -134,7 +134,7 @@ When directly constructing a `ReactionSystem`, we can set the symbolic values to have the desired default values, and this will automatically be propagated through to the equation solvers: ```@example faq3 -using Catalyst, Plots, OrdinaryDiffEqTsit5 +using Catalyst, Plots, OrdinaryDiffEq t = default_t() @parameters β=1e-4 ν=.01 @species S(t)=999.0 I(t)=1.0 R(t)=0.0 @@ -166,7 +166,7 @@ Julia `Symbol`s corresponding to each variable/parameter to their values, or from ModelingToolkitBase symbolic variables/parameters to their values. Using `Symbol`s we have ```@example faq4 -using Catalyst, OrdinaryDiffEqTsit5 +using Catalyst, OrdinaryDiffEq rn = @reaction_network begin α, S + I --> 2I β, I --> R diff --git a/docs/src/index.md b/docs/src/index.md index ce799b43a5..88b78608e7 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -73,7 +73,7 @@ Pkg.add("Catalyst") Many Catalyst features require the installation of additional packages. E.g. for ODE-solving and simulation plotting ```julia -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` is also needed. @@ -116,7 +116,7 @@ an ordinary differential equation. ```@example home_simple_example # Fetch required packages. -using Catalyst, OrdinaryDiffEqDefault, Plots +using Catalyst, OrdinaryDiffEq, Plots # Create model. model = @reaction_network begin 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 c12829cb29..9d2103e021 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 install an ODE solver from a sub-library of the larger `OrdinaryDiffEq` package, and install the `Plots` package for making graphs. We will import the recommended default solver from the `OrdinaryDiffEqDefault` sub-library. A full list of `OrdinaryDiffEq` solver sublibraries can be found on the sidebar of [this page](https://docs.sciml.ai/OrdinaryDiffEq/stable/). +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 install the `OrdinaryDiffEq` package, which provides ODE solvers, and the `Plots` package for making graphs. A full list of available `OrdinaryDiffEq` solvers can be found in [its documentation](https://docs.sciml.ai/OrdinaryDiffEq/stable/). ```julia -Pkg.add("OrdinaryDiffEqDefault") +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 OrdinaryDiffEqDefault +using OrdinaryDiffEq using Plots ``` Here, if we restart Julia, these `using` commands *must be rerun*. diff --git a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md index 9176aa77e0..d515e2f8ae 100644 --- a/docs/src/introduction_to_catalyst/introduction_to_catalyst.md +++ b/docs/src/introduction_to_catalyst/introduction_to_catalyst.md @@ -20,7 +20,7 @@ Pkg.activate("catalyst_introduction") # packages we will use in this tutorial Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqTsit5") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") Pkg.add("Latexify") Pkg.add("JumpProcesses") @@ -29,7 +29,7 @@ Pkg.add("StochasticDiffEq") We next load the basic packages we'll need for our first example: ```@example tut1 -using Catalyst, OrdinaryDiffEqTsit5, Plots, Latexify +using Catalyst, OrdinaryDiffEq, Plots, Latexify ``` Let's start by using the Catalyst [`@reaction_network`](@ref) macro to specify a diff --git a/docs/src/inverse_problems/behaviour_optimisation.md b/docs/src/inverse_problems/behaviour_optimisation.md index 1f2bfa6c21..c1bc34ada9 100644 --- a/docs/src/inverse_problems/behaviour_optimisation.md +++ b/docs/src/inverse_problems/behaviour_optimisation.md @@ -9,8 +9,9 @@ Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted Pkg.add("Catalyst") Pkg.add("OptimizationBase") Pkg.add("OptimizationBBO") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") +Pkg.add("SciMLLogging") ``` ```@raw html @@ -35,7 +36,7 @@ end ``` To demonstrate this pulsing behaviour we will simulate the system for an example parameter set. We select an initial condition (`u0`) so the system begins in a steady state. ```@example behaviour_optimization -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots, SciMLLogging example_p = [:pX => 0.1, :pY => 1.0, :pZ => 1.0] tspan = (0.0, 50.0) example_u0 = [:X => 0.1, :Y => 0.1, :Z => 1.0] @@ -52,7 +53,7 @@ function pulse_amplitude(p, _) p = Dict([:pX => p[1], :pY => p[2], :pZ => p[2]]) u0 = [:X => p[:pX], :Y => p[:pX]*p[:pY], :Z => p[:pZ]/p[:pY]^2] oprob_local = remake(oprob; u0, p) - sol = solve(oprob_local; verbose = false, maxiters = 10000) + sol = solve(oprob_local; verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return -(maximum(sol[:Z]) - sol[:Z][1]) end @@ -60,7 +61,7 @@ nothing # hide ``` This objective function takes two arguments (a parameter value `p`, and an additional one which we will ignore but is discussed in a note [here](@ref optimization_parameter_fitting_basics)). 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 objective function provided, input parameter set. Finally, Optimization.jl finds the function's *minimum value*, so to find the *maximum* relative pulse amplitude, we make our objective function return the negative pulse amplitude. -As described [in our tutorial on parameter fitting using Optimization.jl](@ref optimization_parameter_fitting_basics) we use `remake`, `verbose = false`, `maxiters = 10000`, and a check on the simulations return code, all providing various advantages to the optimisation procedure (as explained in that tutorial). +As described [in our tutorial on parameter fitting using Optimization.jl](@ref optimization_parameter_fitting_basics) we use `remake`, `verbose = SciMLLogging.None()`, `maxiters = 10000`, and a check on the simulations return code, all providing various advantages to the optimisation procedure (as explained in that tutorial). Just like for [parameter fitting](@ref optimization_parameter_fitting_basics), we create an `OptimizationProblem` using our objective function, and some initial guess of the parameter values. We also [set upper and lower bounds](@ref optimization_parameter_fitting_constraints) 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 diff --git a/docs/src/inverse_problems/examples/ode_fitting_oscillation.md b/docs/src/inverse_problems/examples/ode_fitting_oscillation.md index 3f06866f16..7829614f5d 100644 --- a/docs/src/inverse_problems/examples/ode_fitting_oscillation.md +++ b/docs/src/inverse_problems/examples/ode_fitting_oscillation.md @@ -9,8 +9,9 @@ Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted Pkg.add("Catalyst") Pkg.add("OptimizationBase") Pkg.add("OptimizationOptimisers") -Pkg.add("OrdinaryDiffEqRosenbrock") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") +Pkg.add("SciMLLogging") Pkg.add("SciMLSensitivity") ``` ```@raw html @@ -23,9 +24,10 @@ In this example we will use [Optimization.jl](https://github.com/SciML/Optimizat First, we fetch the required packages. ```@example pe_osc_example using Catalyst -using OrdinaryDiffEqRosenbrock +using OrdinaryDiffEq using OptimizationBase using OptimizationOptimisers # Required for the ADAM optimizer. +using SciMLLogging using SciMLSensitivity # Required for the `AutoZygote()` automatic differentiation option. ``` @@ -78,7 +80,7 @@ function optimize_p(pinit, tend, p = set_p(prob, p) newtimes = filter(<=(tend), sample_times) newprob = remake(prob; p) - sol = Array(solve(newprob, Rosenbrock23(); saveat = newtimes, verbose = false, maxiters = 10000)) + sol = Array(solve(newprob, Rosenbrock23(); saveat = newtimes, verbose = SciMLLogging.None(), maxiters = 10000)) loss = sum(abs2, sol .- sample_vals[:, 1:size(sol,2)]) return loss end diff --git a/docs/src/inverse_problems/global_sensitivity_analysis.md b/docs/src/inverse_problems/global_sensitivity_analysis.md index ea98f82263..f73675443e 100644 --- a/docs/src/inverse_problems/global_sensitivity_analysis.md +++ b/docs/src/inverse_problems/global_sensitivity_analysis.md @@ -9,7 +9,8 @@ Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted Pkg.add("Catalyst") Pkg.add("GlobalSensitivity") Pkg.add("Plots") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") +Pkg.add("SciMLLogging") ``` ```@raw html @@ -19,7 +20,7 @@ Pkg.add("OrdinaryDiffEqDefault") ``` The following code provides a brief example of how to perform global sensitivity analysis using the [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) package. ```julia -using Catalyst, GlobalSensitivity, OrdinaryDiffEqDefault, SymbolicIndexingInterface +using Catalyst, GlobalSensitivity, OrdinaryDiffEq, SciMLLogging, SymbolicIndexingInterface # Designates a model, parameter set, and set of initial conditions. # For this infectious disease model we will determine the peak number of cases's sensitivity to the parameters. @@ -38,7 +39,7 @@ function peak_cases(p) # Updates the ODEProblem with teh proposed parameter set. p = p_setter(oprob_base, p) oprob = remake(oprob_base; p) - sol = solve(oprob; maxiters = 100000, verbose = false) + sol = solve(oprob; maxiters = 100000, verbose = SciMLLogging.None()) return maximum(sol[:I]) end @@ -74,7 +75,7 @@ end ``` We will study the peak number of infected cases's ($max(I(t))$) sensitivity to the system's three parameters. We create a function which simulates the system from a given initial condition and measures this property: ```@example gsa_1 -using OrdinaryDiffEqDefault +using OrdinaryDiffEq, SciMLLogging u0 = [:S => 999.0, :I => 1.0, :E => 0.0, :R => 0.0] p_dummy = [:β => 0.0, :a => 0.0, :γ => 0.0] @@ -83,7 +84,7 @@ oprob_base = ODEProblem(seir_model, u0, (0.0, 10000.0), p_dummy) function peak_cases(p) ps = [:β => p[1], :a => p[2], :γ => p[3]] oprob = remake(oprob_base; p = ps) - sol = solve(oprob; maxiters = 100000, verbose = false) + sol = solve(oprob; maxiters = 100000, verbose = SciMLLogging.None()) SciMLBase.successful_retcode(sol) || return Inf return maximum(sol[:I]) end @@ -106,7 +107,7 @@ on the domain $10^β ∈ (-3.0,-1.0)$, $10^a ∈ (-2.0,0.0)$, $10^γ ∈ (-2.0,0 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_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`. + - 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 = SciMLLogging.None()` 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]]`. @@ -174,7 +175,7 @@ Previously, we have demonstrated GSA on functions with scalar outputs. However, function peak_cases_2(p) ps = [:β => p[1], :a => p[2], :γ => p[3]] oprob = remake(oprob_base; p = ps) - sol = solve(oprob; maxiters = 100000, verbose = false) + sol = solve(oprob; maxiters = 100000, verbose = SciMLLogging.None()) SciMLBase.successful_retcode(sol) || return Inf return [maximum(sol[:E]), maximum(sol[:I])] end diff --git a/docs/src/inverse_problems/likelihood_profiler.md b/docs/src/inverse_problems/likelihood_profiler.md index 549ef9ba72..4b2bbf1553 100644 --- a/docs/src/inverse_problems/likelihood_profiler.md +++ b/docs/src/inverse_problems/likelihood_profiler.md @@ -14,7 +14,7 @@ Pkg.add("Plots") Pkg.add("PEtab") Pkg.add("Optim") Pkg.add("OptimizationLBFGSB") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") ``` ```@raw html @@ -32,7 +32,7 @@ log_growth = @reaction_network begin end # Generate some (synthetic) data for the fitting procedure. -using Distributions, OrdinaryDiffEqDefault, Plots +using Distributions, OrdinaryDiffEq, Plots t_measurement = 1.0:5:150.0 u0 = [:X => 1.0] p_true = [:r => 0.1, :K => 100.0] @@ -87,7 +87,7 @@ end ``` We generate two synthetic datasets, one of higher quality than the other: ```@example likelihood_profiler_pract_ident -using Distributions, OrdinaryDiffEqDefault +using Distributions, OrdinaryDiffEq function generate_data(u0, σ, tend) t_measurement = range(1.0, tend; length = 50) p_true = Dict([:r => 0.1, :K => 100.0]) @@ -165,7 +165,7 @@ log_growth = @reaction_network begin end # Generate synthetic data. -using Distributions, OrdinaryDiffEqDefault, Plots +using Distributions, OrdinaryDiffEq, Plots t_measurement = 1.0:5:150.0 u0 = [:X => 1.0] p_true = [:r => 0.1, :K => 100.0] diff --git a/docs/src/inverse_problems/optimization_ode_param_fitting.md b/docs/src/inverse_problems/optimization_ode_param_fitting.md index e3b2d87cbf..514ea9a825 100644 --- a/docs/src/inverse_problems/optimization_ode_param_fitting.md +++ b/docs/src/inverse_problems/optimization_ode_param_fitting.md @@ -11,8 +11,9 @@ Pkg.add("OptimizationBase") Pkg.add("OptimizationNLopt") Pkg.add("OptimizationEvolutionary") Pkg.add("OptimizationOptimJL") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") +Pkg.add("SciMLLogging") ``` ```@raw html @@ -22,7 +23,7 @@ Pkg.add("Plots") ``` The following code provides a brief example of how to perform parameter fitting using the [Optimization.jl](https://github.com/SciML/Optimization.jl) package. ```julia -using Catalyst, OrdinaryDiffEqDefault, OptimizationBase, OptimizationNLopt, SymbolicIndexingInterface +using Catalyst, OrdinaryDiffEq, OptimizationBase, OptimizationNLopt, SciMLLogging, SymbolicIndexingInterface # What we know: A model, an initial condition, and sampled datapoints. rn = @reaction_network begin @@ -43,7 +44,7 @@ function loss(p, (oprob_base, p_setter, t_samples, X_samples)) oprob = remake(oprob_base; p) # Simulate the model. If sucesfull, return sum-of-squares distance as loss. - sol = solve(oprob; saveat = t_samples, verbose = false, maxiters = 10000) + sol = solve(oprob; saveat = t_samples, verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum(abs2, sol[:X] .- X_samples) end @@ -90,7 +91,7 @@ u0 = [:S => 1.0, :E => 1.0, :SE => 0.0, :P => 0.0] ps_true = [:kB => 1.0, :kD => 0.1, :kP => 0.5] # Generate synthetic data. -using OrdinaryDiffEqDefault +using OrdinaryDiffEq, SciMLLogging oprob_true = ODEProblem(rn, u0, (0.0, 10.0), ps_true) true_sol = solve(oprob_true) data_sol = solve(oprob_true; saveat = 1.0) @@ -113,14 +114,14 @@ oprob_base = ODEProblem(rn, u0, (0.0, 10.0), ps_init) function objective_function(p, _) p = Pair.([:kB, :kD, :kP], p) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = false, maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol .- data_vals) .^2) end ``` When our optimisation algorithm searches parameter space it will likely consider many highly non-plausible parameter sets. To better handle this we: 1. Add `maxiters = 10000` to our `solve` command. As most well-behaved ODEs can be solved in relatively few timesteps, this speeds up the optimisation procedure by preventing us from spending too much time trying to simulate (for the model) unsuitable parameter sets. -2. Add `verbose = false` to our `solve` command. This prevents (potentially a very large number of) warnings from being printed to our output as unsuitable parameter sets are simulated. +2. Add `verbose = SciMLLogging.None()` to our `solve` command. This prevents (potentially a very large number of) warnings from being printed to our output as unsuitable parameter sets are simulated. 3. Add the line `SciMLBase.successful_retcode(sol) || return Inf`, which returns an infinite value for parameter sets which does not lead to successful simulations. To improve optimisation performance, rather than creating a new `ODEProblem` in each iteration, we pre-declare one which we [apply `remake` to](@ref simulation_structure_interfacing_problems_remake). We also use the `saveat = data_ts, save_idxs = :P` arguments to only save the values of the measured species at the measured time points. @@ -194,7 +195,7 @@ In this case we simply modify our objective function to take this into account: function objective_function_S_P(p, _) p = Pair.([:kB, :kD, :kP], p) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = [:S, :P], verbose = false, maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = [:S, :P], verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol[:S] .- data_vals_S) .^2 + (sol[:P] .- data_vals_P) .^2) end @@ -228,7 +229,7 @@ If we from previous knowledge know that $kD = 0.1$, and only want to fit the val function objective_function_known_kD(p, _) p = Pair.([:kB, :kD, :kP], [p[1], 0.1, p[2]]) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = false, maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol .- data_vals) .^2) end @@ -268,7 +269,7 @@ corresponds to the same true parameter values as used previously (`[:kB => 1.0, function objective_function_logtransformed(p, _) p = Pair.([:kB, :kD, :kP], 10.0 .^ p) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = false, maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol .- data_vals) .^2) end diff --git a/docs/src/inverse_problems/petab_ode_param_fitting.md b/docs/src/inverse_problems/petab_ode_param_fitting.md index 86e76b3b00..21b1fb2335 100644 --- a/docs/src/inverse_problems/petab_ode_param_fitting.md +++ b/docs/src/inverse_problems/petab_ode_param_fitting.md @@ -40,7 +40,7 @@ u0 = [:S => 1.0, :E => 1.0, :SE => 0.0, :P => 0.0] p_true = [:kB => 1.0, :kD => 0.1, :kP => 0.5] # Generate synthetic data. -using OrdinaryDiffEqDefault +using OrdinaryDiffEq oprob_true = ODEProblem(rn, u0, (0.0, 10.0), p_true) true_sol = solve(oprob_true) data_sol = solve(oprob_true; saveat = 1.0) @@ -380,7 +380,7 @@ u0 = [:E => 1.0, :SE => 0.0, :P => 0.0] p_true = [:kB => 1.0, :kD => 0.1, :kP => 0.5] # Simulate data. -using OrdinaryDiffEqDefault +using OrdinaryDiffEq t1, d1 = let oprob_true = ODEProblem(rn, [:S => 1.0; u0], (0.0, 10.0), p_true) data_sol = solve(oprob_true; saveat = 1.0) @@ -533,7 +533,7 @@ end u0 = [:SE => 0.0, :P => 0.0] p_true = [:kB => 1.0, :kD => 0.1, :kP => 0.5, :S0=>1.0, :E0 => 1.0] -using OrdinaryDiffEqDefault +using OrdinaryDiffEq oprob_true = ODEProblem(rn, u0, (0.0, 10.0), p_true) true_sol = solve(oprob_true) data_sol = solve(oprob_true; saveat = 1.0) @@ -570,7 +570,7 @@ Here is an example, adapted from the [more detailed PEtab.jl documentation](https://sebapersson.github.io/PEtab.jl/stable/configuration/default_options) ```@example petab1 -using OrdinaryDiffEqRosenbrock +using OrdinaryDiffEq PEtabODEProblem( petab_model, odesolver = ODESolver(Rodas5P(), abstol = 1e-8, reltol = 1e-8), diff --git a/docs/src/inverse_problems/turing_ode_param_fitting.md b/docs/src/inverse_problems/turing_ode_param_fitting.md index 2c4be60422..7a8044fe35 100644 --- a/docs/src/inverse_problems/turing_ode_param_fitting.md +++ b/docs/src/inverse_problems/turing_ode_param_fitting.md @@ -8,8 +8,9 @@ using Pkg Pkg.activate(".") Pkg.add("Catalyst") Pkg.add("Distributions") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") +Pkg.add("SciMLLogging") Pkg.add("StatsPlots") Pkg.add("SymbolicIndexingInterface") Pkg.add("Turing") @@ -30,7 +31,7 @@ sir = @reaction_network begin end # Generate some (synthetic) data for the fitting procedure. -using Distributions, OrdinaryDiffEqDefault, Plots +using Distributions, OrdinaryDiffEq, Plots, SciMLLogging t_measurement = 1.0:100.0 u0 = [:S => 999.0, :I => 1.0, :R => 0.0] p_true = [:γ => 0.0003, :ν => 0.1] @@ -56,7 +57,7 @@ using Turing, MCMCChains # Simulate the model for parameter values γ, ν. Saves the solution at the measurement times. p = setp_oop(oprob_base, [γ, ν]) oprob_base = remake(oprob_base; p) - sol = solve(oprob_base; saveat, verbose = false, maxiters = 10000) + sol = solve(oprob_base; saveat, verbose = SciMLLogging.None(), maxiters = 10000) # If simulation was unsuccessful, the likelihood is -Inf. if !SciMLBase.successful_retcode(sol) @@ -104,7 +105,7 @@ end ``` From an initial condition where only a small fraction of the population is in the infected state, the model exhibits a peak of infections, after which the epidemic subsides. ```@example turing_paramfit -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots, SciMLLogging u0 = [:S => 999.0, :I => 1.0, :R => 0.0] p_true = [:γ => 0.0005, :ν => 0.1] oprob_true = ODEProblem(sir, u0, 100.0, p_true) @@ -142,7 +143,7 @@ setp_oop = SymbolicIndexingInterface.setp_oop(oprob_true, [:γ, :ν]) # Simulate the model for parameter values γ, ν. Saves the solution at the measurement times. p = setp_oop(oprob_base, [γ, ν]) oprob_base = remake(oprob_base; p) - sol = solve(oprob_base; saveat, verbose = false, maxiters = 10000) + sol = solve(oprob_base; saveat, verbose = SciMLLogging.None(), maxiters = 10000) # If simulation was unsuccessful, the likelihood is -Inf. if !SciMLBase.successful_retcode(sol) @@ -159,7 +160,7 @@ nothing # hide ``` Some specific comments regarding how we have declared the model above: -- Like for [normal parameter fitting](@ref optimization_parameter_fitting_basics), we use the `maxiters = 10000` (to prevent spending a long time simulating unfeasible parameter sets) and `verbose = false` (to prevent unnecessary printing of warning messages) arguments to `solve`. +- Like for [normal parameter fitting](@ref optimization_parameter_fitting_basics), we use the `maxiters = 10000` (to prevent spending a long time simulating unfeasible parameter sets) and `verbose = SciMLLogging.None()` (to prevent unnecessary printing of warning messages) arguments to `solve`. - Again, we need to handle parameter sets where the model cannot be successfully simulated. Here, we use `Turing.@addlogprob! -Inf` to set a non-existent likelihood, and `return nothing` to stop further evaluation of the specific parameter set. - Just like for normal parameter fitting, we wish to [fit parameters on a log scale](@ref optimization_parameter_fitting_log_scale). Here we do so by declaring log-scaled prior distributions. - Here we assume that we (correctly) know that the noise is normally distributed. However, we assume that we *do not know the standard deviation*. Instead, we make the standard deviation a third parameter whose value we infer as part of the inference process. More complicated noise formulas can be used (and are sometimes even advisable[^2]). diff --git a/docs/src/model_creation/conservation_laws.md b/docs/src/model_creation/conservation_laws.md index a976869487..339f5bd288 100644 --- a/docs/src/model_creation/conservation_laws.md +++ b/docs/src/model_creation/conservation_laws.md @@ -8,7 +8,7 @@ using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") Pkg.add("Latexify") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -27,7 +27,7 @@ end ``` If we simulate it, we note that while the concentrations of $X₁$ and $X₂$ change throughout the simulation, the total concentration of $X$ ($= X₁ + X₂$) is constant: ```@example conservation_laws -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0 = [:X₁ => 80.0, :X₂ => 20.0] ps = [:k₁ => 10.0, :k₂ => 2.0] oprob = ODEProblem(rs, u0, (0.0, 1.0), ps) @@ -66,7 +66,7 @@ Here, Catalyst encodes all conserved quantities in a single, [vector-valued](@re Practically, the `remove_conserved = true` argument can be provided when a `ReactionSystem` is converted to an `ODEProblem`: ```@example conservation_laws -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0 = [:X₁ => 80.0, :X₂ => 20.0] ps = [:k₁ => 10.0, :k₂ => 2.0] oprob = ODEProblem(rs, u0, (0.0, 1.0), ps; remove_conserved = true) diff --git a/docs/src/model_creation/coupled_non_crn_models.md b/docs/src/model_creation/coupled_non_crn_models.md index 830967f0c9..b571847ffa 100644 --- a/docs/src/model_creation/coupled_non_crn_models.md +++ b/docs/src/model_creation/coupled_non_crn_models.md @@ -7,7 +7,7 @@ The following code sets up an environment for running the code on this page. using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqTsit5") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -17,7 +17,7 @@ Pkg.add("Plots") Non-reaction model components can be inserted directly in a Catalyst model. Here we will briefly describe the simplest case: adding an ODE to a model declared through the `@reaction_network` DSL. The equation is added using the `@equations` option, after which the equation is written (with `D(V)` denoting differential with respect to time). ```julia -using Catalyst, OrdinaryDiffEqDefault, Plots +using Catalyst, OrdinaryDiffEq, Plots # Create model with a ODE for the variable `V` (e.g. denoting Volume). rn = @reaction_network begin @@ -56,7 +56,7 @@ The easiest way to include ODEs and algebraic equations is to just include them when using the DSL to specify a model. Here we include an ODE for $V(t)$ along with degradation and production reactions for $P(t)$: ```@example ceq1 -using Catalyst, OrdinaryDiffEqTsit5, Plots +using Catalyst, OrdinaryDiffEq, Plots rn = @reaction_network growing_cell begin # the growth rate @@ -90,7 +90,7 @@ plot(sol) As an alternative to the previous approach, we could have also constructed our `ReactionSystem` all at once using the symbolic interface: ```@example ceq2 -using Catalyst, OrdinaryDiffEqTsit5, Plots +using Catalyst, OrdinaryDiffEq, Plots t = default_t() D = default_time_deriv() @@ -123,7 +123,7 @@ this through `D = default_time_deriv()`. Here, `D(V)` denotes the differential of the variable `V` with respect to time. ```@example ceq2b -using Catalyst, OrdinaryDiffEqTsit5, Plots +using Catalyst, OrdinaryDiffEq, Plots t = default_t() D = default_time_deriv() @@ -188,7 +188,7 @@ Here we note that: The model can be simulated and plotted using normal syntax, providing an initial condition for `N` and a value for `d` through the normal initial condition and parameter value vectors. ```@example coupled_diff_eqs -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0 = [:Xᵢ => 1.0, :Xₐ => 0.0, :N => 1.0] ps = [:kₐ => 2.0, :kᵢ => 0.5, :d => 1.0] oprob = ODEProblem(rs, u0, 10.0, ps) @@ -230,7 +230,7 @@ nothing # hide ``` Next, we provide `guesses` to our `ODEProblem` as an additional argument. Furthermore, we will use the `mtkcompile = true` argument, which is always required when simulating models containing algebraic equations. With these modifications, the model can be simulated using standard workflows. ```@example coupled_eqs_alg_eq -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots oprob = ODEProblem(algebraic_crn, u0, 10.0, ps; mtkcompile = true, guesses) sol = solve(oprob) plot(sol) @@ -356,7 +356,7 @@ end ``` The model can be simulated using standard syntax. However, poissonians can only be simulated through `HybridProblem`s. The reason is that the variable subject to the poissonian is both governed by a differential equation and discrete jump process. Below, we perform a hybrid simulation for our model, where the $X$'s reactions and the poissonian are simulated as jumps, while $V$'s governing dynamics are simulated as a *piecewise deterministic markov process*. ```@example coupled_eqs_noise -using JumpProcesses, OrdinaryDiffEqTsit5 +using JumpProcesses, OrdinaryDiffEq u0 = [:X => 2.0, :V => 4.0] ps = [:p => 1.2, :d => 0.1, :λ => 2.0] jprob = HybridProblem(noisy_cell, u0, (0.0, 10.0), ps) diff --git a/docs/src/model_creation/dsl_advanced.md b/docs/src/model_creation/dsl_advanced.md index 8ab3ad8fad..4f3cf128c5 100644 --- a/docs/src/model_creation/dsl_advanced.md +++ b/docs/src/model_creation/dsl_advanced.md @@ -8,8 +8,7 @@ using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") Pkg.add("Latexify") -Pkg.add("OrdinaryDiffEqDefault") -Pkg.add("OrdinaryDiffEqTsit5") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -118,7 +117,7 @@ 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: ```@example dsl_advanced_defaults -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0 = [] tspan = (0.0, 10.0) p = [] @@ -160,7 +159,7 @@ 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: ```@example dsl_advanced_defaults -using OrdinaryDiffEqTsit5 +using OrdinaryDiffEq u0 = [] p = [:X₀ => 1.0, :p => 1.0, :d => 0.5] oprob = ODEProblem(rn, u0, tspan, p) @@ -273,7 +272,7 @@ end ``` Now, we can also declare our initial conditions and parameter values as vectors as well: ```@example dsl_advanced_vector_variables -using OrdinaryDiffEqDefault, Plots # hide +using OrdinaryDiffEq, Plots # hide u0 = [:X => [0.0, 2.0]] tspan = (0.0, 1.0) ps = [:k => [1.0, 2.0]] @@ -352,7 +351,7 @@ end ``` We can now simulate our model using normal syntax (initial condition values for observables should not, and can not, be provided): ```@example dsl_advanced_observables -using OrdinaryDiffEqTsit5 +using OrdinaryDiffEq u0 = [:X => 1.0, :Y => 2.0, :XY => 0.0] tspan = (0.0, 10.0) ps = [:kB => 1.0, :kD => 1.5] @@ -499,7 +498,7 @@ 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 OrdinaryDiffEqDefault, Plots # hide +using OrdinaryDiffEq, Plots # hide u0 = [X => 0.1] tspan = (0.0, 10.0) ps = [p => 1.0, d => 0.2] diff --git a/docs/src/model_creation/events.md b/docs/src/model_creation/events.md index 258a59c774..4c94e7c47b 100644 --- a/docs/src/model_creation/events.md +++ b/docs/src/model_creation/events.md @@ -7,7 +7,7 @@ The following code sets up an environment for running the code on this page. using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqTsit5") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -18,7 +18,7 @@ Pkg.add("Plots") ``` In this quick-start example we show how to add a "continuous event" directly to a model declared through the DSL. ```julia -using Catalyst, OrdinaryDiffEqDefault, Plots +using Catalyst, OrdinaryDiffEq, Plots # The `@continuous_events option is used to declare events. # When the condition on the event left hold, the effect on the right is triggered. @@ -66,7 +66,7 @@ lower-level approach for creating events via the DifferentialEquations.jl callback interface is illustrated [here](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/) tutorial. ```@example events1 -using Catalyst, OrdinaryDiffEqTsit5, Plots +using Catalyst, OrdinaryDiffEq, Plots rn = @reaction_network growing_cell begin # the growth rate @@ -142,7 +142,7 @@ on event handling using callbacks. Let's repeat the previous two models using the symbolic interface. We first create our equations and unknowns/species again ```@example events2 -using Catalyst, OrdinaryDiffEqTsit5, Plots +using Catalyst, OrdinaryDiffEq, Plots t = default_t() D = default_time_deriv() diff --git a/docs/src/model_creation/examples/basic_CRN_library.md b/docs/src/model_creation/examples/basic_CRN_library.md index 17cb7e4eca..f2ec71ede1 100644 --- a/docs/src/model_creation/examples/basic_CRN_library.md +++ b/docs/src/model_creation/examples/basic_CRN_library.md @@ -7,7 +7,7 @@ The following code sets up an environment for running the code on this page. using Pkg Pkg.activate(".") Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("StochasticDiffEq") Pkg.add("JumpProcesses") Pkg.add("Plots") @@ -36,7 +36,7 @@ nothing # hide ``` We can now simulate our model using all three interpretations. First, we perform a reaction rate equation-based ODE simulation: ```@example crn_library_birth_death -using OrdinaryDiffEqDefault +using OrdinaryDiffEq oprob = ODEProblem(bd_process, u0, tspan, ps) osol = solve(oprob) nothing # hide @@ -69,7 +69,7 @@ plot(oplt, splt, jplt; lw = 3, size=(800,700), layout = (3,1)) ## [Two-state model](@id basic_CRN_library_two_states) The two-state model describes a component (here called $X$) which can exist in two different forms (here called $X₁$ and $X₂$). It switches between these forms at linear rates. First, we simulate the model using both ODEs and SDEs: ```@example crn_library_two_states -using Catalyst, OrdinaryDiffEqDefault, StochasticDiffEq, Plots +using Catalyst, OrdinaryDiffEq, StochasticDiffEq, Plots two_state_model = @reaction_network begin (k₁,k₂), X₁ <--> X₂ end @@ -113,7 +113,7 @@ u0 = [:S => 301, :E => 100, :SE => 0, :P => 0] tspan = (0., 100.) ps = [:kB => 0.00166, :kD => 0.0001, :kP => 0.1] -using OrdinaryDiffEqDefault +using OrdinaryDiffEq oprob = ODEProblem(mm_system, u0, tspan, ps) osol = solve(oprob) @@ -148,7 +148,7 @@ end ``` Here, we will simulate it using the ODE and jump approaches, and do so across three different scales of population sizes. ```@example crn_library_logistic_growth -using OrdinaryDiffEqDefault, JumpProcesses, Plots +using OrdinaryDiffEq, JumpProcesses, Plots tspan = (0.0, 10.0) oprob_1 = ODEProblem(logistic_growth, [:u => 1], tspan, [:r => 1.0, :K => 10.0]) oprob_2 = ODEProblem(logistic_growth, [:u => 10*1], tspan, [:r => 1.0, :K => 10*10.0]) @@ -184,7 +184,7 @@ end ``` First, we perform a deterministic ODE simulation: ```@example crn_library_sir -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0 = [:S => 99, :I => 1, :R => 0] tspan = (0.0, 500.0) ps = [:α => 0.001, :β => 0.01] @@ -230,7 +230,7 @@ Below, we perform a simple deterministic ODE simulation of the system. Next, we In two separate plots. ```@example crn_library_cc -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0 = [:S₁ => 1.0, :C => 0.05, :S₂ => 1.2, :S₁C => 0.0, :CP => 0.0, :P => 0.0] tspan = (0., 15.) ps = [:k₁ => 5.0, :k₂ => 5.0, :k₃ => 100.0] @@ -257,7 +257,7 @@ end ``` We can simulate the model for two different initial conditions, demonstrating the existence of two different stable steady states. ```@example crn_library_wilhelm -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0_1 = [:X => 1.5, :Y => 0.5] u0_2 = [:X => 2.5, :Y => 0.5] tspan = (0., 10.) @@ -285,7 +285,7 @@ A simple example of such a loop is a transcription factor which activates its ow We simulate the self-activation loop from a single initial condition using both deterministic (ODE) and stochastic (jump) simulations. We note that while the deterministic simulation reaches a single steady state, the stochastic one switches between two different states. ```@example crn_library_self_activation -using JumpProcesses, OrdinaryDiffEqDefault, Plots +using JumpProcesses, OrdinaryDiffEq, Plots u0 = [:X => 4] tspan = (0.0, 1000.0) ps = [:v₀ => 0.1, :v => 2.0, :K => 10.0, :n => 2, :d => 0.1] @@ -317,7 +317,7 @@ 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 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 OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0 = [:X => 1.0, :Y => 1.0] tspan = (0., 50.) ps1 = [:A => 1.0, :B => 1.0] @@ -346,7 +346,7 @@ end ``` Whether the Repressilator oscillates or not depends on its parameter values. Here, we will perform deterministic (ODE) simulations for two different values of $K$, showing that it oscillates for one value and not the other one. Next, we will perform stochastic (SDE) simulations for both $K$ values, showing that the stochastic model can sustain oscillations in both cases. This is an example of the phenomena of *noise-induced oscillation*. ```@example crn_library_brusselator -using OrdinaryDiffEqDefault, StochasticDiffEq, Plots +using OrdinaryDiffEq, StochasticDiffEq, Plots u0 = [:X => 50.0, :Y => 15.0, :Z => 15.0] tspan = (0., 200.) ps1 = [:v => 10.0, :K => 20.0, :n => 3, :d => 0.1] @@ -387,7 +387,7 @@ end ``` Here we simulate the model for a single initial condition, showing both time-state space and phase space how it reaches a [*strange attractor*](https://www.dynamicmath.xyz/strange-attractors/). ```@example crn_library_chaos -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0 = [:X => 1.5, :Y => 1.5, :Z => 1.5] tspan = (0.0, 50.0) p = [:k1 => 2.1, :k2 => 0.7, :k3 => 2.9, :k4 => 1.1, :k5 => 1.0, :k6 => 0.5, :k7 => 2.7] diff --git a/docs/src/model_creation/examples/hodgkin_huxley_equation.md b/docs/src/model_creation/examples/hodgkin_huxley_equation.md index 8960ad71c2..4cfe3cf5ec 100644 --- a/docs/src/model_creation/examples/hodgkin_huxley_equation.md +++ b/docs/src/model_creation/examples/hodgkin_huxley_equation.md @@ -9,7 +9,7 @@ Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted Pkg.add("Catalyst") Pkg.add("ModelingToolkitBase") Pkg.add("NonlinearSolveFirstOrder") -Pkg.add("OrdinaryDiffEqRosenbrock") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -35,7 +35,7 @@ complete model. We begin by importing some necessary packages: ```@example hh1 -using ModelingToolkitBase, Catalyst, NonlinearSolveFirstOrder, Plots, OrdinaryDiffEqRosenbrock +using ModelingToolkitBase, Catalyst, NonlinearSolveFirstOrder, Plots, OrdinaryDiffEq ``` ## Building the model via the Catalyst DSL diff --git a/docs/src/model_creation/examples/noise_modelling_approaches.md b/docs/src/model_creation/examples/noise_modelling_approaches.md index 1a2bd66993..021361ec87 100644 --- a/docs/src/model_creation/examples/noise_modelling_approaches.md +++ b/docs/src/model_creation/examples/noise_modelling_approaches.md @@ -9,7 +9,7 @@ Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted Pkg.add("Catalyst") Pkg.add("DataInterpolations") Pkg.add("Distributions") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") Pkg.add("StochasticDiffEq") ``` @@ -74,7 +74,7 @@ nothing # hide ``` Next, we again perform 4 simulations. While the individual trajectories are performed using deterministic simulations, the randomised parameter values create heterogeneity across the ensemble. ```@example noise_modelling_approaches -using OrdinaryDiffEqDefault +using OrdinaryDiffEq oprob = ODEProblem(repressilator, u0, tend, ps) eprob_extrinsic = EnsembleProblem(oprob; prob_func) sol_extrinsic = solve(eprob_extrinsic; trajectories = 4) 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 3898f8f218..025c1ee3b1 100644 --- a/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md +++ b/docs/src/model_creation/examples/programmatic_generative_linear_pathway.md @@ -7,7 +7,7 @@ The following code sets up an environment for running the code on this page. using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -72,7 +72,7 @@ nothing # hide ``` Next, we prepare an ODE for each model (scaling the initial concentration of $X_0$ and the value of $\tau$ appropriately for each model). ```@example programmatic_generative_linear_pathway_dsl -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0_n3 = [:X0 => 3*1.0, :X1 => 0.0, :X2 => 0.0, :X3 => 0.0] ps_n3 = [:τ => 1.0/3] oprob_n3 = ODEProblem(lp_n3, u0_n3, (0.0, 5.0), ps_n3) @@ -133,7 +133,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 OrdinaryDiffEqDefault, Plots # hide +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/functional_parameters.md b/docs/src/model_creation/functional_parameters.md index 61cb8e913a..062c7f5a1f 100644 --- a/docs/src/model_creation/functional_parameters.md +++ b/docs/src/model_creation/functional_parameters.md @@ -8,7 +8,7 @@ using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") Pkg.add("DataInterpolations") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -19,7 +19,7 @@ Pkg.add("Plots") ``` So called *functional parameters* have many uses. A primary one (shown in this example) is to interpolate some data points and uses as a functional value. ```julia -using Catalyst, DataInterpolations, OrdinaryDiffEqDefault, Plots +using Catalyst, DataInterpolations, OrdinaryDiffEq, Plots # Here we create an interpolated function from some values and plots it. tend = 10.0 @@ -70,7 +70,7 @@ end ``` Finally, we can simulate our model as normal (but where we set the value of the `pIn` parameter to our interpolated data). ```@example functional_parameters_basic_example -using OrdinaryDiffEqDefault +using OrdinaryDiffEq u0 = [:X => 0.5] ps = [:d => 2.0, :pIn => spline] oprob = ODEProblem(bd_model, u0, tend, ps) @@ -113,7 +113,7 @@ rs = complete(rs) ``` Now we can simulate our model. Here, we use the interpolated data as the input parameter's value. ```@example functional_parameters_circ_rhythm -using OrdinaryDiffEqDefault +using OrdinaryDiffEq u0 = [Pᵢ => 1.0, Pₐ => 0.0] ps = [kA => 1.5, kD => 1.0, light_in => interpolated_light] oprob = ODEProblem(rs, u0, tend, ps) @@ -169,7 +169,7 @@ nothing # hide ``` Finally, we can simulate our model. ```@example functional_parameters_sir -using OrdinaryDiffEqDefault +using OrdinaryDiffEq u0 = [:S => 99.0, :I => 1.0, :R => 0.0] ps = [:k1 => 0.002, :k2 => 0.01, :inf_rate => I_rate] oprob = ODEProblem(sir, u0, 250.0, ps) 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 e56ec84848..f7ea869d8f 100644 --- a/docs/src/model_creation/model_file_loading_and_export.md +++ b/docs/src/model_creation/model_file_loading_and_export.md @@ -7,7 +7,7 @@ The following code sets up an environment for running the code on this page. using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") Pkg.add("ReactionNetworkImporters") Pkg.add("SBMLImporter") @@ -97,7 +97,7 @@ rn = loadrxnetwork(BNGNetwork(), "repressilator.net") ``` 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. The `loadrxnetwork` function loads the model as a normal `ReactionSystem` structure, but saves the initial conditions and parameter values as [*default* values](@ref dsl_advanced_options_default_vals) that are automatically accounted for in simulations. The loaded model can be provided to various problem types for simulation. E.g. here we perform an ODE simulation of our repressilator model: ```julia -using Catalyst, OrdinaryDiffEqDefault, Plots +using Catalyst, OrdinaryDiffEq, Plots tspan = (0.0, 10000.0) oprob = ODEProblem(rn, Float64[], tspan, Float64[]) sol = solve(oprob) @@ -125,7 +125,7 @@ rn, cbs = load_SBML("brusselator.xml", massaction = true) `load_SBML` returns two outputs: a `ReactionSystem` (`rn`) and a `CallbackSet` (`cbs`). The `CallbackSet` contains SBML events and callbacks generated from any SBML `piecewise` expressions. While `rn` can be used to create problems, when we simulate them, we must also supply `cbs`. The SBML file also contains simulation initial condition and parameter values. These are saved within the `rn` and can be extracted using the `get_u0_map` and `get_parameter_map` functions. Here we simulate the loaded brusselator model, providing `u0`, `ps`, and `cb` as described. ```julia -using Catalyst, OrdinaryDiffEqDefault, Plots +using Catalyst, OrdinaryDiffEq, Plots tspan = (0.0, 50.0) u0 = get_u0_map(rn) ps = get_parameter_map(rn) diff --git a/docs/src/model_creation/parametric_stoichiometry.md b/docs/src/model_creation/parametric_stoichiometry.md index a48d585133..b226a72393 100644 --- a/docs/src/model_creation/parametric_stoichiometry.md +++ b/docs/src/model_creation/parametric_stoichiometry.md @@ -11,7 +11,7 @@ Pkg.add("Distributions") Pkg.add("JumpProcesses") Pkg.add("Latexify") Pkg.add("ModelingToolkitBase") -Pkg.add("OrdinaryDiffEqTsit5") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -22,7 +22,7 @@ Pkg.add("Plots") ``` Reaction stoichiometric coefficients can be parameters, which values are then designated after model creation. In the following example we designate `n` as a parametric stoichiometry. ```julia -using Catalyst, OrdinaryDiffEqDefault, Plots +using Catalyst, OrdinaryDiffEq, Plots rn = @reaction_network begin (kB,kD), n*X <--> Xn end @@ -45,7 +45,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, OrdinaryDiffEqTsit5, ModelingToolkitBase, Plots +using Catalyst, Latexify, OrdinaryDiffEq, ModelingToolkitBase, Plots revsys = @reaction_network revsys begin @parameters m::Int64 n::Int64 k₊, m*A --> (m*n)*B diff --git a/docs/src/model_creation/reactionsystem_content_accessing.md b/docs/src/model_creation/reactionsystem_content_accessing.md index d5cba12360..222c01069f 100644 --- a/docs/src/model_creation/reactionsystem_content_accessing.md +++ b/docs/src/model_creation/reactionsystem_content_accessing.md @@ -7,7 +7,7 @@ The following code sets up an environment for running the code on this page. using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") Pkg.add("SymbolicIndexingInterface") ``` @@ -37,7 +37,7 @@ rs.X1 ``` to access e.g. `X1`. This symbolic variable can be used just like those [declared using `@parameters` and `@species`](@ref programmatic_CRN_construction): ```@example model_accessing_symbolic_variables -using OrdinaryDiffEqDefault +using OrdinaryDiffEq u0 = [rs.X1 => 1.0, rs.X2 => 2.0] ps = [rs.k1 => 2.0, rs.k2 => 4.0] oprob = ODEProblem(rs, u0, (0.0, 10.0), ps) @@ -232,7 +232,7 @@ rs.kₜ Practically, when we simulate our hierarchical model, we use all of this to designate initial conditions and parameters. I.e. below we designate values for the two `Xᵢ` species and `d` parameters separately: ```@example model_accessing_hierarchical -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0 = [rs.nucleus.Xᵢ => 0.0, rs.cytoplasm.Xᵢ => 0.0, rs.cytoplasm.Xₐ => 0.0] ps = [rs.nucleus.p => 1.0, rs.nucleus.d => 0.2, rs.cytoplasm.kₐ => 5.0, rs.cytoplasm.d => 0.2, rs.kₜ => 0.1] oprob = ODEProblem(rs, u0, (0.0, 10.0), ps) diff --git a/docs/src/model_simulation/ensemble_simulations.md b/docs/src/model_simulation/ensemble_simulations.md index 974412e0df..073e2b2002 100644 --- a/docs/src/model_simulation/ensemble_simulations.md +++ b/docs/src/model_simulation/ensemble_simulations.md @@ -7,7 +7,7 @@ The following code sets up an environment for running the code on this page. using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqTsit5") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") Pkg.add("StochasticDiffEq") ``` @@ -90,7 +90,7 @@ 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 -using OrdinaryDiffEqTsit5 +using OrdinaryDiffEq oprob = ODEProblem(sa_model, u0, tspan, ps) nothing # hide ``` diff --git a/docs/src/model_simulation/examples/interactive_brusselator_simulation.md b/docs/src/model_simulation/examples/interactive_brusselator_simulation.md index 7316de8e72..c324d1e587 100644 --- a/docs/src/model_simulation/examples/interactive_brusselator_simulation.md +++ b/docs/src/model_simulation/examples/interactive_brusselator_simulation.md @@ -9,7 +9,7 @@ Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted Pkg.add("CairoMakie") Pkg.add("Catalyst") Pkg.add("GLMakie") -Pkg.add("OrdinaryDiffEqTsit5") +Pkg.add("OrdinaryDiffEq") ``` ```@raw html @@ -24,7 +24,7 @@ Catalyst can utilize the [GLMakie.jl](https://github.com/JuliaPlots/GLMakie.jl) Let's again use the oscillating Brusselator model, extending the basic simulation [plotting](@ref simulation_plotting) workflow we saw earlier. ```@example interactive_brusselator using Catalyst -using OrdinaryDiffEqTsit5 +using OrdinaryDiffEq ``` ```julia using GLMakie diff --git a/docs/src/model_simulation/examples/periodic_events_simulation.md b/docs/src/model_simulation/examples/periodic_events_simulation.md index 74d27ac978..ff48b1db77 100644 --- a/docs/src/model_simulation/examples/periodic_events_simulation.md +++ b/docs/src/model_simulation/examples/periodic_events_simulation.md @@ -8,7 +8,7 @@ using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") Pkg.add("JumpProcesses") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -31,7 +31,7 @@ end ``` We can now simulate this model, observing how a 24-hour cycle is reached ```@example periodic_event_example -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots u0 = [:X => 150.0, :Xᴾ => 50.0] ps = [:kₚ => 0.1, :kᵢ => 0.1, :l => 1.0] tspan = (0.0, 100.0) diff --git a/docs/src/model_simulation/hybrid_simulations.md b/docs/src/model_simulation/hybrid_simulations.md index 0fd626c326..d970940770 100644 --- a/docs/src/model_simulation/hybrid_simulations.md +++ b/docs/src/model_simulation/hybrid_simulations.md @@ -9,7 +9,7 @@ Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted Pkg.add("Catalyst") Pkg.add("JumpProcesses") Pkg.add("DiffEqNoiseProcess") -Pkg.add("OrdinaryDiffEqTsit5") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") Pkg.add("StochasticDiffEq") ``` @@ -21,7 +21,7 @@ Pkg.add("StochasticDiffEq") ``` The following code provides a brief example of a hybrid simulation (ODE/jump). ```julia -using Catalyst, JumpProcesses, OrdinaryDiffEqTsit5, Plots +using Catalyst, JumpProcesses, OrdinaryDiffEq, Plots # A gene switching between inactive (Gi) and active (Ga) states. # When active, it produces protein X. Gene switching is simulated as a jump process; @@ -65,9 +65,9 @@ rn = @reaction_network begin d, X --> 0, [physical_scale = PhysicalScale.ODE] end ``` -Next, we simulate the model by constructing a `HybridProblem`. ODE/jump systems require both `OrdinaryDiffEqTsit5` and `JumpProcesses`, and are solved with an ODE solver (here `Tsit5`). +Next, we simulate the model by constructing a `HybridProblem`. ODE/jump systems require both `OrdinaryDiffEq` and `JumpProcesses`, and are solved with an ODE solver (here `Tsit5`). ```@example hybrid_simulations_basic -using JumpProcesses, OrdinaryDiffEqTsit5, Plots +using JumpProcesses, OrdinaryDiffEq, Plots u0 = [:X => 100.0, :Ga => 0.0, :Gi => 1.0] ps = [:p => 50.0, :d => 0.25, :kOn => 5.0, :kOff => 5.0] hprob = HybridProblem(rn, u0, (0.0, 10.0), ps) diff --git a/docs/src/model_simulation/ode_simulation_performance.md b/docs/src/model_simulation/ode_simulation_performance.md index 9c4f3fcd81..4513d1a137 100644 --- a/docs/src/model_simulation/ode_simulation_performance.md +++ b/docs/src/model_simulation/ode_simulation_performance.md @@ -11,10 +11,7 @@ Pkg.add("Catalyst") Pkg.add("CUDA") Pkg.add("DiffEqGPU") Pkg.add("IncompleteLU") -Pkg.add("OrdinaryDiffEqBDF") -Pkg.add("OrdinaryDiffEqRosenbrock") -Pkg.add("OrdinaryDiffEqTsit5") -Pkg.add("OrdinaryDiffEqVerner") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") Pkg.add("StaticArrays") ``` @@ -39,7 +36,7 @@ Generally, ODE problems can be categorised into [*stiff ODEs* and *non-stiff ODE 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, OrdinaryDiffEqTsit5, Plots +using Catalyst, OrdinaryDiffEq, Plots brusselator = @reaction_network begin A, ∅ --> X @@ -64,7 +61,7 @@ sol1.retcode ``` Next, we instead try the `Rodas5P` solver (which is designed for stiff problems): ```@example ode_simulation_performance_1 -using OrdinaryDiffEqRosenbrock +using OrdinaryDiffEq sol2 = solve(oprob, Rodas5P()) plot(sol2) ``` @@ -81,7 +78,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 basic_CRN_library_bd): ```@example ode_simulation_performance_2 -using Catalyst, OrdinaryDiffEqTsit5 +using Catalyst, OrdinaryDiffEq bd_model = @reaction_network begin (p,d), 0 <--> X @@ -94,9 +91,9 @@ oprob = ODEProblem(bd_model, u0, tspan, ps) solve(oprob, Tsit5()) nothing # hide ``` -If no solver argument is provided to `solve`, and the `OrdinaryDiffEqDefault` sub-library or meta `OrdinaryDiffEq` library are loaded, then one is automatically selected: +If no solver argument is provided to `solve`, and `OrdinaryDiffEq` is loaded, then one is automatically selected: ```@example ode_simulation_performance_2 -using OrdinaryDiffEqDefault +using OrdinaryDiffEq solve(oprob) nothing # hide ``` @@ -106,7 +103,7 @@ While the default choice is typically enough for most single simulations, if per 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)): ```julia using BenchmarkTools -using OrdinaryDiffEqTsit5, OrdinaryDiffEqRosenbrock, OrdinaryDiffEqVerner, OrdinaryDiffEqBDF +using OrdinaryDiffEq @btime solve(oprob, Tsit5()) @btime solve(oprob, Vern7()) @btime solve(oprob, Rodas5P()) @@ -134,7 +131,7 @@ By default, OrdinaryDiffEq computes the Jacobian using [*automatic differentiati To use this option, simply set `jac = true` when constructing an `ODEProblem`: ```@example ode_simulation_performance_3 -using Catalyst, OrdinaryDiffEqDefault +using Catalyst, OrdinaryDiffEq brusselator = @reaction_network begin A, ∅ --> X @@ -160,7 +157,7 @@ nothing # hide ### [Linear solver selection](@id ode_simulation_performance_symbolic_jacobian_linear_solver) When implicit solvers use e.g. the Newton-Raphson method to (at each simulation time step) solve a (typically non-linear) equation, they actually solve a linearised version of this equation. For this, they use a linear solver, the choice of which can impact performance. To specify one, we use the `linsolve` option (given to the solver function, *not* the `solve` command). E.g. to use the `KLUFactorization` linear solver (which requires loading the [LinearSolve.jl](https://github.com/SciML/LinearSolve.jl) package) we run ```@example ode_simulation_performance_3 -using LinearSolve, OrdinaryDiffEqRosenbrock +using LinearSolve solve(oprob, Rodas5P(linsolve = KLUFactorization())) nothing # hide ``` @@ -176,22 +173,18 @@ Since these methods do not depend on a Jacobian, certain Jacobian options (such ### [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): +In practice, preconditioners are implemented as functions with a specific set of arguments and configured on the `LinearSolve` algorithm used by the ODE solver. 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): ```@example ode_simulation_performance_3 using IncompleteLU -function incompletelu(W, du, u, p, t, newW, Plprev, Prprev, solverdata) - if newW === nothing || newW - Pl = ilu(convert(AbstractMatrix, W), τ = 50.0) - else - Pl = Plprev - end +function incompletelu(A, p) + Pl = ilu(convert(AbstractMatrix, A), τ = 50.0) Pl, nothing end nothing # hide ``` -Next, `incompletelu` can be supplied to our solver using the `precs` argument: +Next, `incompletelu` can be supplied to the `KrylovJL_GMRES` linear solver using the `precs` argument: ```@example ode_simulation_performance_3 -solve(oprob, Rodas5P(linsolve = KrylovJL_GMRES(), precs = incompletelu, concrete_jac = true)) +solve(oprob, Rodas5P(linsolve = KrylovJL_GMRES(precs = incompletelu), concrete_jac = true)) nothing # hide ``` Finally, we note that when using preconditioners with a matrix-free method (like `KrylovJL_GMRES`, which is also the only case when these are relevant), the `concrete_jac = true` argument is required. @@ -202,7 +195,7 @@ Generally, the use of preconditioners is only recommended for advanced users who ## [Elimination of system conservation laws](@id ode_simulation_performance_conservation_laws) Previously, we have described how Catalyst, when it generates ODEs, is able to [detect and eliminate conserved quantities](@ref conservation_laws). In certain cases, doing this can improve performance. E.g. in the following example we will eliminate the single conserved quantity in a [two-state model](@ref basic_CRN_library_two_states). This results in a differential algebraic equation with a single differential equation and a single algebraic equation (as opposed to two differential equations). However, as the algebraic equation is fully determined by the ODE solution, Catalyst moves it to be an observable and our new system therefore only contains one ODE that must be solved numerically. Conservation laws can be eliminated by providing the `remove_conserved = true` option to `ODEProblem`: ```@example ode_simulation_performance_conservation_laws -using Catalyst, OrdinaryDiffEqTsit5 +using Catalyst, OrdinaryDiffEq # Declare model. rs = @reaction_network begin @@ -237,7 +230,7 @@ end ``` The model can be simulated, showing how $P$ is produced from $S$: ```@example ode_simulation_performance_4 -using OrdinaryDiffEqTsit5, Plots +using OrdinaryDiffEq, Plots u0 = [:S => 1.0, :E => 1.0, :SE => 0.0, :P => 0.0] tspan = (0.0, 50.0) ps = [:kB => 1.0, :kD => 0.1, :kP => 0.5, :d => 0.1] @@ -324,7 +317,7 @@ Which backend package you should use depends on your available hardware, with th Next, we declare our model and `ODEProblem`. However, we make all values `Float64` (by appending `f0` to them) and all vectors static (by adding `@SVector` before their declaration, something which requires the [StaticArrays](https://github.com/JuliaArrays/StaticArrays.jl) package). ```@example ode_simulation_performance_5 -using Catalyst, OrdinaryDiffEqDefault, StaticArrays +using Catalyst, OrdinaryDiffEq, StaticArrays mm_model = @reaction_network begin kB, S + E --> SE @@ -334,7 +327,7 @@ mm_model = @reaction_network begin end @unpack S, E, SE, P, kB, kD, kP, d = mm_model -using OrdinaryDiffEqDefault, Plots +using OrdinaryDiffEq, Plots 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] diff --git a/docs/src/model_simulation/simulation_introduction.md b/docs/src/model_simulation/simulation_introduction.md index 8ed0316cec..765ec0bfd9 100644 --- a/docs/src/model_simulation/simulation_introduction.md +++ b/docs/src/model_simulation/simulation_introduction.md @@ -8,9 +8,7 @@ using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") Pkg.add("JumpProcesses") -Pkg.add("OrdinaryDiffEqDefault") -Pkg.add("OrdinaryDiffEqRosenbrock") -Pkg.add("OrdinaryDiffEqTsit5") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") Pkg.add("StochasticDiffEq") ``` @@ -22,7 +20,7 @@ Pkg.add("StochasticDiffEq") ``` The following code provides brief examples of the three main types of simulation. ```julia -using Catalyst, JumpProcesses, OrdinaryDiffEqDefault, StochasticDiffEq +using Catalyst, JumpProcesses, OrdinaryDiffEq, StochasticDiffEq # First we designate our model, initial condition, time span, and parameter values. rn = @reaction_network begin @@ -154,7 +152,7 @@ nothing # hide ``` Next, we can simulate the model (requires loading the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package). Simulations are performed using the `solve` function. ```@example simulation_intro_ode -using OrdinaryDiffEqDefault +using OrdinaryDiffEq sol = solve(oprob) nothing # hide ``` @@ -173,14 +171,16 @@ Some additional considerations: ### [Designating solvers and solver options](@id simulation_intro_solver_options) While good defaults are generally selected, OrdinaryDiffEq enables the user to customise simulations through a long range of options that can be provided to the `solve` function. This includes specifying a [solver algorithm](https://en.wikipedia.org/wiki/Numerical_methods_for_ordinary_differential_equations), which can be provided as a second argument to `solve` (if none is provided, a suitable choice is automatically made). E.g. here we specify that the `Rodas5P` method should be used: ```@example simulation_intro_ode -using OrdinaryDiffEqRosenbrock +using OrdinaryDiffEq 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 ode_simulation_performance_solvers). +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). By default, the OrdinaryDiffEq.jl package only export a small set of the most widely used solvers. To access an expanded set, a specialised solver library must be imported. E.g. + + !!! note - Unlike most other libraries, OrdinaryDiffEq is split into multiple libraries. This is due to it implementing a large number of ODE solvers (most of which a user will not use). Splitting the library improves its loading times. At the highest level, there is OrdinaryDiffEq.jl (imported through `using OrdinaryDiffEq`). This exports all solvers, and is primarily useful if you want to try a wide range of different solvers for a specific problem. Next there is OrdinaryDiffEqDefault.jl (imported through `using OrdinaryDiffEq`). This exports the automated default solver (which selects a solver for the user). It is likely the best one to use for simple workflows. Then there are multiple solver-specific libraries, such as [OrdinaryDiffEqTsit5.jl](https://docs.sciml.ai/OrdinaryDiffEq/stable/explicit/Tsit5/) and [OrdinaryDiffEqRosenbrock.jl](https://docs.sciml.ai/OrdinaryDiffEq/stable/semiimplicit/Rosenbrock/) (a full list can be found [here](https://docs.sciml.ai/OrdinaryDiffEq/stable/)). Each of these exports a specific set of solvers, and are useful if you know in advance which solver you wish to use. + Loading OrdinaryDiffEq.jl through `using OrdinaryDiffEq` makes the default solver choice and the common solver families available. This is the simplest import for tutorials and exploratory workflows. Solver-specific packages still exist for users who want a smaller import surface, but the examples here use the main `OrdinaryDiffEq` package. 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 @@ -402,7 +402,7 @@ end ``` This type of model will generate so called *variable rate jumps* (`VariableRateJump`s in JumpProcesses.jl). Such models can be simulated in Catalyst too, but note that now a method for time-stepping the solver must be provided to `solve`. Here ODE solvers should be given as they are used to handle integrating the explicitly time-dependent propensities for problems with variable rates, i.e. the proceeding example can be solved like ```@example simulation_intro_jumps -using OrdinaryDiffEqTsit5 +using OrdinaryDiffEq u0map = [:P => 0] pmap = [:f => 1.0, :A => 2.0, :ϕ => 0.0, :d => 1.0] tspan = (0.0, 24.0) diff --git a/docs/src/model_simulation/simulation_plotting.md b/docs/src/model_simulation/simulation_plotting.md index 5b330ff479..8de7f4e544 100644 --- a/docs/src/model_simulation/simulation_plotting.md +++ b/docs/src/model_simulation/simulation_plotting.md @@ -7,7 +7,7 @@ The following code sets up an environment for running the code on this page. using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -23,7 +23,7 @@ Catalyst uses the [Plots.jl](https://github.com/JuliaPlots/Plots.jl) package for ## [Common plotting options](@id simulation_plotting_options) 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, OrdinaryDiffEqDefault, Plots +using Catalyst, OrdinaryDiffEq, Plots brusselator = @reaction_network begin A, ∅ --> X diff --git a/docs/src/model_simulation/simulation_structure_interfacing.md b/docs/src/model_simulation/simulation_structure_interfacing.md index 0f57299f67..65f4005aef 100644 --- a/docs/src/model_simulation/simulation_structure_interfacing.md +++ b/docs/src/model_simulation/simulation_structure_interfacing.md @@ -7,7 +7,7 @@ The following code sets up an environment for running the code on this page. using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("SymbolicIndexingInterface") ``` ```@raw html @@ -18,7 +18,7 @@ Pkg.add("SymbolicIndexingInterface") ``` Variable and species values stored in e.g. `ODEProblem`s and solution objects can be easily accessed, e.g. as in the following simple example: ```julia -using Catalyst, OrdinaryDiffEqDefault +using Catalyst, OrdinaryDiffEq # First we create a `ODEProblem` and find its solution. rs = @reaction_network begin @@ -98,7 +98,7 @@ To modify a problem, the `remake` function should be used. It takes an already c Here we modify our problem to increase the initial condition concentrations of the two substrates ($S₁$ and $S₂$), and also confirm that the new problem is different from the old (unchanged) one: ```@example structure_indexing -using OrdinaryDiffEqDefault +using OrdinaryDiffEq oprob_new = remake(oprob; u0 = [:S₁ => 5.0, :S₂ => 2.5]) oprob_new != oprob ``` @@ -197,7 +197,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, OrdinaryDiffEqDefault +using Catalyst, OrdinaryDiffEq t = default_t() @species X1(t) X2(t) @parameters k1 k2 diff --git a/docs/src/spatial_modelling/discrete_space_reaction_systems.md b/docs/src/spatial_modelling/discrete_space_reaction_systems.md index adca581d52..97d25a48ba 100644 --- a/docs/src/spatial_modelling/discrete_space_reaction_systems.md +++ b/docs/src/spatial_modelling/discrete_space_reaction_systems.md @@ -47,7 +47,7 @@ In this example we used non-uniform values for $X1$'s initial condition, but uni We can now simulate our model: ```@example spatial_intro_basics -using OrdinaryDiffEqDefault +using OrdinaryDiffEq sol = solve(oprob) nothing # hide ``` diff --git a/docs/src/spatial_modelling/discrete_space_simulation_plotting.md b/docs/src/spatial_modelling/discrete_space_simulation_plotting.md index 697b6a75c8..4ef5d9264f 100644 --- a/docs/src/spatial_modelling/discrete_space_simulation_plotting.md +++ b/docs/src/spatial_modelling/discrete_space_simulation_plotting.md @@ -19,7 +19,7 @@ The first two functions can be applied to [graph](@ref spatial_dspace_modelling_ ## [Animation and plotting of 1d Cartesian or masked discrete space simulations](@id dspace_simulation_plotting_1d_grids) Let us consider a spatial simulation on a 1d Cartesian grid space: ```@example dspace_plotting_1d -using Catalyst, OrdinaryDiffEqDefault +using Catalyst, OrdinaryDiffEq two_state_model = @reaction_network begin (k1,k2), X1 <--> X2 end @@ -62,7 +62,7 @@ For more information of either function, and additional optional arguments, plea ## [Animation and plotting of 2d Cartesian or masked space simulations](@id dspace_simulation_plotting_2d_grids) Two-dimensional discrete space simulations can be plotted in the same manner as one-dimensional ones. However, instead of displaying a species's value as a line plot, it is displayed as a heatmap. E.g. here we simulate a spatial [Brusselator](@ref basic_CRN_library_brusselator) model and display the value of $X$ at a designated time point. ```@example dspace_plotting_2d -using Catalyst, OrdinaryDiffEqBDF +using Catalyst, OrdinaryDiffEq brusselator = @reaction_network begin A, ∅ --> X 1, 2X + Y --> 3X @@ -95,7 +95,7 @@ Again, please check the API pages for the [`dspace_plot`](@ref) and [`dspace_ani ## [Animation and plotting of graph space simulations](@id dspace_simulation_plotting_graphs) Finally, we consider space simulations on graph spaces. We first simulate a simple [birth-death process](@ref basic_CRN_library_bd) on a (6-node cyclic) graph space. ```@example dspace_plotting_graphs -using Catalyst, Graphs, OrdinaryDiffEqDefault +using Catalyst, Graphs, OrdinaryDiffEq rs = @reaction_network begin (p,d), 0 <--> X end diff --git a/docs/src/spatial_modelling/discrete_space_simulation_structure_interaction.md b/docs/src/spatial_modelling/discrete_space_simulation_structure_interaction.md index bf31d3acf1..009ea8134b 100644 --- a/docs/src/spatial_modelling/discrete_space_simulation_structure_interaction.md +++ b/docs/src/spatial_modelling/discrete_space_simulation_structure_interaction.md @@ -17,7 +17,7 @@ Furthermore, there are some cases of interfacing which are currently not support ## [Retrieving values from space simulations](@id dspace_simulation_structure_interaction_simulation_species) Let us consider a simulation of a [`DiscreteSpaceReactionSystem`](@ref): ```@example dspace_struct_interaction_sims -using Catalyst, OrdinaryDiffEqDefault +using Catalyst, OrdinaryDiffEq two_state_model = @reaction_network begin (k1,k2), X1 <--> X2 end @@ -57,7 +57,7 @@ spat_getu(sol, :X1, dsrs; t = [0.5, 0.75]) ## [Retrieving and updating species values in problems and integrators](@id dspace_simulation_structure_interaction_prob_int_species) Let us consider a spatial `ODEProblem` ```@example dspace_struct_interaction_prob_ints -using Catalyst, OrdinaryDiffEqDefault +using Catalyst, OrdinaryDiffEq two_state_model = @reaction_network begin (k1,k2), X1 <--> X2 end diff --git a/docs/src/spatial_modelling/spatial_ode_simulations.md b/docs/src/spatial_modelling/spatial_ode_simulations.md index f7751e3c59..a864357cc1 100644 --- a/docs/src/spatial_modelling/spatial_ode_simulations.md +++ b/docs/src/spatial_modelling/spatial_ode_simulations.md @@ -7,7 +7,7 @@ Previously we have described [how to select ODE solvers, and how this can impact ## [Jacobian options for spatial ODE simulations](@id spatial_dspace_ode_simulations_jacobians) We have previously described how, when [implicit solvers are used to solve stiff ODEs](@ref ode_simulation_performance_stiffness), the [strategy for computing the system Jacobian](@ref ode_simulation_performance_jacobian) is important. This is especially the case for spatial simulations, where the Jacobian often is large and highly sparse. Catalyst implements special methods for spatial Jacobians. To utilise these, provide the `jac = true` argument to your `ODEProblem` when it is created (if `jac = false`, which is the default, [*automatic differentiation*](https://en.wikipedia.org/wiki/Automatic_differentiation) will be used for Jacobian computation). Here we simulate a [Brusselator](@ref basic_CRN_library_brusselator) while designating to use Catalyst's computed Jacobian: ```@example spatial_ode -using Catalyst, OrdinaryDiffEqBDF +using Catalyst, OrdinaryDiffEq brusselator = @reaction_network begin A, ∅ --> X 1, 2X + Y --> 3X diff --git a/docs/src/steady_state_functionality/examples/bifurcationkit_codim2.md b/docs/src/steady_state_functionality/examples/bifurcationkit_codim2.md index f937ad756c..31c2764492 100644 --- a/docs/src/steady_state_functionality/examples/bifurcationkit_codim2.md +++ b/docs/src/steady_state_functionality/examples/bifurcationkit_codim2.md @@ -8,7 +8,7 @@ using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("BifurcationKit") Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -55,13 +55,13 @@ plot(bifdia; xguide = bif_par, yguide = plot_var, branchlabel = "Continuation of ``` We note that for small values of $v$ the system's single steady state is stable (where the line is thicker). After a [Hopf](https://en.wikipedia.org/wiki/Hopf_bifurcation) bifurcation (the red point), the state turns unstable (where the line is thinner). For chemical reaction networks (which mostly are well-behaved) a single unstable steady state typically corresponds to an oscillation. We can confirm that the system oscillates in the unstable region (while it reaches a stable steady state in the stable region) using simulations: ```@example bifurcationkit_codim2 -using OrdinaryDiffEqDefault +using OrdinaryDiffEq p_nosc = [:v => 5.0, :K => 15.0, :n => 3, :d => 0.2] p_osc = [:v => 15.0, :K => 15.0, :n => 3, :d => 0.2] prob_nosc = ODEProblem(repressilator, u_guess, 80.0, p_nosc) prob_osc = ODEProblem(repressilator, u_guess, 80.0, p_osc) -sol_nosc = OrdinaryDiffEqDefault.solve(prob_nosc) -sol_osc = OrdinaryDiffEqDefault.solve(prob_osc) +sol_nosc = OrdinaryDiffEq.solve(prob_nosc) +sol_osc = OrdinaryDiffEq.solve(prob_osc) plot(plot(sol_nosc; title = "v = 5"), plot(sol_osc; title = "v = 15"), size = (1000,400), lw = 4) ``` @@ -127,8 +127,8 @@ ps_nosc = [:v => sample2[1], :K => sample2[2], :n => 3, :d => 0.2] ps_osc = [:v => sample1[1], :K => sample1[2], :n => 3, :d => 0.2] oprob_nosc = ODEProblem(repressilator, u_guess, 100.0, ps_nosc) oprob_osc = ODEProblem(repressilator, u_guess, 100.0, ps_osc) -sol_nosc = OrdinaryDiffEqDefault.solve(oprob_nosc) -sol_osc = OrdinaryDiffEqDefault.solve(oprob_osc) +sol_nosc = OrdinaryDiffEq.solve(oprob_nosc) +sol_osc = OrdinaryDiffEq.solve(oprob_osc) plot(plot(sol_nosc; title = "No oscillation"), plot(sol_osc; title = "Oscillation"); size = (1000, 400), lw = 4) ``` diff --git a/docs/src/steady_state_functionality/examples/bifurcationkit_periodic_orbits.md b/docs/src/steady_state_functionality/examples/bifurcationkit_periodic_orbits.md index de4a094323..42f28895f5 100644 --- a/docs/src/steady_state_functionality/examples/bifurcationkit_periodic_orbits.md +++ b/docs/src/steady_state_functionality/examples/bifurcationkit_periodic_orbits.md @@ -8,7 +8,7 @@ using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("BifurcationKit") Pkg.add("Catalyst") -Pkg.add("OrdinaryDiffEqDefault") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -54,13 +54,13 @@ plot(bifdia; xguide = bif_par, yguide = plot_var, xlimit = v_span, branchlabel = ``` We note that for small values of $v$ the system's single steady state is stable (where the line is thicker). After a [Hopf](https://en.wikipedia.org/wiki/Hopf_bifurcation) bifurcation (the red point), the state turns unstable (where the line is thinner). For chemical reaction networks (which mostly are well-behaved) a single unstable steady state typically corresponds to an oscillation. We can confirm that the system oscillates in the unstable region (while it reaches a stable steady state in the stable region) using simulations: ```@example bifurcationkit_periodic_orbits -using OrdinaryDiffEqDefault +using OrdinaryDiffEq p_nosc = [:v => 5.0, :K => 15.0, :n => 3, :d => 0.2] p_osc = [:v => 15.0, :K => 15.0, :n => 3, :d => 0.2] prob_nosc = ODEProblem(repressilator, u_guess, 80.0, p_nosc) prob_osc = ODEProblem(repressilator, u_guess, 80.0, p_osc) -sol_nosc = OrdinaryDiffEqDefault.solve(prob_nosc) -sol_osc = OrdinaryDiffEqDefault.solve(prob_osc) +sol_nosc = OrdinaryDiffEq.solve(prob_nosc) +sol_osc = OrdinaryDiffEq.solve(prob_osc) plot(plot(sol_nosc; title = "v = 5"), plot(sol_osc; title = "v = 15"), size = (1000,400), lw = 4) ``` diff --git a/docs/src/steady_state_functionality/nonlinear_solve.md b/docs/src/steady_state_functionality/nonlinear_solve.md index 5f42c436de..903483b0d6 100644 --- a/docs/src/steady_state_functionality/nonlinear_solve.md +++ b/docs/src/steady_state_functionality/nonlinear_solve.md @@ -8,7 +8,7 @@ using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") Pkg.add("NonlinearSolve") -Pkg.add("OrdinaryDiffEqRosenbrock") +Pkg.add("OrdinaryDiffEq") Pkg.add("SteadyStateDiffEq") ``` ```@raw html @@ -136,11 +136,11 @@ Next, we provide these as an input to a `SteadyStateProblem` ssprob = SteadyStateProblem(dimer_production, u0, p) nothing # hide ``` -Finally, we can find the steady states using the `solver` command. Since `SteadyStateProblem`s are solved through forward ODE simulation, we must load the sublibrary of the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package that corresponds to the [selected 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 use the `Rodas5P` solver which is loaded from the `OrdinaryDiffEqRosenbrock` sublibrary: +Finally, we can find the steady states using the `solver` command. Since `SteadyStateProblem`s are solved through forward ODE simulation, we must load [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) for the [selected 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 use the `Rodas5P` solver: (which requires loading the SteadyStateDiffEq package). ```@example steady_state_solving_simulation -using SteadyStateDiffEq, OrdinaryDiffEqRosenbrock +using SteadyStateDiffEq, OrdinaryDiffEq solve(ssprob, DynamicSS(Rodas5P())) ``` 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. diff --git a/docs/unpublished/pdes.md b/docs/unpublished/pdes.md index 42929d08b9..bdb0774334 100644 --- a/docs/unpublished/pdes.md +++ b/docs/unpublished/pdes.md @@ -11,7 +11,7 @@ parameters in (Kim et al., J. Chem. Phys., 146, 2017). First we load the packages we'll use ```julia -using Catalyst, MethodOfLines, DomainSets, OrdinaryDiffEqSDIRK, Plots, Random, Distributions +using Catalyst, MethodOfLines, DomainSets, OrdinaryDiffEq, Plots, Random, Distributions using ModelingToolkitBase: scalarize, unwrap, operation, initial_conditions ``` diff --git a/test/compositional_modelling/brownians_and_jumps_composition.jl b/test/compositional_modelling/brownians_and_jumps_composition.jl index 06e6f18580..cce4b6860a 100644 --- a/test/compositional_modelling/brownians_and_jumps_composition.jl +++ b/test/compositional_modelling/brownians_and_jumps_composition.jl @@ -3,7 +3,7 @@ ### Fetch Packages and Set Global Variables ### # Fetch packages. -using Catalyst, JumpProcesses, OrdinaryDiffEqTsit5, StochasticDiffEq, Test +using Catalyst, JumpProcesses, OrdinaryDiffEq, StochasticDiffEq, Test using Catalyst: isequivalent using ModelingToolkitBase: get_brownians, get_poissonians, get_jumps, Pre import ModelingToolkitBase as MT diff --git a/test/compositional_modelling/component_based_model_creation.jl b/test/compositional_modelling/component_based_model_creation.jl index 27d8d259ce..6849c636bc 100644 --- a/test/compositional_modelling/component_based_model_creation.jl +++ b/test/compositional_modelling/component_based_model_creation.jl @@ -3,7 +3,7 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, LinearAlgebra, OrdinaryDiffEqTsit5, NonlinearSolve, Test +using Catalyst, LinearAlgebra, OrdinaryDiffEq, NonlinearSolve, Test using ModelingToolkitBase: nameof, getname const MT = ModelingToolkitBase diff --git a/test/dsl/dsl_advanced_model_construction.jl b/test/dsl/dsl_advanced_model_construction.jl index 564460d3a4..5b88dadc23 100644 --- a/test/dsl/dsl_advanced_model_construction.jl +++ b/test/dsl/dsl_advanced_model_construction.jl @@ -3,7 +3,7 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, ModelingToolkitBase, OrdinaryDiffEqRosenbrock +using Catalyst, ModelingToolkitBase, OrdinaryDiffEq # Set creates the `t` independent variable. t = default_t() diff --git a/test/dsl/dsl_options.jl b/test/dsl/dsl_options.jl index ce11ece254..fcf572f33c 100644 --- a/test/dsl/dsl_options.jl +++ b/test/dsl/dsl_options.jl @@ -3,7 +3,7 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, ModelingToolkitBase, OrdinaryDiffEqTsit5, OrdinaryDiffEqRosenbrock, Statistics, StochasticDiffEq, Plots, Test +using Catalyst, ModelingToolkitBase, OrdinaryDiffEq, Statistics, StochasticDiffEq, Plots, Test using Catalyst: symmap_to_varmap using Symbolics: unwrap diff --git a/test/extensions/Project.toml b/test/extensions/Project.toml index 353b7d07f4..2b8ebf3694 100644 --- a/test/extensions/Project.toml +++ b/test/extensions/Project.toml @@ -7,9 +7,7 @@ Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" HomotopyContinuation = "f213a82b-91d6-5c5d-acf7-10f1c761b327" JumpProcesses = "ccbc3e58-028d-4f4c-8cd5-9ae44345cda5" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" -OrdinaryDiffEqDefault = "50262376-6c5a-4cf5-baba-aaf4f84d72d7" -OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" -OrdinaryDiffEqVerner = "79d7bb75-1356-48c1-b8c0-6832512096c2" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" @@ -26,9 +24,7 @@ GraphMakie = "0.6.3" Graphs = "1.14.0" HomotopyContinuation = "2.19.0" JumpProcesses = "9.29.0" -OrdinaryDiffEqDefault = "2.2.0" -OrdinaryDiffEqTsit5 = "2.0.1" -OrdinaryDiffEqVerner = "2.1.0" +OrdinaryDiffEq = "7" Plots = "1.41.6" SafeTestsets = "0.1.0" StableRNGs = "1.0.4" diff --git a/test/extensions/dspace_simulation_plotting.jl b/test/extensions/dspace_simulation_plotting.jl index c577a40531..79a5d6e71d 100644 --- a/test/extensions/dspace_simulation_plotting.jl +++ b/test/extensions/dspace_simulation_plotting.jl @@ -2,7 +2,7 @@ # Fetch packages. using Catalyst, CairoMakie, GraphMakie, Graphs -using JumpProcesses, OrdinaryDiffEqTsit5, Test +using JumpProcesses, OrdinaryDiffEq, Test ### Checks Basic Plot Cases ### diff --git a/test/extensions/stability_computation.jl b/test/extensions/stability_computation.jl index 7d58cd5ff2..56073d8c2e 100644 --- a/test/extensions/stability_computation.jl +++ b/test/extensions/stability_computation.jl @@ -1,7 +1,7 @@ ### Fetch Packages ### # Fetch packages. -using Catalyst, OrdinaryDiffEqVerner, SteadyStateDiffEq, Test +using Catalyst, OrdinaryDiffEq, SteadyStateDiffEq, Test import HomotopyContinuation # Sets rnd number. diff --git a/test/miscellaneous_tests/api.jl b/test/miscellaneous_tests/api.jl index d9293f0b52..277a576852 100644 --- a/test/miscellaneous_tests/api.jl +++ b/test/miscellaneous_tests/api.jl @@ -3,7 +3,7 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, NonlinearSolve, OrdinaryDiffEqTsit5, SparseArrays, StochasticDiffEq, Test +using Catalyst, NonlinearSolve, OrdinaryDiffEq, SparseArrays, StochasticDiffEq, Test using Catalyst: symmap_to_varmap using LinearAlgebra: norm using ModelingToolkitBase: value diff --git a/test/network_analysis/conservation_laws.jl b/test/network_analysis/conservation_laws.jl index 0929fc61cc..2684ade0c5 100644 --- a/test/network_analysis/conservation_laws.jl +++ b/test/network_analysis/conservation_laws.jl @@ -1,7 +1,7 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, JumpProcesses, LinearAlgebra, NonlinearSolve, OrdinaryDiffEqTsit5, OrdinaryDiffEqDefault, OrdinaryDiffEqVerner, SteadyStateDiffEq, StochasticDiffEq, Test +using Catalyst, JumpProcesses, LinearAlgebra, NonlinearSolve, OrdinaryDiffEq, SteadyStateDiffEq, StochasticDiffEq, Test # Sets stable rng number. using StableRNGs diff --git a/test/reactionsystem_core/coupled_equation_crn_systems.jl b/test/reactionsystem_core/coupled_equation_crn_systems.jl index a7daef61d2..a68d9777d3 100644 --- a/test/reactionsystem_core/coupled_equation_crn_systems.jl +++ b/test/reactionsystem_core/coupled_equation_crn_systems.jl @@ -1,5 +1,5 @@ # Fetch packages. -using Catalyst, NonlinearSolve, OrdinaryDiffEqVerner, OrdinaryDiffEqTsit5, OrdinaryDiffEqRosenbrock, Statistics, SteadyStateDiffEq, StochasticDiffEq, Test +using Catalyst, NonlinearSolve, OrdinaryDiffEq, Statistics, SteadyStateDiffEq, StochasticDiffEq, Test using ModelingToolkitBase: getdefault, getdescription, getdefault using Symbolics: unwrap diff --git a/test/reactionsystem_core/events.jl b/test/reactionsystem_core/events.jl index a0b05d644c..3b5af27c83 100644 --- a/test/reactionsystem_core/events.jl +++ b/test/reactionsystem_core/events.jl @@ -1,7 +1,7 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, DiffEqCallbacks, JumpProcesses, OrdinaryDiffEqTsit5, StochasticDiffEq, Test +using Catalyst, DiffEqCallbacks, JumpProcesses, OrdinaryDiffEq, StochasticDiffEq, Test using ModelingToolkitBase: SymbolicContinuousCallback, SymbolicDiscreteCallback # Sets stable rng number. diff --git a/test/reactionsystem_core/functional_parameters.jl b/test/reactionsystem_core/functional_parameters.jl index 8099df248e..e657863022 100644 --- a/test/reactionsystem_core/functional_parameters.jl +++ b/test/reactionsystem_core/functional_parameters.jl @@ -1,6 +1,6 @@ # Fetch packages. -using Catalyst, DataInterpolations, JumpProcesses, OrdinaryDiffEqDefault, OrdinaryDiffEqTsit5, StochasticDiffEq, Test +using Catalyst, DataInterpolations, JumpProcesses, OrdinaryDiffEq, StochasticDiffEq, Test # Sets the default `t` to use. t = default_t() diff --git a/test/reactionsystem_core/parameter_type_designation.jl b/test/reactionsystem_core/parameter_type_designation.jl index b82d0d4a8f..053f24a4f3 100644 --- a/test/reactionsystem_core/parameter_type_designation.jl +++ b/test/reactionsystem_core/parameter_type_designation.jl @@ -1,7 +1,7 @@ ### Fetch Packages and Set Global Variables ### # Fetch packages. -using Catalyst, JumpProcesses, NonlinearSolve, OrdinaryDiffEqTsit5, StochasticDiffEq, Test +using Catalyst, JumpProcesses, NonlinearSolve, OrdinaryDiffEq, StochasticDiffEq, Test using Symbolics: unwrap # Sets stable rng number. diff --git a/test/reactionsystem_core/reactionsystem.jl b/test/reactionsystem_core/reactionsystem.jl index d1136f37f0..298826ed98 100644 --- a/test/reactionsystem_core/reactionsystem.jl +++ b/test/reactionsystem_core/reactionsystem.jl @@ -1,7 +1,7 @@ ### Fetch Packages and Set Global Variables ### # Fetch packages. -using Catalyst, LinearAlgebra, JumpProcesses, OrdinaryDiffEqTsit5, OrdinaryDiffEqVerner, StochasticDiffEq, Test +using Catalyst, LinearAlgebra, JumpProcesses, OrdinaryDiffEq, StochasticDiffEq, Test const MT = ModelingToolkitBase using ModelingToolkitBase: Pre, unwrap diff --git a/test/reactionsystem_core/symbolic_stoichiometry.jl b/test/reactionsystem_core/symbolic_stoichiometry.jl index beef92db5b..2cbaee3583 100644 --- a/test/reactionsystem_core/symbolic_stoichiometry.jl +++ b/test/reactionsystem_core/symbolic_stoichiometry.jl @@ -1,7 +1,7 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, JumpProcesses, OrdinaryDiffEqTsit5, StochasticDiffEq, Statistics, Test +using Catalyst, JumpProcesses, OrdinaryDiffEq, StochasticDiffEq, Statistics, Test using Symbolics: unwrap import SymbolicIndexingInterface as SII diff --git a/test/simulation_and_solving/hybrid_models.jl b/test/simulation_and_solving/hybrid_models.jl index 9f598ad3c2..d485567ee8 100644 --- a/test/simulation_and_solving/hybrid_models.jl +++ b/test/simulation_and_solving/hybrid_models.jl @@ -1,5 +1,5 @@ # Fetch packages. -using Catalyst, JumpProcesses, ModelingToolkitBase, OrdinaryDiffEqTsit5, Statistics, Test, Random +using Catalyst, JumpProcesses, ModelingToolkitBase, OrdinaryDiffEq, Statistics, Test, Random using StochasticDiffEq: SRIW1 # For SDE+Jump hybrid problems import DiffEqNoiseProcess # Required for SDEProblem via mtkcompile diff --git a/test/simulation_and_solving/jacobian_construction.jl b/test/simulation_and_solving/jacobian_construction.jl index dab99eeb2d..bf448200d0 100644 --- a/test/simulation_and_solving/jacobian_construction.jl +++ b/test/simulation_and_solving/jacobian_construction.jl @@ -1,7 +1,7 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, DiffEqBase, OrdinaryDiffEqRosenbrock, Test +using Catalyst, DiffEqBase, OrdinaryDiffEq, Test # Sets stable rng number. using StableRNGs diff --git a/test/simulation_and_solving/simulate_ODEs.jl b/test/simulation_and_solving/simulate_ODEs.jl index e12cac10a2..f65513ff03 100644 --- a/test/simulation_and_solving/simulate_ODEs.jl +++ b/test/simulation_and_solving/simulate_ODEs.jl @@ -1,7 +1,7 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, OrdinaryDiffEqRosenbrock, OrdinaryDiffEqTsit5, OrdinaryDiffEqVerner, Test +using Catalyst, OrdinaryDiffEq, Test # Sets stable rng number. using StableRNGs diff --git a/test/simulation_and_solving/simulate_jumps.jl b/test/simulation_and_solving/simulate_jumps.jl index ceeaf6a490..eafd71742c 100644 --- a/test/simulation_and_solving/simulate_jumps.jl +++ b/test/simulation_and_solving/simulate_jumps.jl @@ -1,7 +1,7 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, JumpProcesses, ModelingToolkitBase, OrdinaryDiffEqTsit5, Statistics, Test +using Catalyst, JumpProcesses, ModelingToolkitBase, OrdinaryDiffEq, Statistics, Test using SymbolicIndexingInterface: setp # Sets stable rng number. diff --git a/test/simulation_and_solving/solve_nonlinear.jl b/test/simulation_and_solving/solve_nonlinear.jl index a02e9c22a5..84209e73bd 100644 --- a/test/simulation_and_solving/solve_nonlinear.jl +++ b/test/simulation_and_solving/solve_nonlinear.jl @@ -1,7 +1,7 @@ ### Prepares Tests ### # Fetch packages. -using Catalyst, NonlinearSolve, OrdinaryDiffEqRosenbrock, SteadyStateDiffEq +using Catalyst, NonlinearSolve, OrdinaryDiffEq, SteadyStateDiffEq using Random, Test # Sets stable rng number. diff --git a/test/spatial_modelling/dspace_reaction_systems.jl b/test/spatial_modelling/dspace_reaction_systems.jl index 648c8659eb..bbf714695d 100644 --- a/test/spatial_modelling/dspace_reaction_systems.jl +++ b/test/spatial_modelling/dspace_reaction_systems.jl @@ -1,7 +1,7 @@ ### Preparations ### # Fetch packages. -using Catalyst, Graphs, OrdinaryDiffEqTsit5, Test +using Catalyst, Graphs, OrdinaryDiffEq, Test # Fetch test networks. include("../spatial_test_networks.jl") diff --git a/test/spatial_modelling/dspace_reaction_systems_ODEs.jl b/test/spatial_modelling/dspace_reaction_systems_ODEs.jl index c3eff6c156..1b3ddf2c1e 100644 --- a/test/spatial_modelling/dspace_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/dspace_reaction_systems_ODEs.jl @@ -1,7 +1,7 @@ ### Preparations ### # Fetch packages. -using OrdinaryDiffEqTsit5, OrdinaryDiffEqRosenbrock, OrdinaryDiffEqBDF +using OrdinaryDiffEq using Random, Statistics, SparseArrays, Test # Fetch test networks. diff --git a/test/spatial_modelling/dspace_reaction_systems_space_types.jl b/test/spatial_modelling/dspace_reaction_systems_space_types.jl index e6cc8879f5..6d2f39ae7e 100644 --- a/test/spatial_modelling/dspace_reaction_systems_space_types.jl +++ b/test/spatial_modelling/dspace_reaction_systems_space_types.jl @@ -1,7 +1,7 @@ ### Preparations ### # Fetch packages. -using Catalyst, Graphs, OrdinaryDiffEqTsit5, OrdinaryDiffEqBDF, Test +using Catalyst, Graphs, OrdinaryDiffEq, Test # Fetch test networks. include("../spatial_test_networks.jl") diff --git a/test/spatial_modelling/dspace_simulation_struct_interfacing.jl b/test/spatial_modelling/dspace_simulation_struct_interfacing.jl index e5dbd44745..c381aa4257 100644 --- a/test/spatial_modelling/dspace_simulation_struct_interfacing.jl +++ b/test/spatial_modelling/dspace_simulation_struct_interfacing.jl @@ -1,7 +1,7 @@ ### Preparations ### # Fetch packages. -using Catalyst, Graphs, JumpProcesses, OrdinaryDiffEqVerner, OrdinaryDiffEqTsit5, OrdinaryDiffEqRosenbrock, SparseArrays, Test +using Catalyst, Graphs, JumpProcesses, OrdinaryDiffEq, SparseArrays, Test # Fetch test networks. include("../spatial_test_networks.jl") diff --git a/test/upstream/mtk_problem_inputs.jl b/test/upstream/mtk_problem_inputs.jl index 622e862061..10d69234cf 100644 --- a/test/upstream/mtk_problem_inputs.jl +++ b/test/upstream/mtk_problem_inputs.jl @@ -3,7 +3,7 @@ ### Prepares Tests ### # Fetch packages -using Catalyst, JumpProcesses, NonlinearSolve, OrdinaryDiffEqTsit5, StaticArrays, SteadyStateDiffEq, +using Catalyst, JumpProcesses, NonlinearSolve, OrdinaryDiffEq, StaticArrays, SteadyStateDiffEq, StochasticDiffEq, Test # Sets rnd number. diff --git a/test/upstream/mtk_structure_indexing.jl b/test/upstream/mtk_structure_indexing.jl index f9f066f790..75faa8a431 100644 --- a/test/upstream/mtk_structure_indexing.jl +++ b/test/upstream/mtk_structure_indexing.jl @@ -1,7 +1,7 @@ ### Prepares Tests ### # Fetch packages -using Catalyst, JumpProcesses, NonlinearSolve, OrdinaryDiffEqTsit5, Plots, SteadyStateDiffEq, StochasticDiffEq, Test +using Catalyst, JumpProcesses, NonlinearSolve, OrdinaryDiffEq, Plots, SteadyStateDiffEq, StochasticDiffEq, Test import ModelingToolkitBase: getp, getu, setp, setu # Sets rnd number. From d61af4b22dabbd8eda9bb3812167e0d1118b4687 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 15 Jun 2026 17:49:00 +0100 Subject: [PATCH 3/8] try to handle SciMLLogging --- docs/Project.toml | 16 ++-------------- .../inverse_problems/behaviour_optimisation.md | 8 ++++---- .../examples/ode_fitting_oscillation.md | 6 +++--- .../global_sensitivity_analysis.md | 14 +++++++------- .../optimization_ode_param_fitting.md | 18 +++++++++--------- .../turing_ode_param_fitting.md | 12 ++++++------ .../dynamical_systems.md | 15 +++++++-------- 7 files changed, 38 insertions(+), 51 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 0547d99a4b..0a3bb22ae5 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -33,13 +33,7 @@ OptimizationLBFGSB = "22f7324a-a79d-40f2-bebe-3af60c77bd15" OptimizationNLopt = "4e6fcdb7-1186-4e1f-a706-475e75c168bb" OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e" OptimizationOptimisers = "42dfb2eb-d2b4-4451-abcd-913932933ac1" -OrdinaryDiffEqBDF = "6ad6398a-0878-4a85-9266-38940aa047c8" -OrdinaryDiffEqDefault = "50262376-6c5a-4cf5-baba-aaf4f84d72d7" -OrdinaryDiffEqNonlinearSolve = "127b3ac7-2247-4354-8eb6-78cf4e7c58e8" -OrdinaryDiffEqRosenbrock = "43230ef6-c299-4910-a778-202eb28ce4ce" -OrdinaryDiffEqSDIRK = "2d112036-d095-4a1e-ab9a-08536f3ecdbf" -OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" -OrdinaryDiffEqVerner = "79d7bb75-1356-48c1-b8c0-6832512096c2" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" PEtab = "48d54b35-e43e-4a66-a5a1-dde6b987cf69" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" QuasiMonteCarlo = "8a4e6c94-4038-4cdc-81c3-7e6ffdb2a71b" @@ -91,13 +85,7 @@ OptimizationLBFGSB = "1" OptimizationNLopt = "0.3" OptimizationOptimJL = "0.4" OptimizationOptimisers = "0.3" -OrdinaryDiffEqBDF = "1, 2" -OrdinaryDiffEqDefault = "1, 2" -OrdinaryDiffEqNonlinearSolve = "1, 2" -OrdinaryDiffEqRosenbrock = "1, 2" -OrdinaryDiffEqSDIRK = "1, 2" -OrdinaryDiffEqTsit5 = "1, 2" -OrdinaryDiffEqVerner = "1, 2" +OrdinaryDiffEq = "6, 7" PEtab = "5" Plots = "1.40" QuasiMonteCarlo = "0.3" diff --git a/docs/src/inverse_problems/behaviour_optimisation.md b/docs/src/inverse_problems/behaviour_optimisation.md index c1bc34ada9..3566bef7b7 100644 --- a/docs/src/inverse_problems/behaviour_optimisation.md +++ b/docs/src/inverse_problems/behaviour_optimisation.md @@ -11,7 +11,7 @@ Pkg.add("OptimizationBase") Pkg.add("OptimizationBBO") Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") -Pkg.add("SciMLLogging") +Pkg.add("DiffEqBase") ``` ```@raw html @@ -36,7 +36,7 @@ end ``` To demonstrate this pulsing behaviour we will simulate the system for an example parameter set. We select an initial condition (`u0`) so the system begins in a steady state. ```@example behaviour_optimization -using OrdinaryDiffEq, Plots, SciMLLogging +using OrdinaryDiffEq, Plots, DiffEqBase example_p = [:pX => 0.1, :pY => 1.0, :pZ => 1.0] tspan = (0.0, 50.0) example_u0 = [:X => 0.1, :Y => 0.1, :Z => 1.0] @@ -53,7 +53,7 @@ function pulse_amplitude(p, _) p = Dict([:pX => p[1], :pY => p[2], :pZ => p[2]]) u0 = [:X => p[:pX], :Y => p[:pX]*p[:pY], :Z => p[:pZ]/p[:pY]^2] oprob_local = remake(oprob; u0, p) - sol = solve(oprob_local; verbose = SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob_local; verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return -(maximum(sol[:Z]) - sol[:Z][1]) end @@ -61,7 +61,7 @@ nothing # hide ``` This objective function takes two arguments (a parameter value `p`, and an additional one which we will ignore but is discussed in a note [here](@ref optimization_parameter_fitting_basics)). 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 objective function provided, input parameter set. Finally, Optimization.jl finds the function's *minimum value*, so to find the *maximum* relative pulse amplitude, we make our objective function return the negative pulse amplitude. -As described [in our tutorial on parameter fitting using Optimization.jl](@ref optimization_parameter_fitting_basics) we use `remake`, `verbose = SciMLLogging.None()`, `maxiters = 10000`, and a check on the simulations return code, all providing various advantages to the optimisation procedure (as explained in that tutorial). +As described [in our tutorial on parameter fitting using Optimization.jl](@ref optimization_parameter_fitting_basics) we use `remake`, `verbose = DiffEqBase.SciMLLogging.None()`, `maxiters = 10000`, and a check on the simulations return code, all providing various advantages to the optimisation procedure (as explained in that tutorial). Just like for [parameter fitting](@ref optimization_parameter_fitting_basics), we create an `OptimizationProblem` using our objective function, and some initial guess of the parameter values. We also [set upper and lower bounds](@ref optimization_parameter_fitting_constraints) 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 diff --git a/docs/src/inverse_problems/examples/ode_fitting_oscillation.md b/docs/src/inverse_problems/examples/ode_fitting_oscillation.md index 7829614f5d..889f1ebaa4 100644 --- a/docs/src/inverse_problems/examples/ode_fitting_oscillation.md +++ b/docs/src/inverse_problems/examples/ode_fitting_oscillation.md @@ -11,7 +11,7 @@ Pkg.add("OptimizationBase") Pkg.add("OptimizationOptimisers") Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") -Pkg.add("SciMLLogging") +Pkg.add("DiffEqBase") Pkg.add("SciMLSensitivity") ``` ```@raw html @@ -27,7 +27,7 @@ using Catalyst using OrdinaryDiffEq using OptimizationBase using OptimizationOptimisers # Required for the ADAM optimizer. -using SciMLLogging +using DiffEqBase using SciMLSensitivity # Required for the `AutoZygote()` automatic differentiation option. ``` @@ -80,7 +80,7 @@ function optimize_p(pinit, tend, p = set_p(prob, p) newtimes = filter(<=(tend), sample_times) newprob = remake(prob; p) - sol = Array(solve(newprob, Rosenbrock23(); saveat = newtimes, verbose = SciMLLogging.None(), maxiters = 10000)) + sol = Array(solve(newprob, Rosenbrock23(); saveat = newtimes, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000)) loss = sum(abs2, sol .- sample_vals[:, 1:size(sol,2)]) return loss end diff --git a/docs/src/inverse_problems/global_sensitivity_analysis.md b/docs/src/inverse_problems/global_sensitivity_analysis.md index f73675443e..cfee8cffc7 100644 --- a/docs/src/inverse_problems/global_sensitivity_analysis.md +++ b/docs/src/inverse_problems/global_sensitivity_analysis.md @@ -10,7 +10,7 @@ Pkg.add("Catalyst") Pkg.add("GlobalSensitivity") Pkg.add("Plots") Pkg.add("OrdinaryDiffEq") -Pkg.add("SciMLLogging") +Pkg.add("DiffEqBase") ``` ```@raw html @@ -20,7 +20,7 @@ Pkg.add("SciMLLogging") ``` The following code provides a brief example of how to perform global sensitivity analysis using the [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) package. ```julia -using Catalyst, GlobalSensitivity, OrdinaryDiffEq, SciMLLogging, SymbolicIndexingInterface +using Catalyst, GlobalSensitivity, OrdinaryDiffEq, DiffEqBase, SymbolicIndexingInterface # Designates a model, parameter set, and set of initial conditions. # For this infectious disease model we will determine the peak number of cases's sensitivity to the parameters. @@ -39,7 +39,7 @@ function peak_cases(p) # Updates the ODEProblem with teh proposed parameter set. p = p_setter(oprob_base, p) oprob = remake(oprob_base; p) - sol = solve(oprob; maxiters = 100000, verbose = SciMLLogging.None()) + sol = solve(oprob; maxiters = 100000, verbose = DiffEqBase.SciMLLogging.None()) return maximum(sol[:I]) end @@ -75,7 +75,7 @@ end ``` We will study the peak number of infected cases's ($max(I(t))$) sensitivity to the system's three parameters. We create a function which simulates the system from a given initial condition and measures this property: ```@example gsa_1 -using OrdinaryDiffEq, SciMLLogging +using OrdinaryDiffEq, DiffEqBase u0 = [:S => 999.0, :I => 1.0, :E => 0.0, :R => 0.0] p_dummy = [:β => 0.0, :a => 0.0, :γ => 0.0] @@ -84,7 +84,7 @@ oprob_base = ODEProblem(seir_model, u0, (0.0, 10000.0), p_dummy) function peak_cases(p) ps = [:β => p[1], :a => p[2], :γ => p[3]] oprob = remake(oprob_base; p = ps) - sol = solve(oprob; maxiters = 100000, verbose = SciMLLogging.None()) + sol = solve(oprob; maxiters = 100000, verbose = DiffEqBase.SciMLLogging.None()) SciMLBase.successful_retcode(sol) || return Inf return maximum(sol[:I]) end @@ -107,7 +107,7 @@ on the domain $10^β ∈ (-3.0,-1.0)$, $10^a ∈ (-2.0,0.0)$, $10^γ ∈ (-2.0,0 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_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 = SciMLLogging.None()` arguments to `solve`. + - 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 = DiffEqBase.SciMLLogging.None()` 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]]`. @@ -175,7 +175,7 @@ Previously, we have demonstrated GSA on functions with scalar outputs. However, function peak_cases_2(p) ps = [:β => p[1], :a => p[2], :γ => p[3]] oprob = remake(oprob_base; p = ps) - sol = solve(oprob; maxiters = 100000, verbose = SciMLLogging.None()) + sol = solve(oprob; maxiters = 100000, verbose = DiffEqBase.SciMLLogging.None()) SciMLBase.successful_retcode(sol) || return Inf return [maximum(sol[:E]), maximum(sol[:I])] end diff --git a/docs/src/inverse_problems/optimization_ode_param_fitting.md b/docs/src/inverse_problems/optimization_ode_param_fitting.md index 514ea9a825..9521a1f8bb 100644 --- a/docs/src/inverse_problems/optimization_ode_param_fitting.md +++ b/docs/src/inverse_problems/optimization_ode_param_fitting.md @@ -13,7 +13,7 @@ Pkg.add("OptimizationEvolutionary") Pkg.add("OptimizationOptimJL") Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") -Pkg.add("SciMLLogging") +Pkg.add("DiffEqBase") ``` ```@raw html @@ -23,7 +23,7 @@ Pkg.add("SciMLLogging") ``` The following code provides a brief example of how to perform parameter fitting using the [Optimization.jl](https://github.com/SciML/Optimization.jl) package. ```julia -using Catalyst, OrdinaryDiffEq, OptimizationBase, OptimizationNLopt, SciMLLogging, SymbolicIndexingInterface +using Catalyst, OrdinaryDiffEq, OptimizationBase, OptimizationNLopt, DiffEqBase, SymbolicIndexingInterface # What we know: A model, an initial condition, and sampled datapoints. rn = @reaction_network begin @@ -44,7 +44,7 @@ function loss(p, (oprob_base, p_setter, t_samples, X_samples)) oprob = remake(oprob_base; p) # Simulate the model. If sucesfull, return sum-of-squares distance as loss. - sol = solve(oprob; saveat = t_samples, verbose = SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob; saveat = t_samples, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum(abs2, sol[:X] .- X_samples) end @@ -91,7 +91,7 @@ u0 = [:S => 1.0, :E => 1.0, :SE => 0.0, :P => 0.0] ps_true = [:kB => 1.0, :kD => 0.1, :kP => 0.5] # Generate synthetic data. -using OrdinaryDiffEq, SciMLLogging +using OrdinaryDiffEq, DiffEqBase oprob_true = ODEProblem(rn, u0, (0.0, 10.0), ps_true) true_sol = solve(oprob_true) data_sol = solve(oprob_true; saveat = 1.0) @@ -114,14 +114,14 @@ oprob_base = ODEProblem(rn, u0, (0.0, 10.0), ps_init) function objective_function(p, _) p = Pair.([:kB, :kD, :kP], p) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol .- data_vals) .^2) end ``` When our optimisation algorithm searches parameter space it will likely consider many highly non-plausible parameter sets. To better handle this we: 1. Add `maxiters = 10000` to our `solve` command. As most well-behaved ODEs can be solved in relatively few timesteps, this speeds up the optimisation procedure by preventing us from spending too much time trying to simulate (for the model) unsuitable parameter sets. -2. Add `verbose = SciMLLogging.None()` to our `solve` command. This prevents (potentially a very large number of) warnings from being printed to our output as unsuitable parameter sets are simulated. +2. Add `verbose = DiffEqBase.SciMLLogging.None()` to our `solve` command. This prevents (potentially a very large number of) warnings from being printed to our output as unsuitable parameter sets are simulated. 3. Add the line `SciMLBase.successful_retcode(sol) || return Inf`, which returns an infinite value for parameter sets which does not lead to successful simulations. To improve optimisation performance, rather than creating a new `ODEProblem` in each iteration, we pre-declare one which we [apply `remake` to](@ref simulation_structure_interfacing_problems_remake). We also use the `saveat = data_ts, save_idxs = :P` arguments to only save the values of the measured species at the measured time points. @@ -195,7 +195,7 @@ In this case we simply modify our objective function to take this into account: function objective_function_S_P(p, _) p = Pair.([:kB, :kD, :kP], p) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = [:S, :P], verbose = SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = [:S, :P], verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol[:S] .- data_vals_S) .^2 + (sol[:P] .- data_vals_P) .^2) end @@ -229,7 +229,7 @@ If we from previous knowledge know that $kD = 0.1$, and only want to fit the val function objective_function_known_kD(p, _) p = Pair.([:kB, :kD, :kP], [p[1], 0.1, p[2]]) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol .- data_vals) .^2) end @@ -269,7 +269,7 @@ corresponds to the same true parameter values as used previously (`[:kB => 1.0, function objective_function_logtransformed(p, _) p = Pair.([:kB, :kD, :kP], 10.0 .^ p) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol .- data_vals) .^2) end diff --git a/docs/src/inverse_problems/turing_ode_param_fitting.md b/docs/src/inverse_problems/turing_ode_param_fitting.md index 7a8044fe35..f7173c668a 100644 --- a/docs/src/inverse_problems/turing_ode_param_fitting.md +++ b/docs/src/inverse_problems/turing_ode_param_fitting.md @@ -10,7 +10,7 @@ Pkg.add("Catalyst") Pkg.add("Distributions") Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") -Pkg.add("SciMLLogging") +Pkg.add("DiffEqBase") Pkg.add("StatsPlots") Pkg.add("SymbolicIndexingInterface") Pkg.add("Turing") @@ -31,7 +31,7 @@ sir = @reaction_network begin end # Generate some (synthetic) data for the fitting procedure. -using Distributions, OrdinaryDiffEq, Plots, SciMLLogging +using Distributions, OrdinaryDiffEq, Plots, DiffEqBase t_measurement = 1.0:100.0 u0 = [:S => 999.0, :I => 1.0, :R => 0.0] p_true = [:γ => 0.0003, :ν => 0.1] @@ -57,7 +57,7 @@ using Turing, MCMCChains # Simulate the model for parameter values γ, ν. Saves the solution at the measurement times. p = setp_oop(oprob_base, [γ, ν]) oprob_base = remake(oprob_base; p) - sol = solve(oprob_base; saveat, verbose = SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob_base; saveat, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) # If simulation was unsuccessful, the likelihood is -Inf. if !SciMLBase.successful_retcode(sol) @@ -105,7 +105,7 @@ end ``` From an initial condition where only a small fraction of the population is in the infected state, the model exhibits a peak of infections, after which the epidemic subsides. ```@example turing_paramfit -using OrdinaryDiffEq, Plots, SciMLLogging +using OrdinaryDiffEq, Plots, DiffEqBase u0 = [:S => 999.0, :I => 1.0, :R => 0.0] p_true = [:γ => 0.0005, :ν => 0.1] oprob_true = ODEProblem(sir, u0, 100.0, p_true) @@ -143,7 +143,7 @@ setp_oop = SymbolicIndexingInterface.setp_oop(oprob_true, [:γ, :ν]) # Simulate the model for parameter values γ, ν. Saves the solution at the measurement times. p = setp_oop(oprob_base, [γ, ν]) oprob_base = remake(oprob_base; p) - sol = solve(oprob_base; saveat, verbose = SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob_base; saveat, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) # If simulation was unsuccessful, the likelihood is -Inf. if !SciMLBase.successful_retcode(sol) @@ -160,7 +160,7 @@ nothing # hide ``` Some specific comments regarding how we have declared the model above: -- Like for [normal parameter fitting](@ref optimization_parameter_fitting_basics), we use the `maxiters = 10000` (to prevent spending a long time simulating unfeasible parameter sets) and `verbose = SciMLLogging.None()` (to prevent unnecessary printing of warning messages) arguments to `solve`. +- Like for [normal parameter fitting](@ref optimization_parameter_fitting_basics), we use the `maxiters = 10000` (to prevent spending a long time simulating unfeasible parameter sets) and `verbose = DiffEqBase.SciMLLogging.None()` (to prevent unnecessary printing of warning messages) arguments to `solve`. - Again, we need to handle parameter sets where the model cannot be successfully simulated. Here, we use `Turing.@addlogprob! -Inf` to set a non-existent likelihood, and `return nothing` to stop further evaluation of the specific parameter set. - Just like for normal parameter fitting, we wish to [fit parameters on a log scale](@ref optimization_parameter_fitting_log_scale). Here we do so by declaring log-scaled prior distributions. - Here we assume that we (correctly) know that the noise is normally distributed. However, we assume that we *do not know the standard deviation*. Instead, we make the standard deviation a third parameter whose value we infer as part of the inference process. More complicated noise formulas can be used (and are sometimes even advisable[^2]). diff --git a/docs/src/steady_state_functionality/dynamical_systems.md b/docs/src/steady_state_functionality/dynamical_systems.md index 31af340b9e..e4ab655a50 100644 --- a/docs/src/steady_state_functionality/dynamical_systems.md +++ b/docs/src/steady_state_functionality/dynamical_systems.md @@ -9,8 +9,7 @@ Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted Pkg.add("CairoMakie") Pkg.add("Catalyst") Pkg.add("DynamicalSystems") -Pkg.add("OrdinaryDiffEqRosenbrock") -Pkg.add("OrdinaryDiffEqTsit5") +Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") ``` ```@raw html @@ -41,7 +40,7 @@ nothing # hide ``` Next, for any application of DynamicalSystems.jl, our `ODEProblem` must be converted into a so-called `CoupledODEs` structure. This is done by combining the ODE with the solver (and potential solver options) with which we wish to simulate it (just like when it is simulated using `solve`). Here, we will simply designate the `Tsit5` numeric solver (but provide no other options). ```@example dynamical_systems_basins -using DynamicalSystems, OrdinaryDiffEqTsit5 +using DynamicalSystems, OrdinaryDiffEq ds = CoupledODEs(oprob, (alg = Tsit5(),)) ``` We can now compute the basins of attraction. This is done by first creating a grid that designates which subspace of phase-space we wish to investigate (here, the corresponding basin of attraction is found for every point on the grid). Next, we create a `AttractorsViaRecurrences` struct, that maps initial conditions to attractors, and then use that as input to the `basins_of_attraction` function. @@ -88,7 +87,7 @@ end ``` We can simulate the model, noting that its behaviour seems chaotic. ```@example dynamical_systems_lyapunov -using OrdinaryDiffEqRosenbrock, Plots +using OrdinaryDiffEq, Plots u0 = [:X => 1.5, :Y => 1.5, :Z => 1.5] tspan = (0.0, 100.0) @@ -101,10 +100,10 @@ plot(sol; idxs=(:X, :Y, :Z)) Next, like when we [computed basins of attraction](@ref dynamical_systems_basins_of_attraction), we create a `CoupledODEs` corresponding to the model and state for which we wish to compute our Lyapunov spectrum. Like previously, `tspan` must provide some small interval (at least `(0.0, 1.0)` is recommended), but else have no impact on the computed Lyapunov spectrum. ```@example dynamical_systems_lyapunov using DynamicalSystems -ds = CoupledODEs(oprob, (alg = Rodas5P(autodiff = false),)) +ds = CoupledODEs(oprob, (alg = Rodas5P(autodiff = AutoFiniteDiff()),)) nothing # hide ``` -Here, the `autodiff = false` argument is required when Lyapunov spectrums are computed. We can now provide our `CoupledODEs` (`ds`) to `lyapunovspectrum` to compute the lyapunov spectrum. This function requires a second argument (here set to `100`). Generally setting this to a higher value will increase accuracy, but also increase runtime (since `lyapunovspectrum` is fast for most systems, setting this to a large value is recommended). +Here, the `autodiff = AutoFiniteDiff()` argument requests finite-difference derivatives when Lyapunov spectrums are computed. We can now provide our `CoupledODEs` (`ds`) to `lyapunovspectrum` to compute the lyapunov spectrum. This function requires a second argument (here set to `100`). Generally setting this to a higher value will increase accuracy, but also increase runtime (since `lyapunovspectrum` is fast for most systems, setting this to a large value is recommended). ```@example dynamical_systems_lyapunov lyapunovspectrum(ds, 100) ``` @@ -112,7 +111,7 @@ Here, the largest exponent is positive, suggesting that the model is chaotic (or 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 -using OrdinaryDiffEqTsit5 +using OrdinaryDiffEq brusselator = @reaction_network begin A, ∅ --> X 1, 2X + Y --> 3X @@ -134,7 +133,7 @@ plot!(osol2; idxs = (:X, :Y)) ``` Next, we compute the Lyapunov spectrum at one of the initial conditions: ```@example dynamical_systems_lyapunov -ds = CoupledODEs(oprob1, (alg = Rodas5P(autodiff = false),)) +ds = CoupledODEs(oprob1, (alg = Rodas5P(autodiff = AutoFiniteDiff()),)) lyapunovspectrum(ds, 100) ``` Here, all Lyapunov exponents are negative, confirming that the brusselator is non-chaotic. From 90d8a7c2da51571bd21638b5d787191190485b6a Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Tue, 16 Jun 2026 14:55:30 +0100 Subject: [PATCH 4/8] finish writing, udpated doc env --- docs/Project.toml | 6 + docs/src/inverse_problems/udes.md | 357 ++++++++++++++++++++++++++++-- 2 files changed, 343 insertions(+), 20 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 0547d99a4b..7ed9d83d06 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -21,12 +21,15 @@ Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" LikelihoodProfiler = "93acb638-a083-5915-8dce-d129bc6a3f59" LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" +Lux = "b2108857-7c20-44ae-9111-449ecde12c47" MCMCChains = "c7f686f2-ff18-58e9-bc7b-31028e88f75d" ModelingToolkitBase = "7771a370-6774-4173-bd38-47e70ca0b839" +ModelingToolkitNeuralNets = "f162e290-f571-43a6-83d9-22ecc16da15f" NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a" NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" NonlinearSolveFirstOrder = "5959db7a-ea39-4486-b5fe-2dd0bf03d60d" Optim = "429524aa-4258-5aef-a3af-852621145aeb" +Optimisers = "3bd65402-5787-11e9-1adc-39752487f4e2" OptimizationBBO = "3e6eede4-6085-4f62-9a71-46d9bc1eb92b" OptimizationBase = "bca83a33-5cc9-4baa-983d-23429ab6bcbb" OptimizationLBFGSB = "22f7324a-a79d-40f2-bebe-3af60c77bd15" @@ -79,12 +82,15 @@ JumpProcesses = "9.23" Latexify = "0.16.5" LikelihoodProfiler = "1.5.1" LinearSolve = "2.30, 3" +Lux = "1.31.4" MCMCChains = "6, 7" ModelingToolkitBase = "1.17" +ModelingToolkitNeuralNets = "2.5.1" NetworkLayout = "0.4" NonlinearSolve = "4" NonlinearSolveFirstOrder = "1, 2.1" Optim = "2" +Optimisers = "0.4.7" OptimizationBBO = "0.4" OptimizationBase = "4, 5.0" OptimizationLBFGSB = "1" diff --git a/docs/src/inverse_problems/udes.md b/docs/src/inverse_problems/udes.md index a085ee9299..f647e05b7c 100644 --- a/docs/src/inverse_problems/udes.md +++ b/docs/src/inverse_problems/udes.md @@ -22,49 +22,368 @@ Pkg.add("Plots") ```@raw html
Quick-start example ``` -The following code provides a brief example of how to perform parameter fitting using the [Optimization.jl](https://github.com/SciML/Optimization.jl) package. +The following code provides a brief example of how to declare a UDE using Catalyst, +[ModelingToolkitNeuralNets.jl](https://github.com/SciML/ModelingToolkitNeuralNets.jl), and +[Lux.jl](https://github.com/LuxDL/Lux.jl), and then fit it using +[PEtab.jl](https://github.com/sebapersson/PEtab.jl). ```julia # Declare the model (a mutual activation loop). using Catalyst +rn = @reaction_network begin + hill(Y, v, K, n), 0 --> X + X, 0 --> Y + d, (X, Y) --> 0 +end # Generate synthetic data to which we will apply the fitting procedure. using Distributions, OrdinaryDiffEq, Plots +u0 = [:X => 2.0, :Y => 0.1] +ps_true = [:v => 1.1, :K => 2.0, :n => 3.0, :d => 0.5] +tend = 45.0 +oprob_true = ODEProblem(rn, u0, tend, ps_true) +sol_true = solve(oprob_true) +sample_t = range(0.0, tend; length = 20) +σ = 0.1 +sample_X = [rand(Normal(X, σ)) for X in sol_true(sample_t; idxs = :X)] +sample_Y = [rand(Normal(Y, σ)) for Y in sol_true(sample_t; idxs = :Y)] +plot(sol_true; lw = 6, label = ["X (true)" "Y (true)"]) +plot!(sample_t, sample_X, seriestype = :scatter, label = "X (data)", color = 1, ms = 6) +plot!(sample_t, sample_Y, seriestype = :scatter, label = "Y (data)", color = 2, ms = 6) -# Create a UDE using Lu and ModelingToolkitNeuralNets. +# Create a UDE using Lux and ModelingToolkitNeuralNets. using ModelingToolkitNeuralNets, Lux +nn_arch = Lux.Chain( + Lux.Dense(1 => 3, Lux.softplus, use_bias = false), + Lux.Dense(3 => 3, Lux.softplus, use_bias = false), + Lux.Dense(3 => 1, Lux.softplus, use_bias = false), +) +@SymbolicNeuralNetwork U, θ = nn_arch +A(z) = U(z, θ)[1] +rn_ude = @reaction_network begin + $A(Y), 0 --> X + X, 0 --> Y + d, (X, Y) --> 0 +end -# Fit the model using PEtab. -using PEtab, Optimisers +# Bundle the UDE and data into a PEtab problem. +using DataFrames, PEtab +ps_est = [PEtabMLParameter(:θ), PEtabParameter(:d)] +observables = [PEtabObservable(:obs_X, :X, σ), PEtabObservable(:obs_Y, :Y, σ)] +dfX = DataFrame(obs_id = "obs_X", time = sample_t, measurement = sample_X) +dfY = DataFrame(obs_id = "obs_Y", time = sample_t, measurement = sample_Y) +measurements = vcat(dfX, dfY) +petab_model = PEtabModel(rn_ude, observables, measurements, ps_est; speciemap = u0) +petab_prob = PEtabODEProblem(petab_model) -# Plot the fitted simulation and recovered activation functions. -plot() -plot() +# Fit the unknown function and parameters using 5 independent starts of 10,000 Adam iterations. +using Optimisers +options = OptimisersOptions(iterations = 10000) +@time res = calibrate_multistart(petab_prob, Optimisers.Adam(1e-3), 5; options) + +# Plot the fitted simulation and the recovered activation functions. +plot(res, petab_prob) +plot(res, petab_prob; plot_type = :best_function) ``` ```@raw html
``` \ -Default stuff open meues with env and copy example. -## Fitting a rate-based UDE +In this tutorial, we describe how to create and fit *universal differential equation* (UDE) +models declared through Catalyst. UDEs are a *hybrid modelling* framework: they combine +mechanistic modelling (where models are built from first principles) with data-driven +modelling (where models are learnt from data)[^1]. The core idea is to declare known model +components as standard differential equations, while allowing unknown system +dynamics to be learnt from data. In practice, this is done by inserting *universal function +approximators* (typically neural networks) into differential equations to represent unknown dynamics. + +In the context of Catalyst and chemical reaction networks, UDEs can naturally be used to +learn *reaction rates*. Non-constant reaction rates are typically modelled by +heuristically chosen functions, and in some cases a more natural choice is to learn these +rate functions directly from data[^2]. This can be used to, for example, learn a protein's +production rate as a function of a transcription factor's concentration. + +In this tutorial, we use [Lux.jl](https://github.com/LuxDL/Lux.jl) to declare neural +networks, [ModelingToolkitNeuralNets.jl](https://github.com/SciML/ModelingToolkitNeuralNets.jl) +to incorporate them into Catalyst models, and +[PEtab.jl](https://github.com/sebapersson/PEtab.jl) to fit these models to data. More +details on these packages can be found in their respective documentation, while we here will describe +how they compose with each other and Catalyst. + +## [Fitting a rate-based UDE](@id udes_basic_example) + +For this example, we will use a mutual activation loop in which two species ($X$ and $Y$) +activate each other's production. +```@example ude_rate_based +using Catalyst +rn_true = @reaction_network begin + hill(Y, v, K, n), 0 --> X + X, 0 --> Y + d, (X, Y) --> 0 +end +``` +Here, the production of $X$ follows a Hill function, which we will later try to recover +through a UDE. + +First, we designate the true simulation conditions, from which we will generate a synthetic +dataset for the fitting procedure. +```@example ude_rate_based +using Distributions, OrdinaryDiffEq, Plots + +# Simulate the model at the true conditions. +u0 = [:X => 2.0, :Y => 0.1] +ps_true = [:v => 1.1, :K => 2.0, :n => 3.0, :d => 0.5] +tend = 45.0 +oprob_true = ODEProblem(rn_true, u0, (0.0, tend), ps_true) +sol_true = solve(oprob_true) -Short introduction to rate-based UDEs goes here. +# Generate normally distributed noisy measurements with a fixed standard deviation. +t_measurement = range(0.0, tend; length = 20) +σ = 0.1 +X_observed = [rand(Normal(X, σ)) for X in sol_true(t_measurement; idxs = :X)] +Y_observed = [rand(Normal(Y, σ)) for Y in sol_true(t_measurement; idxs = :Y)] -Generate synthetic data. +# Plots the true simulation and the synthetic data. +plot(sol_true; lw = 4, label = ["X (true)" "Y (true)"]) +plot!(t_measurement, X_observed; seriestype = :scatter, label = "X (data)", color = 1, ms = 5) +plot!(t_measurement, Y_observed; seriestype = :scatter, label = "Y (data)", color = 2, ms = 5) +``` + +Next, we declare a UDE version of the model by replacing the Hill function with a neural +network. The neural network will be created using Lux. Here, a relatively small +feed-forward architecture is sufficient. By using a non-negative activation function in the +final layer (here, [softplus](https://en.wikipedia.org/wiki/Softplus)), we constrain the +fitted function to be non-negative, which is a known physical constraint for reaction rate +functions. Softplus is also smooth, a desirable property for functions embedded in ODE +simulations. A full list of available activation functions can be found +[here](https://lux.csail.mit.edu/stable/api/NN_Primitives/ActivationFunctions). +```@example ude_rate_based +using Lux +nn_arch = Lux.Chain( + Lux.Dense(1 => 3, Lux.softplus, use_bias = false), + Lux.Dense(3 => 3, Lux.softplus, use_bias = false), + Lux.Dense(3 => 1, Lux.softplus, use_bias = false) +) +``` +We now generate a `SymbolicNeuralNetwork` using ModelingToolkitNeuralNets. This embeds the +Lux model in a symbolic format that is compatible with Catalyst (and ModelingToolkit). +```@example ude_rate_based +using ModelingToolkitNeuralNets +@SymbolicNeuralNetwork U, θ = nn_arch +``` +Here, `U` represents the neural network structure, and `θ` represents its parameters. Additional options for using the `@SymbolicNeuralNetwork` macro are described [here](https://sciml.github.io/ModelingToolkitNeuralNets.jl/stable/api/#ModelingToolkitNeuralNets.@SymbolicNeuralNetwork) and [here](https://sciml.github.io/ModelingToolkitNeuralNets.jl/stable/symbolic_ude_tutorial/). Next, using the following syntax, we can [interpolate](@ref dsl_advanced_options_symbolics_and_DSL_interpolation) this function approximator as a Catalyst model rate. +```@example ude_rate_based +A(z) = U(z, θ)[1] +rn_ude = @reaction_network begin + $A(Y), 0 --> X + X, 0 --> Y + d, (X, Y) --> 0 +end +``` +We can inspect the parameters contained within this model: +```@example ude_rate_based +parameters(rn_ude) +``` +In addition to the mechanistic parameter $d$, the neural network parameters $θ$ have also +been incorporated into the model. The neural network architecture, $U$, is also represented +as a parameter, but in practice this paraemter is fixed and can be ignored. -Declare UDE +Now, we use PEtab to simultaneously fit $d$ and $θ$. The workflow is almost identical to +the standard [PEtab parameter fitting workflow](@ref petab_parameter_fitting). The main +differences are that we declare $θ$ as a `PEtabMLParameter` (rather than a +`PEtabParameter`), and that we choose an optimisation algorithm better suited to neural +network training. We begin by declaring the parameters we wish to fit, the observables (and +their noise formulas), and the measurements, and then use these to generate a +`PEtabODEProblem`. +```@example ude_rate_based +using DataFrames, PEtab +ps_est = [PEtabMLParameter(:θ), PEtabParameter(:d)] +observables = [PEtabObservable(:obs_X, :X, σ), PEtabObservable(:obs_Y, :Y, σ)] +dfX = DataFrame(obs_id = "obs_X", time = t_measurement, measurement = X_observed) +dfY = DataFrame(obs_id = "obs_Y", time = t_measurement, measurement = Y_observed) +measurements = vcat(dfX, dfY) +petab_model = PEtabModel(rn_ude, observables, measurements, ps_est; speciemap = u0) +petab_prob = PEtabODEProblem(petab_model) +nothing # hide +``` +PEtab supports many options for customising the optimisation problem. Here, we choose the +simplest settings. These options are described further in the [Catalyst-specific PEtab +tutorial](@ref petab_parameter_fitting), and in +[PEtab's documentation](https://sebapersson.github.io/PEtab.jl/stable/). -Fit using PEtab +The UDE model can now be fitted to the data. Here, we use the Adam implementation from the +[Optimisers.jl](https://github.com/FluxML/Optimisers.jl) package, running 5 independent starts +with 10,000 iterations each. +```@example ude_rate_based +using Optimisers +options = OptimisersOptions(iterations = 10000) +@time res = calibrate_multistart(petab_prob, Optimisers.Adam(1e-3), 5; options) +nothing # hide +``` + +PEtab's plot recipes can be applied to fitted UDE models without adaptation. Here, we plot +the fitted UDE together with the data. +```@example ude_rate_based +plot(res, petab_prob; lw = 4, la = 0.6, ms = 5, title = "Final fit") +``` +In the next section, we will show different UDE-specific plotting options. + +### [Investigating fitted neural networks](@id udes_nn_investigation) + +In many cases, UDEs are used to fit unknown model *functions*. When a fitted function has a +single input argument, the final fit can often be interpreted by directly plotting that +function. With PEtab, this can be done using +```@example ude_rate_based +plot(res, petab_prob; plot_type = :best_function, lw = 6) +``` +Since we use synthetic data, we can also compare the fitted function to the ground-truth +function: +```@example ude_rate_based +true_f(Y) = Catalyst.hill(Y, 1.1, 2.0, 3.0) +plot!(true_f, 0.1, 3.8, linestyle = :dash, lw = 3, color = :blue, label = "True activation function") +``` +This lets us check whether the correct function has been correctly recovered. + +When carrying out multistart calibration, PEtab also provides the +`plot_type = :function_ensemble` option, which plots the functional forms found by each +optimisation run. In practice, some runs might not have converged. Here, we use the +`loss_thres` option to only plot functions associated with optimisation runs with a similar +loss value to the best run: +```@example ude_rate_based +plot(res, petab_prob; plot_type = :function_ensemble, lw = 4, xlimit = (0.0, 3.8), loss_thres = res.fmin + 2.0) +``` +Here, we can see that all optimisation runs achieving a good model-to-data fit converge to +the same functional form. In practice, this can be used as a simple form of +[*identifiability analysis*](@ref likelihood_profiler_practical_identifiability), assessing +the extent to which a single functional form can reliably be recovered from the data[^2]. + +A more thorough description of these plots and their options can be found +[here](https://sebapersson.github.io/PEtab.jl/dev/tutorials/sciml/sciml_plotting). To +retrieve the fitted function, for example to evaluate it manually at selected values, use: +```@example ude_rate_based +fitted_oprob = get_odeproblem(res, petab_prob)[1] +fitted_f(z) = fitted_oprob.ps[U](z, fitted_oprob.ps[θ])[1] +fitted_f(1.0) +``` + +## [Alternative approaches for incorporating neural networks into models](@id udes_alternative_forms) +Finally, we consider some additional contexts in which neural networks can be incorporated +into Catalyst models. + +### [Declaring UDEs programmatically](@id udes_programmatic) +While this tutorial has primarily created UDEs using the Catalyst `@reaction_network` DSL, +it is also possible to create them [programmatically](@ref programmatic_CRN_construction). +Below, we create a UDE representation of the same mutual activation loop as previously, but +using the programmatic interface. +```@example ude_programmatic +using Catalyst, ModelingToolkitNeuralNets, Lux +t = default_t() +@species X(t) Y(t) +@parameters d +nn_arch = Lux.Chain( + Lux.Dense(1 => 3, Lux.softplus, use_bias = false), + Lux.Dense(3 => 3, Lux.softplus, use_bias = false), + Lux.Dense(3 => 1, Lux.softplus, use_bias = false), +) +@SymbolicNeuralNetwork U, θ = nn_arch +rxs = [ + Reaction(U(Y, θ)[1], [], [X]), + Reaction(X, [], [Y]), + Reaction(d, [X], []), + Reaction(d, [Y], []), +] +@named rs = ReactionSystem(rxs, t) +rs = complete(rs) +``` +ModelingToolkitNeuralNets also provides a function version of the `@SymbolicNeuralNetwork` +macro, which provides some additional functionality. It can be found +[here](https://sciml.github.io/ModelingToolkitNeuralNets.jl/stable/api/#ModelingToolkitNeuralNets.SymbolicNeuralNetwork). + +### [Non-rate based UDEs](@id udes_non_rate_based) +Above, we described how a neural network can be used to learn an unknown rate function. +However, symbolic neural networks created using `@SymbolicNeuralNetwork` are highly +flexible: they act as normal functions and can be used wherever such functions can be used. +For example, neural networks can be inserted into [coupled equations](@ref coupled_models_diffeqs). +Here, we use this to model a cell growing in a nutrient ($N$). The rate at which the nutrient is +consumed is modelled as an unknown function of both a nutrient-activated protein's active form ($Xₐ$) +and the nutrient's concentration. Note that the unknown function has two input variables, which is +straightforward to handle. +```@example ude_equations +# Create a symbolic neural network depending on two input variables. +using ModelingToolkitNeuralNets, Lux +nn_arch = Lux.Chain( + Lux.Dense(2 => 3, Lux.softplus, use_bias = false), + Lux.Dense(3 => 3, Lux.softplus, use_bias = false), + Lux.Dense(3 => 1, Lux.softplus, use_bias = false), +) +@SymbolicNeuralNetwork U, θ = nn_arch +C(z1, z2) = U(z1, z2, θ)[1] + +# Create a model of a cell that consumes a limited nutrient supply. +using Catalyst +rs = @reaction_network begin + @equations D(N) ~ -$C(N, Xₐ) + (kₐ*N, kᵢ), Xᵢ <--> Xₐ +end +``` + +### [Non-ODE UDEs](@id udes_non_odes) +Up until now, we have assumed that all fitted universal differential equations are based on +ODEs. However, in practice, UDEs declared through Catalyst act as normal differential +equations (although with an unusually large number of parameters). This means that `JumpProblem`s +and `SDEProblem`s can be generated from Catalyst UDEs using the standard syntax, for example: +```@example ude_sde +# Create the UDE. +using Catalyst, ModelingToolkitNeuralNets, Lux +nn_arch = Lux.Chain( + Lux.Dense(1 => 3, Lux.softplus, use_bias = false), + Lux.Dense(3 => 3, Lux.softplus, use_bias = false), + Lux.Dense(3 => 1, Lux.softplus, use_bias = false) +) +@SymbolicNeuralNetwork U, θ = nn_arch +A(z) = U(z, θ)[1] +rn_ude = @reaction_network begin + $A(Y), 0 --> X + X, 0 --> Y + d, (X, Y) --> 0 +end + +# Create an SDEProblem. +u0 = [:X => 2.0, :Y => 0.1] +ps = [:d => 0.5] +sprob = SDEProblem(rn_ude, u0, 45.0, ps) +nothing # hide +``` +generates a universal stochastic differential equation. -Plotting the results +The parameters of this model cannot currently be fitted using PEtab. However, one can use +an approach similar to those used [here](@ref optimization_parameter_fitting) and +[here](@ref behaviour_optimisation) to declare a loss function comparing the behaviour of +this UDE with available data. Next, the parameters can be fitted by minimising that loss +function. An example of how this is done for an ODE-declared UDE can be found +[here](https://sciml.github.io/ModelingToolkitNeuralNets.jl/stable/symbolic_ude_tutorial/). +### [Learning parameters or observables using neural networks](@id udes_parameters_n_observables) +Throughout this tutorial, we have shown how neural networks can be incorporated into +Catalyst models to learn unknown functions of system variables within the main model +system. PEtab, however, also supports two additional ways to incorporate neural networks +that, due to how they are inserted, can be handled more efficiently: +- Models where the neural network exists within the mapping between the model's *states* and + its *observables*. +- Models where parameters are neural-network mappings of other parameter values. Primarily, + this can be used to learn phenotypical parameters (such as binding rates) from measurable + parameters (such as protein structure data). +To learn more about how to declare such models, please see the following two PEtab +tutorials: [observable neural networks](https://sebapersson.github.io/PEtab.jl/dev/tutorials/sciml/observable) +and [pre-simulation neural networks](https://sebapersson.github.io/PEtab.jl/dev/tutorials/sciml/pre_simulate). --- -## [Citations](@id petab_citations) +## [Citations](@id udes_citations) -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 Lux.jl and PEtab.jl packages: +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 Lux.jl and PEtab.jl packages: ```bibtex @software{pal2025lux, @@ -93,8 +412,6 @@ If you use this functionality in your research, [in addition to Catalyst](@ref d ## References -[^1]: - [Rackauckas, R et al. _Universal differential equations for scientific machine learning_, arXiv (2020).](https://arxiv.org/abs/2001.04385) +[^1]: [Rackauckas, R et al. _Universal differential equations for scientific machine learning_, arXiv (2020).](https://arxiv.org/abs/2001.04385) -[^2]: - [Loman, T and R, Baker. _Functional and parametric identifiability for universal differential equations applied to chemical reaction networks_, arXiv (2025).](https://arxiv.org/abs/2510.14140) +[^2]: [Loman, T and R, Baker. _Functional and parametric identifiability for universal differential equations applied to chemical reaction networks_, arXiv (2025).](https://arxiv.org/abs/2510.14140) From 0d4feab9ec594227fd513e867e1cd6e7b8c1bc20 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 22 Jun 2026 14:54:50 +0100 Subject: [PATCH 5/8] Use SciMLLogging correctly --- docs/Project.toml | 2 +- .../inverse_problems/behaviour_optimisation.md | 7 +++---- .../examples/ode_fitting_oscillation.md | 4 +--- .../global_sensitivity_analysis.md | 13 ++++++------- .../optimization_ode_param_fitting.md | 17 ++++++++--------- .../turing_ode_param_fitting.md | 11 +++++------ 6 files changed, 24 insertions(+), 30 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 0a3bb22ae5..4b801d6be1 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -85,7 +85,7 @@ OptimizationLBFGSB = "1" OptimizationNLopt = "0.3" OptimizationOptimJL = "0.4" OptimizationOptimisers = "0.3" -OrdinaryDiffEq = "6, 7" +OrdinaryDiffEq = "7.0.1" PEtab = "5" Plots = "1.40" QuasiMonteCarlo = "0.3" diff --git a/docs/src/inverse_problems/behaviour_optimisation.md b/docs/src/inverse_problems/behaviour_optimisation.md index 3566bef7b7..14b7df3e1b 100644 --- a/docs/src/inverse_problems/behaviour_optimisation.md +++ b/docs/src/inverse_problems/behaviour_optimisation.md @@ -11,7 +11,6 @@ Pkg.add("OptimizationBase") Pkg.add("OptimizationBBO") Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") -Pkg.add("DiffEqBase") ``` ```@raw html @@ -36,7 +35,7 @@ end ``` To demonstrate this pulsing behaviour we will simulate the system for an example parameter set. We select an initial condition (`u0`) so the system begins in a steady state. ```@example behaviour_optimization -using OrdinaryDiffEq, Plots, DiffEqBase +using OrdinaryDiffEq, Plots example_p = [:pX => 0.1, :pY => 1.0, :pZ => 1.0] tspan = (0.0, 50.0) example_u0 = [:X => 0.1, :Y => 0.1, :Z => 1.0] @@ -53,7 +52,7 @@ function pulse_amplitude(p, _) p = Dict([:pX => p[1], :pY => p[2], :pZ => p[2]]) u0 = [:X => p[:pX], :Y => p[:pX]*p[:pY], :Z => p[:pZ]/p[:pY]^2] oprob_local = remake(oprob; u0, p) - sol = solve(oprob_local; verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob_local; verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return -(maximum(sol[:Z]) - sol[:Z][1]) end @@ -61,7 +60,7 @@ nothing # hide ``` This objective function takes two arguments (a parameter value `p`, and an additional one which we will ignore but is discussed in a note [here](@ref optimization_parameter_fitting_basics)). 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 objective function provided, input parameter set. Finally, Optimization.jl finds the function's *minimum value*, so to find the *maximum* relative pulse amplitude, we make our objective function return the negative pulse amplitude. -As described [in our tutorial on parameter fitting using Optimization.jl](@ref optimization_parameter_fitting_basics) we use `remake`, `verbose = DiffEqBase.SciMLLogging.None()`, `maxiters = 10000`, and a check on the simulations return code, all providing various advantages to the optimisation procedure (as explained in that tutorial). +As described [in our tutorial on parameter fitting using Optimization.jl](@ref optimization_parameter_fitting_basics) we use `remake`, `verbose = SciMLLogging.None()`, `maxiters = 10000`, and a check on the simulations return code, all providing various advantages to the optimisation procedure (as explained in that tutorial). Just like for [parameter fitting](@ref optimization_parameter_fitting_basics), we create an `OptimizationProblem` using our objective function, and some initial guess of the parameter values. We also [set upper and lower bounds](@ref optimization_parameter_fitting_constraints) 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 diff --git a/docs/src/inverse_problems/examples/ode_fitting_oscillation.md b/docs/src/inverse_problems/examples/ode_fitting_oscillation.md index 889f1ebaa4..63be50fa8e 100644 --- a/docs/src/inverse_problems/examples/ode_fitting_oscillation.md +++ b/docs/src/inverse_problems/examples/ode_fitting_oscillation.md @@ -11,7 +11,6 @@ Pkg.add("OptimizationBase") Pkg.add("OptimizationOptimisers") Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") -Pkg.add("DiffEqBase") Pkg.add("SciMLSensitivity") ``` ```@raw html @@ -27,7 +26,6 @@ using Catalyst using OrdinaryDiffEq using OptimizationBase using OptimizationOptimisers # Required for the ADAM optimizer. -using DiffEqBase using SciMLSensitivity # Required for the `AutoZygote()` automatic differentiation option. ``` @@ -80,7 +78,7 @@ function optimize_p(pinit, tend, p = set_p(prob, p) newtimes = filter(<=(tend), sample_times) newprob = remake(prob; p) - sol = Array(solve(newprob, Rosenbrock23(); saveat = newtimes, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000)) + sol = Array(solve(newprob, Rosenbrock23(); saveat = newtimes, verbose = SciMLLogging.None(), maxiters = 10000)) loss = sum(abs2, sol .- sample_vals[:, 1:size(sol,2)]) return loss end diff --git a/docs/src/inverse_problems/global_sensitivity_analysis.md b/docs/src/inverse_problems/global_sensitivity_analysis.md index cfee8cffc7..652c145831 100644 --- a/docs/src/inverse_problems/global_sensitivity_analysis.md +++ b/docs/src/inverse_problems/global_sensitivity_analysis.md @@ -10,7 +10,6 @@ Pkg.add("Catalyst") Pkg.add("GlobalSensitivity") Pkg.add("Plots") Pkg.add("OrdinaryDiffEq") -Pkg.add("DiffEqBase") ``` ```@raw html @@ -20,7 +19,7 @@ Pkg.add("DiffEqBase") ``` The following code provides a brief example of how to perform global sensitivity analysis using the [GlobalSensitivity.jl](https://github.com/SciML/GlobalSensitivity.jl) package. ```julia -using Catalyst, GlobalSensitivity, OrdinaryDiffEq, DiffEqBase, SymbolicIndexingInterface +using Catalyst, GlobalSensitivity, OrdinaryDiffEq, SymbolicIndexingInterface # Designates a model, parameter set, and set of initial conditions. # For this infectious disease model we will determine the peak number of cases's sensitivity to the parameters. @@ -39,7 +38,7 @@ function peak_cases(p) # Updates the ODEProblem with teh proposed parameter set. p = p_setter(oprob_base, p) oprob = remake(oprob_base; p) - sol = solve(oprob; maxiters = 100000, verbose = DiffEqBase.SciMLLogging.None()) + sol = solve(oprob; maxiters = 100000, verbose = SciMLLogging.None()) return maximum(sol[:I]) end @@ -75,7 +74,7 @@ end ``` We will study the peak number of infected cases's ($max(I(t))$) sensitivity to the system's three parameters. We create a function which simulates the system from a given initial condition and measures this property: ```@example gsa_1 -using OrdinaryDiffEq, DiffEqBase +using OrdinaryDiffEq u0 = [:S => 999.0, :I => 1.0, :E => 0.0, :R => 0.0] p_dummy = [:β => 0.0, :a => 0.0, :γ => 0.0] @@ -84,7 +83,7 @@ oprob_base = ODEProblem(seir_model, u0, (0.0, 10000.0), p_dummy) function peak_cases(p) ps = [:β => p[1], :a => p[2], :γ => p[3]] oprob = remake(oprob_base; p = ps) - sol = solve(oprob; maxiters = 100000, verbose = DiffEqBase.SciMLLogging.None()) + sol = solve(oprob; maxiters = 100000, verbose = SciMLLogging.None()) SciMLBase.successful_retcode(sol) || return Inf return maximum(sol[:I]) end @@ -107,7 +106,7 @@ on the domain $10^β ∈ (-3.0,-1.0)$, $10^a ∈ (-2.0,0.0)$, $10^γ ∈ (-2.0,0 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_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 = DiffEqBase.SciMLLogging.None()` arguments to `solve`. + - 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 = SciMLLogging.None()` 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]]`. @@ -175,7 +174,7 @@ Previously, we have demonstrated GSA on functions with scalar outputs. However, function peak_cases_2(p) ps = [:β => p[1], :a => p[2], :γ => p[3]] oprob = remake(oprob_base; p = ps) - sol = solve(oprob; maxiters = 100000, verbose = DiffEqBase.SciMLLogging.None()) + sol = solve(oprob; maxiters = 100000, verbose = SciMLLogging.None()) SciMLBase.successful_retcode(sol) || return Inf return [maximum(sol[:E]), maximum(sol[:I])] end diff --git a/docs/src/inverse_problems/optimization_ode_param_fitting.md b/docs/src/inverse_problems/optimization_ode_param_fitting.md index 9521a1f8bb..2d99a2a060 100644 --- a/docs/src/inverse_problems/optimization_ode_param_fitting.md +++ b/docs/src/inverse_problems/optimization_ode_param_fitting.md @@ -13,7 +13,6 @@ Pkg.add("OptimizationEvolutionary") Pkg.add("OptimizationOptimJL") Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") -Pkg.add("DiffEqBase") ``` ```@raw html @@ -23,7 +22,7 @@ Pkg.add("DiffEqBase") ``` The following code provides a brief example of how to perform parameter fitting using the [Optimization.jl](https://github.com/SciML/Optimization.jl) package. ```julia -using Catalyst, OrdinaryDiffEq, OptimizationBase, OptimizationNLopt, DiffEqBase, SymbolicIndexingInterface +using Catalyst, OrdinaryDiffEq, OptimizationBase, OptimizationNLopt, SymbolicIndexingInterface # What we know: A model, an initial condition, and sampled datapoints. rn = @reaction_network begin @@ -44,7 +43,7 @@ function loss(p, (oprob_base, p_setter, t_samples, X_samples)) oprob = remake(oprob_base; p) # Simulate the model. If sucesfull, return sum-of-squares distance as loss. - sol = solve(oprob; saveat = t_samples, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob; saveat = t_samples, verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum(abs2, sol[:X] .- X_samples) end @@ -91,7 +90,7 @@ u0 = [:S => 1.0, :E => 1.0, :SE => 0.0, :P => 0.0] ps_true = [:kB => 1.0, :kD => 0.1, :kP => 0.5] # Generate synthetic data. -using OrdinaryDiffEq, DiffEqBase +using OrdinaryDiffEq oprob_true = ODEProblem(rn, u0, (0.0, 10.0), ps_true) true_sol = solve(oprob_true) data_sol = solve(oprob_true; saveat = 1.0) @@ -114,14 +113,14 @@ oprob_base = ODEProblem(rn, u0, (0.0, 10.0), ps_init) function objective_function(p, _) p = Pair.([:kB, :kD, :kP], p) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol .- data_vals) .^2) end ``` When our optimisation algorithm searches parameter space it will likely consider many highly non-plausible parameter sets. To better handle this we: 1. Add `maxiters = 10000` to our `solve` command. As most well-behaved ODEs can be solved in relatively few timesteps, this speeds up the optimisation procedure by preventing us from spending too much time trying to simulate (for the model) unsuitable parameter sets. -2. Add `verbose = DiffEqBase.SciMLLogging.None()` to our `solve` command. This prevents (potentially a very large number of) warnings from being printed to our output as unsuitable parameter sets are simulated. +2. Add `verbose = SciMLLogging.None()` to our `solve` command. This prevents (potentially a very large number of) warnings from being printed to our output as unsuitable parameter sets are simulated. 3. Add the line `SciMLBase.successful_retcode(sol) || return Inf`, which returns an infinite value for parameter sets which does not lead to successful simulations. To improve optimisation performance, rather than creating a new `ODEProblem` in each iteration, we pre-declare one which we [apply `remake` to](@ref simulation_structure_interfacing_problems_remake). We also use the `saveat = data_ts, save_idxs = :P` arguments to only save the values of the measured species at the measured time points. @@ -195,7 +194,7 @@ In this case we simply modify our objective function to take this into account: function objective_function_S_P(p, _) p = Pair.([:kB, :kD, :kP], p) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = [:S, :P], verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = [:S, :P], verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol[:S] .- data_vals_S) .^2 + (sol[:P] .- data_vals_P) .^2) end @@ -229,7 +228,7 @@ If we from previous knowledge know that $kD = 0.1$, and only want to fit the val function objective_function_known_kD(p, _) p = Pair.([:kB, :kD, :kP], [p[1], 0.1, p[2]]) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol .- data_vals) .^2) end @@ -269,7 +268,7 @@ corresponds to the same true parameter values as used previously (`[:kB => 1.0, function objective_function_logtransformed(p, _) p = Pair.([:kB, :kD, :kP], 10.0 .^ p) oprob = remake(oprob_base; p) - sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob; saveat = data_ts, save_idxs = :P, verbose = SciMLLogging.None(), maxiters = 10000) SciMLBase.successful_retcode(sol) || return Inf return sum((sol .- data_vals) .^2) end diff --git a/docs/src/inverse_problems/turing_ode_param_fitting.md b/docs/src/inverse_problems/turing_ode_param_fitting.md index f7173c668a..1badb253eb 100644 --- a/docs/src/inverse_problems/turing_ode_param_fitting.md +++ b/docs/src/inverse_problems/turing_ode_param_fitting.md @@ -10,7 +10,6 @@ Pkg.add("Catalyst") Pkg.add("Distributions") Pkg.add("OrdinaryDiffEq") Pkg.add("Plots") -Pkg.add("DiffEqBase") Pkg.add("StatsPlots") Pkg.add("SymbolicIndexingInterface") Pkg.add("Turing") @@ -31,7 +30,7 @@ sir = @reaction_network begin end # Generate some (synthetic) data for the fitting procedure. -using Distributions, OrdinaryDiffEq, Plots, DiffEqBase +using Distributions, OrdinaryDiffEq, Plots t_measurement = 1.0:100.0 u0 = [:S => 999.0, :I => 1.0, :R => 0.0] p_true = [:γ => 0.0003, :ν => 0.1] @@ -57,7 +56,7 @@ using Turing, MCMCChains # Simulate the model for parameter values γ, ν. Saves the solution at the measurement times. p = setp_oop(oprob_base, [γ, ν]) oprob_base = remake(oprob_base; p) - sol = solve(oprob_base; saveat, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob_base; saveat, verbose = SciMLLogging.None(), maxiters = 10000) # If simulation was unsuccessful, the likelihood is -Inf. if !SciMLBase.successful_retcode(sol) @@ -105,7 +104,7 @@ end ``` From an initial condition where only a small fraction of the population is in the infected state, the model exhibits a peak of infections, after which the epidemic subsides. ```@example turing_paramfit -using OrdinaryDiffEq, Plots, DiffEqBase +using OrdinaryDiffEq, Plots u0 = [:S => 999.0, :I => 1.0, :R => 0.0] p_true = [:γ => 0.0005, :ν => 0.1] oprob_true = ODEProblem(sir, u0, 100.0, p_true) @@ -143,7 +142,7 @@ setp_oop = SymbolicIndexingInterface.setp_oop(oprob_true, [:γ, :ν]) # Simulate the model for parameter values γ, ν. Saves the solution at the measurement times. p = setp_oop(oprob_base, [γ, ν]) oprob_base = remake(oprob_base; p) - sol = solve(oprob_base; saveat, verbose = DiffEqBase.SciMLLogging.None(), maxiters = 10000) + sol = solve(oprob_base; saveat, verbose = SciMLLogging.None(), maxiters = 10000) # If simulation was unsuccessful, the likelihood is -Inf. if !SciMLBase.successful_retcode(sol) @@ -160,7 +159,7 @@ nothing # hide ``` Some specific comments regarding how we have declared the model above: -- Like for [normal parameter fitting](@ref optimization_parameter_fitting_basics), we use the `maxiters = 10000` (to prevent spending a long time simulating unfeasible parameter sets) and `verbose = DiffEqBase.SciMLLogging.None()` (to prevent unnecessary printing of warning messages) arguments to `solve`. +- Like for [normal parameter fitting](@ref optimization_parameter_fitting_basics), we use the `maxiters = 10000` (to prevent spending a long time simulating unfeasible parameter sets) and `verbose = SciMLLogging.None()` (to prevent unnecessary printing of warning messages) arguments to `solve`. - Again, we need to handle parameter sets where the model cannot be successfully simulated. Here, we use `Turing.@addlogprob! -Inf` to set a non-existent likelihood, and `return nothing` to stop further evaluation of the specific parameter set. - Just like for normal parameter fitting, we wish to [fit parameters on a log scale](@ref optimization_parameter_fitting_log_scale). Here we do so by declaring log-scaled prior distributions. - Here we assume that we (correctly) know that the noise is normally distributed. However, we assume that we *do not know the standard deviation*. Instead, we make the standard deviation a third parameter whose value we infer as part of the inference process. More complicated noise formulas can be used (and are sometimes even advisable[^2]). From 8b753b796c5e9ffa9ae5a56b4f430a503480f498 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 22 Jun 2026 14:57:30 +0100 Subject: [PATCH 6/8] bound dynamical systems --- docs/Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 4b801d6be1..00f1353df9 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -63,7 +63,7 @@ DiffEqNoiseProcess = "5.31.1" Distributions = "0.25" Documenter = "1.11.1" DynamicPolynomials = "0.6" -DynamicalSystems = "3.6.7" +DynamicalSystems = "3.6.8" GlobalSensitivity = "2.6" GraphMakie = "0.6" Graphs = "1.11.1" @@ -85,7 +85,7 @@ OptimizationLBFGSB = "1" OptimizationNLopt = "0.3" OptimizationOptimJL = "0.4" OptimizationOptimisers = "0.3" -OrdinaryDiffEq = "7.0.1" +OrdinaryDiffEq = "7.1.0" PEtab = "5" Plots = "1.40" QuasiMonteCarlo = "0.3" From 13647660c2b92873cf940eddc36f05cd7fff6d8a Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 22 Jun 2026 16:10:23 +0100 Subject: [PATCH 7/8] QNDF to FBDF. Re-add previousy removed optim example bit. --- docs/Project.toml | 2 +- .../optimization_ode_param_fitting.md | 8 +++ .../dspace_reaction_systems_ODEs.jl | 64 +++++++++---------- .../dspace_reaction_systems_space_types.jl | 22 +++---- 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 00f1353df9..40d1f84062 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -79,7 +79,7 @@ NetworkLayout = "0.4" NonlinearSolve = "4" NonlinearSolveFirstOrder = "1, 2.1" Optim = "2" -OptimizationBBO = "0.4" +OptimizationBBO = "0.4.7" OptimizationBase = "4, 5.0" OptimizationLBFGSB = "1" OptimizationNLopt = "0.3" diff --git a/docs/src/inverse_problems/optimization_ode_param_fitting.md b/docs/src/inverse_problems/optimization_ode_param_fitting.md index 2d99a2a060..6390a9b084 100644 --- a/docs/src/inverse_problems/optimization_ode_param_fitting.md +++ b/docs/src/inverse_problems/optimization_ode_param_fitting.md @@ -159,6 +159,14 @@ Catalyst.PNG(plot(plt; fmt = :png, dpi = 200)) # hide !!! 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 is a concept that studies how to deal with this problem. +Say that we instead would like to use a [differential evolution](https://en.wikipedia.org/wiki/Differential_evolution) approach, as implemented by the [BlackBoxOptim.jl](https://github.com/SciML/BlackBoxOptim.jl) package. In this case we can run: +```@example optimization_paramfit_1 +using OptimizationBBO +sol = solve(optprob, BBO_adaptive_de_rand_1_bin_radiuslimited()) +nothing # hide +``` +to solve `optprob` for this combination of solve and implementation. + ## [Utilising automatic differentiation](@id optimization_parameter_fitting_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). diff --git a/test/spatial_modelling/dspace_reaction_systems_ODEs.jl b/test/spatial_modelling/dspace_reaction_systems_ODEs.jl index 1b3ddf2c1e..5337ad4264 100644 --- a/test/spatial_modelling/dspace_reaction_systems_ODEs.jl +++ b/test/spatial_modelling/dspace_reaction_systems_ODEs.jl @@ -80,8 +80,8 @@ let dsrs = DiscreteSpaceReactionSystem(rs, [tr], space); 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; + 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], :d => 0.1, :pY => 0.5, :D => D_vals] oprob = ODEProblem(dsrs, u0, (0.0, 0.0), ps; jac=true, sparse=true) @@ -94,12 +94,12 @@ let pY, = pY d, = d D1 = D_vals[1,2]; D2 = D_vals[2,1]; - D3 = D_vals[2,3]; D4 = D_vals[3,2]; + 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 + du[3] = pX2 - d*X2 + D1*X1 - (D2+D3)*X2 + D4*X3 du[4] = pY*X2 - d*Y2 - du[5] = pX3 - d*X3 + D3*X2 - D4*X3 + du[5] = pX3 - d*X3 + D3*X2 - D4*X3 du[6] = pY*X3 - d*Y3 end function jac_manual!(J, u, p, t) @@ -109,7 +109,7 @@ let pY, = pY d, = d D1 = D_vals[1,2]; D2 = D_vals[2,1]; - D3 = D_vals[2,3]; D4 = D_vals[3,2]; + D3 = D_vals[2,3]; D4 = D_vals[3,2]; J .= 0.0 @@ -307,7 +307,7 @@ end ### Test Grid Types ### # Tests that identical spaces (using different types of spaces) give identical results. -let +let # Declares the diffusion parameters. sigmaB_p_spat = [:DσB => 0.05, :Dw => 0.04, :Dv => 0.03] @@ -319,7 +319,7 @@ let oprob1_cartesian = ODEProblem(dsrs1_cartesian, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) oprob1_masked = ODEProblem(dsrs1_masked, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) oprob1_graph = ODEProblem(dsrs1_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()) + @test solve(oprob1_cartesian, FBDF()) ≈ solve(oprob1_masked, FBDF()) ≈ solve(oprob1_graph, FBDF()) # 2d spaces. dsrs2_cartesian = DiscreteSpaceReactionSystem(sigmaB_system, sigmaB_srs_2, very_small_2d_cartesian_grid) @@ -329,7 +329,7 @@ let oprob2_cartesian = ODEProblem(dsrs2_cartesian, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) oprob2_masked = ODEProblem(dsrs2_masked, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) oprob2_graph = ODEProblem(dsrs2_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()) + @test solve(oprob2_cartesian, FBDF()) ≈ solve(oprob2_masked, FBDF()) ≈ solve(oprob2_graph, FBDF()) # 3d spaces. dsrs3_cartesian = DiscreteSpaceReactionSystem(sigmaB_system, sigmaB_srs_2, very_small_3d_cartesian_grid) @@ -339,11 +339,11 @@ let oprob3_cartesian = ODEProblem(dsrs3_cartesian, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) oprob3_masked = ODEProblem(dsrs3_masked, sigmaB_u0, (0.0,1.0), [sigmaB_p; sigmaB_p_spat]) oprob3_graph = ODEProblem(dsrs3_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()) + @test solve(oprob3_cartesian, FBDF()) ≈ solve(oprob3_masked, FBDF()) ≈ solve(oprob3_graph, FBDF()) end # Tests that input parameter and u0 values can be given using different types of input for 2d spaces. -# Tries both for cartesian and masked (where all vertices are `true`). +# Tries both for cartesian and masked (where all vertices are `true`). # Tries for Vector, Tuple, and Dictionary inputs. let for space in [CartesianGrid((4,3)), fill(true, 4, 3)] @@ -354,13 +354,13 @@ let 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.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(dsrs, 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] @@ -377,7 +377,7 @@ end let space = [true true false; true false false; true true true; false true true] dsrs = DiscreteSpaceReactionSystem(SIR_system, SIR_srs_1, space) - + # Initial condition values. 999 is used for empty points. 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] @@ -385,15 +385,15 @@ let 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. + + # Parameter values. 9.99 is used for empty points. β_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 .* space) 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(dsrs, 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] @@ -425,19 +425,19 @@ let end # Tries non-trivial diffusion rates. -let +let SIR_tr_S_alt = @transport_reaction dS1+dS2 S 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] dsrs_1 = DiscreteSpaceReactionSystem(SIR_system, SIR_srs_2, small_2d_graph_grid) dsrs_2 = DiscreteSpaceReactionSystem(SIR_system, SIR_srs_2_alt, small_2d_graph_grid) - + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(dsrs_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] - + ss_1 = solve(ODEProblem(dsrs_1, u0, (0.0, 500.0), [pV; pE_1]), Tsit5()).u[end] ss_2 = solve(ODEProblem(dsrs_2, u0, (0.0, 500.0), [pV; pE_2]), Tsit5()).u[end] @test ss_1 == ss_2 @@ -460,7 +460,7 @@ let end @unpack dLigand, dSilane, Silane = CuH_Amination_system_alt_1 @parameters dAmine_E dNewspecies1 - @species Ligand(t) Amine_E(t) Newspecies1(t) + @species Ligand(t) Amine_E(t) Newspecies1(t) tr_alt_1_1 = TransportReaction(dLigand, Ligand) tr_alt_1_2 = TransportReaction(dSilane, Silane) tr_alt_1_3 = TransportReaction(dAmine_E, Amine_E) @@ -486,7 +486,7 @@ let end @unpack Decomposition, dCu_ELigand, Cu_ELigand = CuH_Amination_system_alt_2 @parameters dNewspecies2 dDecomposition - @species Newspecies2(t) + @species Newspecies2(t) tr_alt_2_1 = @transport_reaction dLigand Ligand tr_alt_2_2 = @transport_reaction dSilane Silane tr_alt_2_3 = @transport_reaction dAmine_E Amine_E @@ -496,7 +496,7 @@ let 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] dsrs_2 = DiscreteSpaceReactionSystem(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] @@ -556,17 +556,17 @@ let ] oprob_alt = ODEProblem(dsrs_alt, u0_alt, (0.0, 10.0), p_alt) ss_alt = solve(oprob_alt, Tsit5(); abstol=1e-9, reltol=1e-9).u[end] - + binding_srs_main = [TransportReaction(dX, X), TransportReaction(dXY, XY)] dsrs = DiscreteSpaceReactionSystem(binding_system, binding_srs_main, small_2d_graph_grid) u0 = u0_alt[1:3] p = p_alt[1:4] oprob = ODEProblem(dsrs, u0, (0.0, 10.0), p) 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 ss_alt[((i - 1) * 6 + 1):((i - 1) * 6 + 3)] ≈ ss[((i - 1) * 3 + 1):((i - 1) * 3 + 3)] end @@ -601,10 +601,10 @@ let # 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; + 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)) @@ -614,14 +614,14 @@ let # Creates a base solution to compare all solution to. dsrs_base = DiscreteSpaceReactionSystem(brusselator_system, brusselator_srs_2, very_small_2d_graph_grid) oprob_base = ODEProblem(dsrs_base, u0s[1], (0.0, 1.0), ps[1]) - sol_base = solve(oprob_base, QNDF(); saveat = 0.01) + sol_base = solve(oprob_base, FBDF(); saveat = 0.01) # Checks all combinations of input types. dsrs = DiscreteSpaceReactionSystem(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(dsrs, u0, (0.0, 1.0), p; sparse = true, jac = true) - sol = solve(oprob, QNDF(); saveat = 0.01) + sol = solve(oprob, FBDF(); saveat = 0.01) @test sol.u ≈ sol_base.u atol = 1e-6 rtol = 1e-6 end end diff --git a/test/spatial_modelling/dspace_reaction_systems_space_types.jl b/test/spatial_modelling/dspace_reaction_systems_space_types.jl index 6d2f39ae7e..6266708648 100644 --- a/test/spatial_modelling/dspace_reaction_systems_space_types.jl +++ b/test/spatial_modelling/dspace_reaction_systems_space_types.jl @@ -10,13 +10,13 @@ include("../spatial_test_networks.jl") ### Run Tests ### # Test errors when attempting to create networks with dimensions > 3. -let +let @test_throws Exception DiscreteSpaceReactionSystem(brusselator_system, brusselator_srs_1, CartesianGrid((5, 5, 5, 5))) @test_throws Exception DiscreteSpaceReactionSystem(brusselator_system, brusselator_srs_1, fill(true, 5, 5, 5, 5)) end # Checks that getter functions give the correct output. -let +let # Create DiscreteSpaceReactionSystems. cartesian_1d_dsrs = DiscreteSpaceReactionSystem(brusselator_system, brusselator_srs_1, small_1d_cartesian_grid) cartesian_2d_dsrs = DiscreteSpaceReactionSystem(brusselator_system, brusselator_srs_1, small_2d_cartesian_grid) @@ -83,7 +83,7 @@ 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 +let # Create DiscreteSpaceReactionSystems. cartesian_grid = CartesianGrid((5, 5)) masked_grid = fill(true, 5, 5) @@ -100,8 +100,8 @@ let @test num_verts(cartesian_dsrs) == num_verts(masked_dsrs) == num_verts(graph_dsrs) @test num_edges(cartesian_dsrs) == num_edges(masked_dsrs) == num_edges(graph_dsrs) @test num_species(cartesian_dsrs) == num_species(masked_dsrs) == num_species(graph_dsrs) - @test isequal(spatial_species(cartesian_dsrs), spatial_species(masked_dsrs)) - @test isequal(spatial_species(masked_dsrs), spatial_species(graph_dsrs)) + @test isequal(spatial_species(cartesian_dsrs), spatial_species(masked_dsrs)) + @test isequal(spatial_species(masked_dsrs), spatial_species(graph_dsrs)) @test isequal(parameters(cartesian_dsrs), parameters(masked_dsrs)) @test isequal(parameters(masked_dsrs), parameters(graph_dsrs)) @test isequal(vertex_parameters(cartesian_dsrs), vertex_parameters(masked_dsrs)) @@ -147,27 +147,27 @@ let masked_dsrs = DiscreteSpaceReactionSystem(brusselator_system, brusselator_srs_1, masked_grid) graph_dsrs = DiscreteSpaceReactionSystem(brusselator_system, brusselator_srs_1, graph_grid) - + # Check internal structures. @test num_verts(masked_dsrs) == num_verts(graph_dsrs) @test num_edges(masked_dsrs) == num_edges(graph_dsrs) @test issetequal(edge_iterator(masked_dsrs), edge_iterator(graph_dsrs)) - + # Checks that simulations yields the same output. 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_dsrs, 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) + base_osol = solve(base_oprob, FBDF(); saveat=0.1, abstol=1e-9, reltol=1e-9) for jac in [false, true], sparse in [false, true] masked_oprob = ODEProblem(masked_dsrs, u0_masked_grid, (0.0, 100.0), [pV_masked_grid; pE]; jac, sparse) graph_oprob = ODEProblem(graph_dsrs, 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) + masked_sol = solve(masked_oprob, FBDF(); saveat=0.1, abstol=1e-9, reltol=1e-9) + graph_sol = solve(graph_oprob, FBDF(); saveat=0.1, abstol=1e-9, reltol=1e-9) @test base_osol ≈ masked_sol atol = 1e-6 rtol = 1e-6 @test base_osol ≈ graph_sol atol = 1e-6 rtol = 1e-6 @test masked_sol ≈ graph_sol atol = 1e-6 rtol = 1e-6 From 67c3b5358d8b16e8831df8a6df1a9355479ca5b6 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 22 Jun 2026 16:18:31 +0100 Subject: [PATCH 8/8] optim doc fix --- docs/src/inverse_problems/optimization_ode_param_fitting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/inverse_problems/optimization_ode_param_fitting.md b/docs/src/inverse_problems/optimization_ode_param_fitting.md index 6390a9b084..bc31075fe8 100644 --- a/docs/src/inverse_problems/optimization_ode_param_fitting.md +++ b/docs/src/inverse_problems/optimization_ode_param_fitting.md @@ -8,8 +8,8 @@ using Pkg Pkg.activate(; temp = true) # Creates a temporary environment, which is deleted when the Julia session ends. Pkg.add("Catalyst") Pkg.add("OptimizationBase") +Pkg.add("OptimizationBBO") Pkg.add("OptimizationNLopt") -Pkg.add("OptimizationEvolutionary") Pkg.add("OptimizationOptimJL") Pkg.add("OrdinaryDiffEq") Pkg.add("Plots")