├── .cargo └── config.toml ├── .gitconfig ├── .github ├── FUNDING.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── book ├── .gitignore ├── book.toml ├── src │ ├── SUMMARY.md │ ├── benchmarks │ │ ├── benchmarks.md │ │ ├── images │ │ │ ├── bench_bdf.svg │ │ │ ├── bench_bdf_diffsl.svg │ │ │ ├── bench_tr_bdf2_esdirk.svg │ │ │ └── python_plot.svg │ │ ├── python.md │ │ ├── python_plot.py │ │ ├── python_results.csv │ │ └── sundials.md │ ├── primer │ │ ├── backwards_sensitivity_analysis.md │ │ ├── bouncing_ball.md │ │ ├── compartmental_models_of_drug_delivery.md │ │ ├── discrete_events.md │ │ ├── electrical_circuits.md │ │ ├── first_order_odes.md │ │ ├── forward_sensitivity_analysis.md │ │ ├── heat_equation.md │ │ ├── higher_order_odes.md │ │ ├── images │ │ │ ├── Mass_spring_damper.svg │ │ │ ├── battery-simulation.html │ │ │ ├── bouncing-ball.html │ │ │ ├── drug-delivery.html │ │ │ ├── electrical-circuit.html │ │ │ ├── heat-equation.html │ │ │ ├── pk2.svg │ │ │ ├── prey-predator.html │ │ │ ├── prey-predator2.html │ │ │ └── spring-mass-system.html │ │ ├── modelling_with_diffsol.md │ │ ├── pdes.md │ │ ├── physics_based_battery_simulation.md │ │ ├── population_dynamics.md │ │ ├── population_dynamics_fitting.md │ │ ├── spatial_population_dynamics.md │ │ ├── spring_mass_fitting.md │ │ ├── spring_mass_systems.md │ │ ├── src │ │ │ └── spm.ds │ │ ├── the_mass_matrix.md │ │ └── weather_neural_ode.md │ ├── solve │ │ ├── adjoint_sens.md │ │ ├── forward_sens.md │ │ ├── interpolation.md │ │ ├── manual-time-stepping.md │ │ ├── solving_the_problem.md │ │ ├── stopping.md │ │ ├── stopping_event.md │ │ └── stopping_time.md │ ├── solver │ │ ├── creating_a_solver.md │ │ ├── initialisation.md │ │ └── tableau.md │ └── specify │ │ ├── closure │ │ ├── explicit.md │ │ ├── forward_sensitivity.md │ │ ├── implicit.md │ │ ├── mass_matrix.md │ │ ├── root_finding.md │ │ ├── rust_closures.md │ │ └── sparse_problems.md │ │ ├── diffsl │ │ └── diffsl.md │ │ ├── specifying_the_problem.md │ │ └── trait │ │ ├── constant_functions.md │ │ ├── linear_functions.md │ │ ├── non_linear_functions.md │ │ ├── ode_equations_trait.md │ │ └── ode_systems.md └── theme │ └── head.hbs ├── diffsol ├── Cargo.toml ├── benches │ ├── ode_solvers.rs │ ├── plot.py │ ├── sundials │ │ ├── cvRoberts_block_klu.c │ │ ├── cvRoberts_block_klu_v5.c │ │ ├── cvRoberts_block_klu_v6.c │ │ ├── idaFoodWeb_bnd_10.c │ │ ├── idaFoodWeb_bnd_20.c │ │ ├── idaFoodWeb_bnd_30.c │ │ ├── idaFoodWeb_bnd_5.c │ │ ├── idaFoodWeb_bnd_v5.c │ │ ├── idaFoodWeb_bnd_v6.c │ │ ├── idaHeat2d_bnd_10.c │ │ ├── idaHeat2d_bnd_20.c │ │ ├── idaHeat2d_bnd_30.c │ │ ├── idaHeat2d_bnd_5.c │ │ ├── idaHeat2d_klu_v5.c │ │ ├── idaHeat2d_klu_v6.c │ │ ├── idaRoberts_dns.c │ │ ├── idaRoberts_dns_v5.c │ │ ├── idaRoberts_dns_v6.c │ │ └── mangle.h │ └── sundials_benches.rs ├── build.rs ├── src │ ├── context │ │ ├── cuda.rs │ │ ├── faer.rs │ │ ├── mod.rs │ │ └── nalgebra.rs │ ├── cuda_kernels │ │ ├── all.cu │ │ ├── mat_from_diagonal.cu │ │ ├── mat_get_diagonal.cu │ │ ├── mat_scale_add_assign.cu │ │ ├── mat_set_column.cu │ │ ├── mat_set_data_with_indices.cu │ │ ├── vec_add.cu │ │ ├── vec_add_assign.cu │ │ ├── vec_add_assign_rhs.cu │ │ ├── vec_assign_at_indices.cu │ │ ├── vec_copy.cu │ │ ├── vec_copy_from_indices.cu │ │ ├── vec_div_assign.cu │ │ ├── vec_fill.cu │ │ ├── vec_gather.cu │ │ ├── vec_mul_assign.cu │ │ ├── vec_mul_assign_scalar.cu │ │ ├── vec_mul_scalar.cu │ │ ├── vec_root_finding.cu │ │ ├── vec_scatter.cu │ │ ├── vec_squared_norm.cu │ │ ├── vec_sub.cu │ │ ├── vec_sub_assign.cu │ │ └── vec_sub_assign_rhs.cu │ ├── error.rs │ ├── jacobian │ │ ├── coloring.rs │ │ ├── graph.rs │ │ ├── greedy_coloring.rs │ │ └── mod.rs │ ├── lib.rs │ ├── linear_solver │ │ ├── cuda │ │ │ ├── lu.rs │ │ │ └── mod.rs │ │ ├── faer │ │ │ ├── lu.rs │ │ │ ├── mod.rs │ │ │ └── sparse_lu.rs │ │ ├── mod.rs │ │ ├── nalgebra │ │ │ ├── lu.rs │ │ │ └── mod.rs │ │ └── suitesparse │ │ │ ├── klu.rs │ │ │ └── mod.rs │ ├── matrix │ │ ├── cuda.rs │ │ ├── default_solver.rs │ │ ├── dense_faer_serial.rs │ │ ├── dense_nalgebra_serial.rs │ │ ├── extract_block.rs │ │ ├── mod.rs │ │ ├── sparse_faer.rs │ │ ├── sparsity.rs │ │ └── utils.rs │ ├── nonlinear_solver │ │ ├── convergence.rs │ │ ├── mod.rs │ │ ├── newton.rs │ │ └── root.rs │ ├── ode_solver │ │ ├── adjoint.rs │ │ ├── adjoint_equations.rs │ │ ├── bdf.rs │ │ ├── bdf_state.rs │ │ ├── builder.rs │ │ ├── checkpointing.rs │ │ ├── diffsl.rs │ │ ├── equations.rs │ │ ├── explicit_rk.rs │ │ ├── jacobian_update.rs │ │ ├── method.rs │ │ ├── mod.rs │ │ ├── problem.rs │ │ ├── sdirk.rs │ │ ├── sdirk_state.rs │ │ ├── sens_equations.rs │ │ ├── sensitivities.rs │ │ ├── state.rs │ │ ├── tableau.rs │ │ ├── test.rs │ │ └── test_models │ │ │ ├── dydt_y2.rs │ │ │ ├── exponential_decay.rs │ │ │ ├── exponential_decay_with_algebraic.rs │ │ │ ├── foodweb.rs │ │ │ ├── gaussian_decay.rs │ │ │ ├── heat2d.rs │ │ │ ├── mod.rs │ │ │ ├── robertson.rs │ │ │ ├── robertson_ode.rs │ │ │ ├── robertson_ode_with_sens.rs │ │ │ └── snapshots │ │ │ ├── diffsol__ode_solver__test_models__heat2d__tests__jacobian.snap │ │ │ └── diffsol__ode_solver__test_models__heat2d__tests__mass.snap │ ├── op │ │ ├── bdf.rs │ │ ├── closure.rs │ │ ├── closure_no_jac.rs │ │ ├── closure_with_adjoint.rs │ │ ├── closure_with_sens.rs │ │ ├── constant_closure.rs │ │ ├── constant_closure_with_adjoint.rs │ │ ├── constant_closure_with_sens.rs │ │ ├── constant_op.rs │ │ ├── init.rs │ │ ├── linear_closure.rs │ │ ├── linear_closure_with_adjoint.rs │ │ ├── linear_op.rs │ │ ├── linearise.rs │ │ ├── matrix.rs │ │ ├── mod.rs │ │ ├── nonlinear_op.rs │ │ ├── sdirk.rs │ │ └── unit.rs │ ├── scalar │ │ ├── cuda.rs │ │ └── mod.rs │ ├── solver │ │ └── mod.rs │ ├── sundials_sys.rs │ └── vector │ │ ├── cuda.rs │ │ ├── faer_serial.rs │ │ ├── mod.rs │ │ ├── nalgebra_serial.rs │ │ └── utils.rs └── wrapper.h ├── examples ├── bouncing-ball │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── compartmental-models-drug-delivery │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── custom-ode-equations │ ├── Cargo.toml │ └── src │ │ ├── build.rs │ │ ├── common.rs │ │ ├── main.rs │ │ ├── my_equations.rs │ │ ├── my_equations_impl_ode_equations.rs │ │ ├── my_equations_impl_op.rs │ │ ├── my_init.rs │ │ ├── my_mass.rs │ │ ├── my_out.rs │ │ ├── my_rhs.rs │ │ ├── my_rhs_impl_nonlinear.rs │ │ ├── my_rhs_impl_op.rs │ │ └── my_root.rs ├── electrical-circuits │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── intro-logistic-closures │ ├── Cargo.toml │ └── src │ │ ├── create_solvers.rs │ │ ├── create_solvers_tableau.rs │ │ ├── create_solvers_uninit.rs │ │ ├── main.rs │ │ ├── print_jacobian.rs │ │ ├── problem_explicit.rs │ │ ├── problem_fwd_sens.rs │ │ ├── problem_implicit.rs │ │ ├── problem_mass.rs │ │ ├── problem_root.rs │ │ ├── problem_sparse.rs │ │ ├── solve.rs │ │ ├── solve_adjoint_sens.rs │ │ ├── solve_adjoint_sens_sum_squares.rs │ │ ├── solve_dense.rs │ │ ├── solve_fwd_sens.rs │ │ ├── solve_fwd_sens_step.rs │ │ ├── solve_interpolate.rs │ │ ├── solve_match_step.rs │ │ └── solve_step.rs ├── intro-logistic-diffsl │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── mass-spring-fitting-adjoint │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ └── main_llvm.rs ├── neural-ode-weather-prediction │ ├── Cargo.toml │ ├── neural-ode-weather_5 │ ├── requirements.txt │ └── src │ │ ├── data │ │ ├── .gitignore │ │ ├── MonthlyDelhiClimate.csv │ │ └── pre-process.py │ │ ├── main.rs │ │ └── model │ │ ├── .gitignore │ │ └── model.py ├── pde-heat │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── physics-based-battery-simulation │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── population-dynamics │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── predator-prey-fitting-forward │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ └── main_llvm.rs └── spring-mass-system │ ├── Cargo.toml │ └── src │ └── main.rs └── llvm.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [net] 2 | git-fetch-with-cli = true -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [credential] 2 | helper = store -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: martinjrobins 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | 17 | # Added by cargo 18 | 19 | /target 20 | /Cargo.lock 21 | 22 | .vscode 23 | *pending-snap 24 | perf.data* 25 | flamegraph.svg 26 | *.png 27 | env/ 28 | *.mtx 29 | *notes.txt -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "diffsol", 4 | "examples/*", 5 | ] 6 | exclude = [ 7 | "book", 8 | ] 9 | resolver = "2" 10 | 11 | [workspace.package] 12 | edition = "2021" 13 | license = "MIT" 14 | readme = "diffsol/README.md" 15 | authors = ["Martin Robinson "] 16 | repository = "https://github.com/martinjrobins/diffsol" 17 | 18 | [workspace.dependencies] 19 | nalgebra = "0.33.2" 20 | faer = "0.22.6" 21 | cudarc = { version = "0.16.4", default-features = false } 22 | 23 | plotly = { version = "0.12.1" } 24 | argmin = { version = "0.10.0" } 25 | argmin-math = { version = "0.4" } 26 | argmin-observer-slog = { version = "0.1.0" } 27 | ort = "=2.0.0-rc.9" 28 | ort-sys = { version = "=2.0.0-rc.9", default-features = false } 29 | 30 | [profile.profiling] 31 | inherits = "release" 32 | debug = true -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Martin Robinson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | keeper_* 3 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["martinjrobins"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Diffsol" 7 | 8 | [output.html] 9 | mathjax-support = true -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Modelling with Diffsol](./primer/modelling_with_diffsol.md) 4 | - [Explicit First Order ODEs](./primer/first_order_odes.md) 5 | - [Example: Population Dynamics](./primer/population_dynamics.md) 6 | - [Higher Order ODEs](./primer/higher_order_odes.md) 7 | - [Example: Spring-mass systems](./primer/spring_mass_systems.md) 8 | - [Discrete Events](./primer/discrete_events.md) 9 | - [Example: Compartmental models of Drug Delivery](./primer/compartmental_models_of_drug_delivery.md) 10 | - [Example: Bouncing Ball](./primer/bouncing_ball.md) 11 | - [DAEs via the Mass Matrix](./primer/the_mass_matrix.md) 12 | - [Example: Electrical Circuits](./primer/electrical_circuits.md) 13 | - [PDEs](./primer/pdes.md) 14 | - [Example: Heat Equation](./primer/heat_equation.md) 15 | - [Example: Physics-based Battery Simulation](./primer/physics_based_battery_simulation.md) 16 | - [Forward Sensitivity Analysis](./primer/forward_sensitivity_analysis.md) 17 | - [Example: Fitting a predator-prey model to data](./primer/population_dynamics_fitting.md) 18 | - [Backwards Sensitivity Analysis](./primer/backwards_sensitivity_analysis.md) 19 | - [Example: Fitting a spring-mass model to data](./primer/spring_mass_fitting.md) 20 | - [Example: Weather prediction using neural ODEs](./primer/weather_neural_ode.md) 21 | - [Diffsol APIs for specifying problems](./specify/specifying_the_problem.md) 22 | - [DiffSL](./specify/diffsl/diffsl.md) 23 | - [Rust closures](./specify/closure/rust_closures.md) 24 | - [Explicit](./specify/closure/explicit.md) 25 | - [Implicit](./specify/closure/implicit.md) 26 | - [Mass matrix](./specify/closure/mass_matrix.md) 27 | - [Root finding](./specify/closure/root_finding.md) 28 | - [Forward Sensitivity](./specify/closure/forward_sensitivity.md) 29 | - [Sparse problems](./specify/closure/sparse_problems.md) 30 | - [OdeEquations trait](./specify/trait/ode_equations_trait.md) 31 | - [Non-linear functions](./specify/trait/non_linear_functions.md) 32 | - [Constant functions](./specify/trait/constant_functions.md) 33 | - [Linear functions](./specify/trait/linear_functions.md) 34 | - [ODE systems](./specify/trait/ode_systems.md) 35 | - [Creating a solver](./solver/creating_a_solver.md) 36 | - [Initialisation](./solver/initialisation.md) 37 | - [Tableau](./solver/tableau.md) 38 | - [Solving the problem](./solve/solving_the_problem.md) 39 | - [Manual time-stepping](./solve/manual-time-stepping.md) 40 | - [Interpolation](./solve/interpolation.md) 41 | - [Stopping](./solve/stopping.md) 42 | - [Forward Sensitivities](./solve/forward_sens.md) 43 | - [Benchmarks](./benchmarks/benchmarks.md) 44 | - [Sundials](./benchmarks/sundials.md) 45 | - [Python (Diffrax & Casadi)](./benchmarks/python.md) 46 | -------------------------------------------------------------------------------- /book/src/benchmarks/benchmarks.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | The goal of this chapter is to compare the performance of the Diffsol implementation with other similar ode solver libraries. 4 | 5 | The libraries we will compare against are: 6 | - [Sundials](https://computing.llnl.gov/projects/sundials): A suite of advanced numerical solvers for ODEs and DAEs, implemented in C. 7 | - [Diffrax](https://docs.kidger.site/diffrax/): A Python library for solving ODEs and SDEs implemented using JAX. 8 | - [Casadi](https://web.casadi.org/): A C++ library with Python anbd MATLAB bindings, for solving ODEs and DAEs, nonlinear optimisation and algorithmic differentiation. 9 | 10 | The comparison with Sundials will be done in Rust by wrapping C functions and comparing them to Rust implementations. The comparsion with the Python libraries (Diffrax and Casadi) will be done by wrapping Diffsol in Python using the PyO3 library, and comparing the performance of the wrapped functions. As well as benchmarking the Diffsol solvers, this also serves [as an example](https://github.com/martinjrobins/diffsol_python_benchmark) of how to wrap and use Diffsol in other languages. 11 | -------------------------------------------------------------------------------- /book/src/benchmarks/python_plot.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import matplotlib.pyplot as plt 3 | 4 | # load python_results.csv file 5 | df = pd.read_csv('book/src/benchmarks/python_results.csv') 6 | diffrax_low_tol = df[df['tol'] == 1e-4]['diffrax_time'] 7 | casadi_low_tol = df[df['tol'] == 1e-4]['casadi_time'] 8 | diffsol_low_tol = df[df['tol'] == 1e-4]['diffsol_time'] 9 | ngroups_low_tol = df[df['tol'] == 1e-4]['ngroups'] 10 | diffrax_high_tol = df[df['tol'] == 1e-8]['diffrax_time'] 11 | casadi_high_tol = df[df['tol'] == 1e-8]['casadi_time'] 12 | diffsol_high_tol = df[df['tol'] == 1e-8]['diffsol_time'] 13 | ngroups_high_tol = df[df['tol'] == 1e-8]['ngroups'] 14 | 15 | # plot the results from diffrax and casadi scaled by the reference (diffsol) 16 | fig, ax = plt.subplots() 17 | ax.plot(ngroups_low_tol, diffrax_low_tol / diffsol_low_tol, label='diffrax (tol=1e-4)') 18 | ax.plot(ngroups_low_tol, casadi_low_tol / diffsol_low_tol, label='casadi (tol=1e-4)') 19 | ax.plot(ngroups_low_tol, diffrax_high_tol / diffsol_high_tol, label='diffrax (tol=1e-8)') 20 | ax.plot(ngroups_low_tol, casadi_high_tol / diffsol_high_tol, label='casadi (tol=1e-8)') 21 | ax.legend() 22 | ax.set_yscale('log') 23 | ax.set_xscale('log') 24 | ax.set_xlabel('ngroups') 25 | ax.set_ylabel('Time relative to diffsol') 26 | 27 | plt.savefig('python_plot.png') 28 | plt.savefig('book/src/benchmarks/images/python_plot.svg') 29 | -------------------------------------------------------------------------------- /book/src/benchmarks/python_results.csv: -------------------------------------------------------------------------------- 1 | ngroups,tol,casadi_time,diffsol_time,diffrax_time 2 | 1,0.0001,0.0011767226459924133,3.1152486801147464e-05,0.0003240704019553959 3 | 1,1e-08,0.0018366031849291176,8.334216196089983e-05,0.00031373293697834013 4 | 10,0.0001,0.001293827251996845,0.00019592561409808695,0.00045331977191381156 5 | 10,1e-08,0.002042409308953211,0.00042948652803897856,0.0004587201240938157 6 | 20,0.0001,0.0014396793539635836,0.0003247480639256537,0.0011893865989986807 7 | 20,1e-08,0.0022794388150796296,0.0006490132620092481,0.0009902620629873126 8 | 40,0.0001,0.0017169011461082846,0.0005423122260253876,0.003972686865832657 9 | 40,1e-08,0.002716647819848731,0.001075255556963384,0.0028768490890506656 10 | 70,0.0001,0.0020806838658172636,0.000872906104195863,0.016255469823023304 11 | 70,1e-08,0.0032871941849589347,0.001750042901840061,0.015982740553095936 12 | 100,0.0001,0.002430817337706685,0.0012063957899808883,0.032641630642116067 13 | 100,1e-08,0.0038433158202096818,0.0023834101222455504,0.024714520414359866 14 | 200,0.0001,0.0035816779760112455,0.0028888281384432638, 15 | 200,1e-08,0.006066468168139548,0.005082464570671812, 16 | 400,0.0001,0.006550437295809388,0.004779179625911638, 17 | 400,1e-08,0.010515550004784017,0.00890555749530904, 18 | 700,0.0001,0.009862706633284687,0.008093493856489659, 19 | 700,1e-08,0.020905138103291393,0.01849150845594704, 20 | 1000,0.0001,0.017060047758018806,0.012448280966944164, 21 | 1000,1e-08,0.024620620634717247,0.023281645935235754, 22 | 5000,0.0001,0.07811664411258933,0.07020582679021907, 23 | 5000,1e-08,0.12322642290229469,0.13951222289745746, 24 | 10000,0.0001,0.1340123851162692,0.13100541778840125, 25 | 10000,1e-08,0.238225372430558,0.2701200796549933, 26 | -------------------------------------------------------------------------------- /book/src/primer/bouncing_ball.md: -------------------------------------------------------------------------------- 1 | # Example: Bouncing Ball 2 | 3 | Modelling a bouncing ball is a simple and intuitive example of a system with discrete events. The ball is dropped from a height \\(h\\) and bounces off the ground with a coefficient of restitution \\(e\\). When the ball hits the ground, its velocity is reversed and scaled by the coefficient of restitution, and the ball rises and then continues to fall until it hits the ground again. This process repeats until halted. 4 | 5 | The second order ODE that describes the motion of the ball is given by: 6 | 7 | \\[ 8 | \frac{d^2x}{dt^2} = -g 9 | \\] 10 | 11 | where \\(x\\) is the position of the ball, \\(t\\) is time, and \\(g\\) is the acceleration due to gravity. We can rewrite this as a system of two first order ODEs by introducing a new variable for the velocity of the ball: 12 | 13 | \\[ 14 | \begin{align*} 15 | \frac{dx}{dt} &= v \\\\ 16 | \frac{dv}{dt} &= -g 17 | \end{align*} 18 | \\] 19 | 20 | where \\(v = \frac{dx}{dt}\\) is the velocity of the ball. This is a system of two first order ODEs, which can be written in vector form as: 21 | 22 | \\[ 23 | \frac{d\mathbf{y}}{dt} = \mathbf{f}(\mathbf{y}, t) 24 | \\] 25 | 26 | where 27 | 28 | \\[ 29 | \mathbf{y} = \begin{bmatrix} x \\\\ v \end{bmatrix} 30 | \\] 31 | 32 | and 33 | 34 | \\[ 35 | \mathbf{f}(\mathbf{y}, t) = \begin{bmatrix} v \\\\ -g \end{bmatrix} 36 | \\] 37 | 38 | The initial conditions for the ball, including the height from which it is dropped and its initial velocity, are given by: 39 | 40 | \\[ 41 | \mathbf{y}(0) = \begin{bmatrix} h \\\\ 0 \end{bmatrix} 42 | \\] 43 | 44 | When the ball hits the ground, we need to update the velocity of the ball according to the coefficient of restitution, which is the ratio of the velocity after the bounce to the velocity before the bounce. The velocity after the bounce \\(v'\\) is given by: 45 | 46 | \\[ 47 | v' = -e v 48 | \\] 49 | 50 | where \\(e\\) is the coefficient of restitution. However, to implement this in our ODE solver, we need to detect when the ball hits the ground. We can do this by using Diffsol's event handling feature, which allows us to specify a function that is equal to zero when the event occurs, i.e. when the ball hits the ground. This function \\(g(\mathbf{y}, t)\\) is called an event or root function, and for our bouncing ball problem, it is given by: 51 | 52 | \\[ 53 | g(\mathbf{y}, t) = x 54 | \\] 55 | 56 | where \\(x\\) is the position of the ball. When the ball hits the ground, the event function will be zero and Diffsol will stop the integration, and we can update the velocity of the ball accordingly. 57 | 58 | In code, the bouncing ball problem can be solved using Diffsol as follows: 59 | 60 | ```rust,ignore 61 | {{#include ../../../examples/bouncing-ball/src/main.rs}} 62 | ``` 63 | 64 | {{#include images/bouncing-ball.html}} 65 | -------------------------------------------------------------------------------- /book/src/primer/discrete_events.md: -------------------------------------------------------------------------------- 1 | # Discrete Events 2 | 3 | ODEs describe the continuous evolution of a system over time, but many systems also involve discrete events that occur at specific times. For example, in a compartmental model of drug delivery, the administration of a drug is a discrete event that occurs at a specific time. In a bouncing ball model, the collision of the ball with the ground is a discrete event that changes the state of the system. It is normally difficult to model these events using ODEs alone, as they require a different approach to handle the discontinuities in the system. While we can represent discrete events mathematically using delta functions, many ODE solvers are not designed to handle discontinuities, and may produce inaccurate results or fail to converge during the integration. 4 | 5 | Diffsol provides a way to model discrete events in a system of ODEs by allowing users to manipulate the internal state of each solver during the time-stepping. Each solver has an internal state that holds information such as the current time \\(t\\), the current state of the system \\(\mathbf{y}\\), and other solver-specific information. When a discrete event occurs, the user can update the internal state of the solver to reflect the change in the system, and then continue the integration of the ODE as normal. 6 | 7 | Diffsol also provides a way to stop the integration of the ODEs, either at a specific time or when a specific condition is met. This can be useful for modelling systems with discrete events, as it allows the user to control the integration of the ODEs and to handle the events in a flexible way. 8 | 9 | The [Solving the Problem](../solving_the_problem.md) and [Root Finding](../specify/root_finding.md) sections provides an introduction to the API for solving ODEs and detecting events with Diffsol. In the next two sections, we will look at two examples of systems with discrete events: compartmental models of drug delivery and bouncing ball models, and solve them using Diffsol using this API. -------------------------------------------------------------------------------- /book/src/primer/electrical_circuits.md: -------------------------------------------------------------------------------- 1 | # Example: Electrical Circuits 2 | 3 | Lets consider the following low-pass LRC filter circuit: 4 | 5 | ```plaintext 6 | +---L---+---C---+ 7 | | | | 8 | V_s = R | 9 | | | | 10 | +-------+-------+ 11 | ``` 12 | 13 | The circuit consists of a resistor \\(R\\), an inductor \\(L\\), and a capacitor \\(C\\) connected to a voltage source \\(V_s\\). The voltage across the resistor \\(V\\) is given by Ohm's law: 14 | 15 | \\[ 16 | V = R i_R 17 | \\] 18 | 19 | where \\(i_R\\) is the current flowing through the resistor. The voltage across the inductor is given by: 20 | 21 | \\[ 22 | \frac{di_L}{dt} = \frac{V_s - V}{L} 23 | \\] 24 | 25 | where \\(di_L/dt\\) is the rate of change of current with respect to time. The voltage across the capacitor is the same as the voltage across the resistor and the equation for an ideal capacitor is: 26 | 27 | \\[ 28 | \frac{dV}{dt} = \frac{i_C}{C} 29 | \\] 30 | 31 | where \\(i_C\\) is the current flowing through the capacitor. The sum of the currents flowing into and out of the top-center node of the circuit must be zero according to Kirchhoff's current law: 32 | 33 | \\[ 34 | i_L = i_R + i_C 35 | \\] 36 | 37 | Thus we have a system of two differential equations and two algebraic equation that describe the evolution of the currents through the resistor, inductor, and capacitor; and the voltage across the resistor. We can write these equations in the general form: 38 | 39 | \\[ 40 | M \frac{d\mathbf{y}}{dt} = \mathbf{f}(\mathbf{y}, t) 41 | \\] 42 | 43 | where 44 | 45 | \\[ 46 | \mathbf{y} = \begin{bmatrix} i_R \\\\ i_L \\\\ i_C \\\\ V \end{bmatrix} 47 | \\] 48 | 49 | and 50 | 51 | \\[ 52 | \mathbf{f}(\mathbf{y}, t) = \begin{bmatrix} V - R i_R \\\\ \frac{V_s - V}{L} \\\\ i_L - i_R - i_C \\\\ \frac{i_C}{C} \end{bmatrix} 53 | \\] 54 | 55 | The mass matrix \\(M\\) has one on the diagonal for the differential equations and zero for the algebraic equation. 56 | 57 | \\[ 58 | M = \begin{bmatrix} 0 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 0 & 0 \\\\ 0 & 0 & 0 & 1 \end{bmatrix} 59 | \\] 60 | 61 | Instead of providing the mass matrix explicitly, the DiffSL language specifies the multiplication of the mass matrix with the derivative term, \\(M \frac{d\mathbf{y}}{dt}\\), which is given by: 62 | 63 | \\[ 64 | M \frac{d\mathbf{y}}{dt} = \begin{bmatrix} 0 \\\\ \frac{di_L}{dt} \\\\ 0 \\\\ \frac{dV}{dt} \end{bmatrix} 65 | \\] 66 | 67 | The initial conditions for the system are: 68 | 69 | \\[ 70 | \mathbf{y}(0) = \begin{bmatrix} 0 \\\\ 0 \\\\ 0 \\\\ 0 \end{bmatrix} 71 | \\] 72 | 73 | The voltage source \\(V_s\\) acts as a forcing function for the system, and we can specify this as sinusoidal function of time. 74 | 75 | \\[ 76 | V_s(t) = V_0 \sin(\omega t) 77 | \\] 78 | 79 | where \\(\omega\\) is the angular frequency of the source. Since this is a low-pass filter, we will choose a high frequency for the source, say \\(\omega = 200\\), to demonstrate the filtering effect of the circuit. 80 | 81 | We can solve this system of equations using Diffsol and plot the current and voltage across the resistor as a function of time. 82 | 83 | ```rust,ignore 84 | {{#include ../../../examples/electrical-circuits/src/main.rs}} 85 | ``` 86 | 87 | {{#include images/electrical-circuit.html}} 88 | -------------------------------------------------------------------------------- /book/src/primer/first_order_odes.md: -------------------------------------------------------------------------------- 1 | # Explicit First Order ODEs 2 | 3 | Ordinary Differential Equations (ODEs) are often called rate equations because they describe how the *rate of change* of a system depends on its current *state*. For example, lets assume we wish to model the growth of a population of cells within a petri dish. We could define the *state* of the system as the concentration of cells in the dish, and assign this state to a variable \\(c\\). The *rate of change* of the system would then be the rate at which the concentration of cells changes with time, which we could denote as \\(\frac{dc}{dt}\\). We know that our cells will grow at a rate proportional to the current concentration of cells, so this can be written as: 4 | 5 | \\[ 6 | \frac{dc}{dt} = k c 7 | \\] 8 | 9 | where \\(k\\) is a constant that describes the growth rate of the cells. This is a first order ODE, because it involves only the first derivative of the state variable \\(c\\) with respect to time. 10 | 11 | We can extend this further to solve multiple equations simultaineously, in order to model the rate of change of more than one quantity. For example, say we had *two* populations of cells in the same dish that grow with different rates. We could define the state of the system as the concentrations of the two cell populations, and assign these states to variables \\(c_1\\) and \\(c_2\\). could then write down both equations as: 12 | 13 | \\[ 14 | \begin{align*} 15 | \frac{dc_1}{dt} &= k_1 c_1 \\\\ 16 | \frac{dc_2}{dt} &= k_2 c_2 17 | \end{align*} 18 | \\] 19 | 20 | and then combine them in a vector form as: 21 | 22 | \\[ 23 | \begin{bmatrix} 24 | \frac{dc_1}{dt} \\\\ 25 | \frac{dc_2}{dt} 26 | \end{bmatrix} = \begin{bmatrix} 27 | k_1 c_1 \\\\ 28 | k_2 c_2 29 | \end{bmatrix} 30 | \\] 31 | 32 | By defining a new *vector* of state variables \\(\mathbf{y} = [c_1, c_2]\\) and a vector valued function \\(\mathbf{f}(\mathbf{y}, t) = \begin{bmatrix} k_1 c_1 \\\\ k_2 c_2 \end{bmatrix}\\), we are left with the standard form of a *explicit* first order ODE system: 33 | 34 | \\[ 35 | \frac{d\mathbf{y}}{dt} = \mathbf{f}(\mathbf{y}, t) 36 | \\] 37 | 38 | This is an explicit equation for the derivative of the state, \\(\frac{d\mathbf{y}}{dt}\\), as a function of only of the state variables \\(\mathbf{y}\\) and of time \\(t\\). 39 | 40 | We need one more piece of information to solve this system of ODEs: the initial conditions for the populations at time \\(t = 0\\). For example, if we started with a concentration of 10 for the first population and 5 for the second population, we would write: 41 | 42 | \\[ 43 | \mathbf{y}(0) = \begin{bmatrix} 10 \\\\ 5 \end{bmatrix} 44 | \\] 45 | 46 | Many ODE solver libraries, like Diffsol, require users to provide their ODEs in the form of a set of explicit first order ODEs. Given both the system of ODEs and the initial conditions, the solver can then integrate the equations forward in time to find the solution \\(\mathbf{y}(t)\\). This is the general process for solving ODEs, so it is important to know how to translate your problem into a set of first order ODEs, and thus to the general form of a explicit first order ODE system shown above. In the next two sections, we will look at an example of a first order ODE system in the area of population dynamics, and then solve it using Diffsol. -------------------------------------------------------------------------------- /book/src/primer/forward_sensitivity_analysis.md: -------------------------------------------------------------------------------- 1 | # Forward Sensitivity Analysis 2 | 3 | Recall our general ODE system (we'll define it without the mass matrix for now): 4 | 5 | \\[ 6 | \begin{align*} 7 | \frac{dy}{dt} &= f(t, y, p) \\\\ 8 | y(t_0) &= y_0 9 | \end{align*} 10 | \\] 11 | 12 | Solving this system gives us the solution \\(y(t)\\) for a given set of parameters \\(p\\). However, we often want to know how the solution changes with respect to the parameters (e.g. for model fitting). This is where forward sensitivity analysis comes in. If we take the derivative of the ODE system with respect to the parameters, we get the sensitivity equations: 13 | 14 | \\[ 15 | \begin{align*} 16 | \frac{d}{dt} \frac{dy}{dp} &= \frac{\partial f}{\partial y} \frac{dy}{dp} + \frac{\partial f}{\partial p} \\\\ 17 | \frac{dy}{dp}(t_0) &= \frac{dy_0}{dp} 18 | \end{align*} 19 | \\] 20 | 21 | Here, \\(\frac{dy}{dp}\\) is the sensitivity of the solution with respect to the parameters. The sensitivity equations are solved alongside the ODE system to give us the solution and the sensitivity of the solution with respect to the parameters. Note that this is a similar concept to forward-mode automatic differentiation, but whereas automatic differentiation calculates the derivative of the code itself (e.g. the "discretised" ODE system), forward sensitivity analysis calculates the derivative of the continuous equations before they are discretised. This means that the error control for forward sensitivity analysis is decoupled from the forward solve, and the tolerances for both can be set independently. However, both methods have the same scaling properties as the number of parameters increases, each additional parameter requires one additional solve, so the method is not efficient for large numbers of parameters (>100). In this case, adjoint sensitivity analysis is often preferred. 22 | 23 | To use forward sensitvity analysis in Diffsol, more equations need to be specified that calculate the gradients with respect to the parameters. If you are using the `OdeBuilder` struct and rust closures, you need to supply additional closures that calculate the gradient of the right-hand side, and the gradient of the initial state vector with respect to the parameters. You can see an example of this in the [Forward Sensitivity API](../specify/forward_sensitivity.md) section. If you are using the DiffSL language, these gradients are calculated automatically and you don't need to worry about them. An example of using forward sensitivity analysis in DiffSL is given in the [Fitting a Preditor-Prey Model to Data](./population_dynamics_fitting.md) section next. -------------------------------------------------------------------------------- /book/src/primer/higher_order_odes.md: -------------------------------------------------------------------------------- 1 | # Higher Order ODEs 2 | 3 | The *order* of an ODE is the highest derivative that appears in the equation. So far, we have only looked at first order ODEs, which involve only the first derivative of the state variable with respect to time. However, many physical systems are described by higher order ODEs, which involve second or higher derivatives of the state variable. A simple example of a second order ODE is the motion of a mass under the influence of gravity. The equation of motion for the mass can be written as: 4 | 5 | \\[ 6 | \frac{d^2x}{dt^2} = -g 7 | \\] 8 | 9 | where \\(x\\) is the position of the mass, \\(t\\) is time, and \\(g\\) is the acceleration due to gravity. This is a second order ODE because it involves the second derivative of the position with respect to time. 10 | 11 | Higher order ODEs can always be rewritten as a system of first order ODEs by introducing new variables. For example, we can rewrite the second order ODE above as a system of two first order ODEs by introducing a new variable for the velocity of the mass: 12 | 13 | \\[ 14 | \begin{align*} 15 | \frac{dx}{dt} &= v \\\\ 16 | \frac{dv}{dt} &= -g 17 | \end{align*} 18 | \\] 19 | 20 | where \\(v = \frac{dx}{dt}\\) is the velocity of the mass. This is a system of two first order ODEs, which can be written in vector form as: 21 | 22 | \\[ 23 | \frac{d\mathbf{y}}{dt} = \mathbf{f}(\mathbf{y}, t) 24 | \\] 25 | 26 | where 27 | 28 | \\[ 29 | \mathbf{y} = \begin{bmatrix} x \\\\ v \end{bmatrix} 30 | \\] 31 | 32 | and 33 | 34 | \\[ 35 | \mathbf{f}(\mathbf{y}, t) = \begin{bmatrix} v \\\\ -g \end{bmatrix} 36 | \\] 37 | 38 | In the next section, we'll look at another example of a higher order ODE system: the spring-mass system, and solve this using Diffsol. -------------------------------------------------------------------------------- /book/src/primer/pdes.md: -------------------------------------------------------------------------------- 1 | # Partial Differential Equations (PDEs) 2 | 3 | Diffsol is an ODE solver, but it can also solve PDEs. The idea is to discretize the PDE in space and time, and then solve the resulting system of ODEs. This is called the method of lines. 4 | 5 | Discretizing a PDE is a large topic, and there are many ways to do it. Common methods include finite difference, finite volume, finite element, and spectral methods. Finite difference methods are the simplest to understand and implement, so some of the examples in this book will demonstrate this method to give you a flavour of how to solve PDEs with Diffsol. However, in general we recommend that you use another package to discretise your PDE, and then import the resulting ODE system into Diffsol for solving. 6 | 7 | ## Some useful packages 8 | 9 | There are many packages in the Python and Julia ecosystems that can help you discretise your PDE. Here are a few, but there are many more out there: 10 | 11 | Python 12 | - [FEniCS](https://fenicsproject.org/): A finite element package. Uses the Unified Form Language (UFL) to specify PDEs. 13 | - [FireDrake](https://firedrakeproject.org/): A finite element package, uses the same UFL as FEniCS. 14 | - [FiPy](https://www.ctcms.nist.gov/fipy/): A finite volume package. 15 | - [scikit-fdiff](https://scikit-fdiff.readthedocs.io/en/latest/): A finite difference package. 16 | 17 | Julia: 18 | - [MethodOfLines.jl](https://github.com/SciML/MethodOfLines.jl): A finite difference package. 19 | - [Gridap.jl](https://github.com/gridap/Gridap.jl): A finite element package. 20 | 21 | 22 | -------------------------------------------------------------------------------- /book/src/primer/spatial_population_dynamics.md: -------------------------------------------------------------------------------- 1 | # Example: Spatial Population Dynamics 2 | -------------------------------------------------------------------------------- /book/src/primer/spring_mass_systems.md: -------------------------------------------------------------------------------- 1 | # Example: Spring-mass systems 2 | 3 | We will model a [damped spring-mass system](https://en.wikipedia.org/wiki/Mass-spring-damper_model) using a second order ODE. The system consists of a mass \\(m\\) attached to a spring with spring constant \\(k\\), and a damping force proportional to the velocity of the mass with damping coefficient \\(c\\). 4 | 5 | 6 | 7 | The equation of motion for the mass can be written as: 8 | 9 | \\[ 10 | m \frac{d^2x}{dt^2} = -k x - c \frac{dx}{dt} 11 | \\] 12 | 13 | where \\(x\\) is the position of the mass, \\(t\\) is time, and the negative sign on the right hand side indicates that the spring force and damping force act in the opposite direction to the displacement of the mass. 14 | 15 | We can convert this to a system of two first order ODEs by introducing a new variable for the velocity of the mass: 16 | 17 | \\[ 18 | \begin{align*} 19 | \frac{dx}{dt} &= v \\\\ 20 | \frac{dv}{dt} &= -\frac{k}{m} x - \frac{c}{m} v 21 | \end{align*} 22 | \\] 23 | 24 | where \\(v = \frac{dx}{dt}\\) is the velocity of the mass. 25 | 26 | We can solve this system of ODEs using Diffsol with the following code: 27 | 28 | ```rust,ignore 29 | {{#include ../../../examples/spring-mass-system/src/main.rs}} 30 | ``` 31 | 32 | {{#include images/spring-mass-system.html}} 33 | -------------------------------------------------------------------------------- /book/src/primer/the_mass_matrix.md: -------------------------------------------------------------------------------- 1 | # DAEs via the Mass Matrix 2 | 3 | Differential-algebraic equations (DAEs) are a generalisation of ordinary differential equations (ODEs) that include algebraic equations, or equations that do not involve derivatives. Algebraic equations can arise in many physical systems and often are used to model constraints on the system, such as conservation laws or other relationships between the state variables. For example, in an electrical circuit, the current flowing into a node must equal the current flowing out of the node, which can be written as an algebraic equation. 4 | 5 | DAEs can be written in the general implicit form: 6 | 7 | \\[ 8 | \mathbf{F}(\mathbf{y}, \mathbf{y}', t) = 0 9 | \\] 10 | 11 | where \\(\mathbf{y}\\) is the vector of state variables, \\(\mathbf{y}'\\) is the vector of derivatives of the state variables, and \\(\mathbf{F}\\) is a vector-valued function that describes the system of equations. However, for the purposes of this primer and the capabilities of Diffsol, we will focus on a specific form of DAEs called index-1 or semi-explicit DAEs, which can be written as a combination of differential and algebraic equations: 12 | 13 | \\[ 14 | \begin{align*} 15 | \frac{d\mathbf{y}}{dt} &= \mathbf{f}(\mathbf{y}, t) \\\\ 16 | 0 &= \mathbf{g}(\mathbf{y}, t) 17 | \end{align*} 18 | \\] 19 | 20 | where \\(\mathbf{f}\\) is the vector-valued function that describes the differential equations and \\(\mathbf{g}\\) is the vector-valued function that describes the algebraic equations. The key difference between DAEs and ODEs is that DAEs include algebraic equations that must be satisfied at each time step, in addition to the differential equations that describe the rate of change of the state variables. 21 | 22 | How does this relate to the standard form of an explicit ODE that we have seen before? Recall that an explicit ODE can be written as: 23 | 24 | \\[ 25 | \frac{d\mathbf{y}}{dt} = \mathbf{f}(\mathbf{y}, t) 26 | \\] 27 | 28 | Lets update this equation to include a matrix \\(\mathbf{M}\\) that multiplies the derivative term: 29 | 30 | \\[ 31 | M \frac{d\mathbf{y}}{dt} = \mathbf{f}(\mathbf{y}, t) 32 | \\] 33 | 34 | When \\(M\\) is the identity matrix (i.e. a matrix with ones along the diagonal), this reduces to the standard form of an explicit ODE. However, when \\(M\\) has diagonal entries that are zero, this introduces algebraic equations into the system and it reduces to the semi-explicit DAE equations show above. The matrix \\(M\\) is called the *mass matrix*. 35 | 36 | Thus, we now have a general form of a set of differential equations, that includes both ODEs and semi-explicit DAEs. This general form is used by Diffsol to allow users to specify a wide range of problems, from simple ODEs to more complex DAEs. In the next section, we will look at a few examples of DAEs and how to solve them using Diffsol and a mass matrix. 37 | 38 | -------------------------------------------------------------------------------- /book/src/solve/adjoint_sens.md: -------------------------------------------------------------------------------- 1 | # Adjoint Sensitivities 2 | 3 | Solving the adjoint sensitivity problem requires a different approach than the forward sensitivity problem. The adjoint sensitivity problem is solved by first solving the original ODE problem and checkpointing the solution so it can be used later. The adjoint sensitivity problem is then solved backwards in time, using the solution of the original ODE problem as an input. 4 | 5 | The [`AdjointOdeSolverMethod`](https://docs.rs/diffsol/latest/diffsol/ode_solver/adjoint/trait.AdjointOdeSolverMethod.html) trait provides a way to compute the adjoint sensitivities of the solution of an ODE problem, using its `solve_dense_adjoint_sensitivities` method. -------------------------------------------------------------------------------- /book/src/solve/forward_sens.md: -------------------------------------------------------------------------------- 1 | # Forward Sensitivities 2 | 3 | The [`SensitivitiesOdeSolverMethod`](https://docs.rs/diffsol/latest/diffsol/ode_solver/sensitivities/trait.SensitivitiesOdeSolverMethod.html) trait provides a way to compute the forward sensitivities of the solution of an ODE problem, using its `solve_dense_sensitivities` method. 4 | 5 | This method computes both the solution and the sensitivities at the same time, at the time-points provided by the user. These are returned as a tuple containing: 6 | 7 | - A matrix of the solution at each time-point, where each column corresponds to a time-point and each row corresponds to a state variable. 8 | - a `Vec` of matrices containing the sensitivities at each time-point. Each element of the outer vector corresponds to the sensitivities for each parameter, and each column of the inner matrix corresponds to a time-point. 9 | 10 | ```rust,ignore 11 | {{#include ../../../examples/intro-logistic-closures/src/solve_fwd_sens.rs}} 12 | ``` -------------------------------------------------------------------------------- /book/src/solve/interpolation.md: -------------------------------------------------------------------------------- 1 | # Interpolation 2 | 3 | Often you will want to get the solution at a specific time \\(t_o\\), which is probably different to the internal timesteps chosen by the solver. To do this, you can use the `step` method to first step the solver forward in time until you are just beyond \\(t_o\\), and then use the `interpolate` method to get the solution at \\(t_o\\). 4 | 5 | ```rust,ignore 6 | {{#include ../../../examples/intro-logistic-closures/src/solve_interpolate.rs}} 7 | ``` -------------------------------------------------------------------------------- /book/src/solve/manual-time-stepping.md: -------------------------------------------------------------------------------- 1 | # Manual time-stepping 2 | 3 | The fundamental method to step the solver through a solution is the [`step`](https://docs.rs/diffsol/latest/diffsol/ode_solver/method/trait.OdeSolverMethod.html#tymethod.step) method on the `OdeSolverMethod` trait, which steps the solution forward in time by a single step, with a step size chosen by the solver in order to satisfy the error tolerances in the `problem` struct. The `step` method returns a `Result` that contains the new state of the solution if the step was successful, or an error if the step failed. 4 | 5 | ```rust,ignore 6 | {{#include ../../../examples/intro-logistic-closures/src/solve_step.rs}} 7 | ``` 8 | 9 | The `step` method will return an error if the solver fails to converge to the solution or if the step size becomes too small. -------------------------------------------------------------------------------- /book/src/solve/solving_the_problem.md: -------------------------------------------------------------------------------- 1 | # Solving the problem 2 | 3 | Each solver implements the [`OdeSolverMethod`](https://docs.rs/diffsol/latest/diffsol/ode_solver/method/trait.OdeSolverMethod.html) trait, which provides a number of high-level methods to solve the problem. 4 | 5 | - [`solve`](https://docs.rs/diffsol/latest/diffsol/ode_solver/method/trait.OdeSolverMethod.html#method.solve) - solve the problem from an initial state up to a specified time, returning the solution at all the internal timesteps used by the solver. 6 | - [`solve_dense`](https://docs.rs/diffsol/latest/diffsol/ode_solver/method/trait.OdeSolverMethod.html#method.solve_dense) - solve the problem from an initial state, returning the solution at a `Vec` of times provided by the user. 7 | 8 | The following example shows how to solve a simple ODE problem up to \\(t=10\\) using the `solve` method on the `OdeSolverMethod` trait. The solver will return the solution at all the internal timesteps used by the solver (`_ys`), as well as the timesteps used by the solver (`_ts`). The solution is returned as a matrix whose columns are the solution at each timestep. 9 | 10 | ```rust,ignore 11 | {{#include ../../../examples/intro-logistic-closures/src/solve.rs}} 12 | ``` 13 | 14 | `solve_dense` will solve a problem from an initial state, returning the solution as a matrix whose columns are the solution at each timestep in `times`. 15 | 16 | ```rust,ignore 17 | {{#include ../../../examples/intro-logistic-closures/src/solve_dense.rs}} 18 | ``` -------------------------------------------------------------------------------- /book/src/solve/stopping.md: -------------------------------------------------------------------------------- 1 | # Stopping 2 | 3 | There are two methods to halt the solver when a certain condition is met: you can either stop at a specific time or stop when a certain event occurs. 4 | 5 | Stopping at a specific time is straightforward, as you can use the `set_stop_time` method on the `OdeSolverMethod` trait and then just check if the return value of the `step` method is `Ok(OdeSolverStopReason::TstopReached)` 6 | 7 | Stopping at a certain event requires you to set a root function in your system of equations, see [Root Finding](specify/closure/root_finding.md) for more information. During time-stepping, you can check if the solver has discovered a root by checking if the return value of the `step` method is `Ok(OdeSolverStopReason::RootFound)`. `RootFound` holds the time at which the root was found, and you can use the `interpolate` method to obtain the solution at that time. 8 | 9 | ```rust,ignore 10 | {{#include ../../../examples/intro-logistic-closures/src/solve_match_step.rs}} 11 | ``` -------------------------------------------------------------------------------- /book/src/solve/stopping_event.md: -------------------------------------------------------------------------------- 1 | # Stopping at Events 2 | -------------------------------------------------------------------------------- /book/src/solve/stopping_time.md: -------------------------------------------------------------------------------- 1 | # Stopping at time 2 | 3 | -------------------------------------------------------------------------------- /book/src/solver/creating_a_solver.md: -------------------------------------------------------------------------------- 1 | # Creating a solver 2 | 3 | Once you have defined the problem, you need to create a solver to solve the problem. The available solvers are: 4 | - [`diffsol::Bdf`](https://docs.rs/diffsol/latest/diffsol/ode_solver/bdf/struct.Bdf.html): A Backwards Difference Formulae solver, suitable for stiff problems and singular mass matrices. 5 | - [`diffsol::Sdirk`](https://docs.rs/diffsol/latest/diffsol/ode_solver/sdirk/struct.Sdirk.html) A Singly Diagonally Implicit Runge-Kutta (SDIRK or ESDIRK) solver. You can define your own butcher tableau using [`Tableau`](https://docs.rs/diffsol/latest/diffsol/ode_solver/tableau/struct.Tableau.html) or use one of the pre-defined tableaues. 6 | - [`diffsol::ExplicitRk`](https://docs.rs/diffsol/latest/diffsol/ode_solver/explicit_rk/struct.ExplicitRk.html): An explicit Runge-Kutta solver. You can define your own butcher tableau using [`Tableau`](https://docs.rs/diffsol/latest/diffsol/ode_solver/tableau/struct.Tableau.html) or use one of the pre-defined tableaues. 7 | 8 | For each solver, you will need to specify the linear solver type to use. The available linear solvers are: 9 | - [`diffsol::NalgebraLU`](https://docs.rs/diffsol/latest/diffsol/linear_solver/nalgebra_lu/struct.NalgebraLU.html): A LU decomposition solver using the [nalgebra](https://nalgebra.org) crate. 10 | - [`diffsol::FaerLU`](https://docs.rs/diffsol/latest/diffsol/linear_solver/faer_lu/struct.FaerLU.html): A LU decomposition solver using the [faer](https://github.com/sarah-ek/faer-rs) crate. 11 | - [`diffsol::FaerSparseLU`](https://docs.rs/diffsol/latest/diffsol/linear_solver/faer_sparse_lu/struct.FaerSparseLU.html): A sparse LU decomposition solver using the `faer` crate. 12 | 13 | Each solver can be created directly, but it generally easier to use the methods on the [`OdeSolverProblem`](https://docs.rs/diffsol/latest/diffsol/ode_solver/problem/struct.OdeSolverProblem.html) struct to create the solver. 14 | For example: 15 | 16 | ```rust,ignore 17 | {{#include ../../../examples/intro-logistic-closures/src/create_solvers.rs}} 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /book/src/solver/initialisation.md: -------------------------------------------------------------------------------- 1 | # Initialisation 2 | 3 | Each solver has an internal state that holds information like the current state vector, the gradient of the state vector, the current time, and the current step size. When you create a solver using the `bdf` or `sdirk` methods on the `OdeSolverProblem` struct, the solver will be initialised with an initial state based on the initial conditions of the problem as well as satisfying any algebraic constraints. An initial time step will also be chosen based on your provided equations. 4 | 5 | Each solver's state struct implements the [`OdeSolverState`](https://docs.rs/diffsol/latest/diffsol/ode_solver/state/trait.OdeSolverState.html) trait, and if you wish to manually create and setup a state, you can use the methods on this trait to do so. 6 | 7 | For example, say that you wish to bypass the initialisation of the state as you already have the algebraic constraints and so don't need to solve for them. You can use the `new_without_initialise` method on the `OdeSolverState` trait to create a new state without initialising it. You can then use the `as_mut` method to get a mutable reference to the state and set the values manually. 8 | 9 | ```rust,ignore 10 | {{#include ../../../examples/intro-logistic-closures/src/create_solvers_uninit.rs}} 11 | ``` 12 | 13 | Note that each state struct has a [`as_ref`](https://docs.rs/diffsol/latest/diffsol/ode_solver/state/trait.OdeSolverState.html#tymethod.as_ref) and [`as_mut`](https://docs.rs/diffsol/latest/diffsol/ode_solver/state/trait.OdeSolverState.html#tymethod.as_mut) methods that return a [`StateRef`](https://docs.rs/diffsol/latest/diffsol/ode_solver/state/struct.StateRef.html) or [`StateRefMut`](https://docs.rs/diffsol/latest/diffsol/ode_solver/state/struct.StateRefMut.html) struct respectively. These structs provide a solver-independent way to access the state values so you can use the same code with different solvers. 14 | 15 | -------------------------------------------------------------------------------- /book/src/solver/tableau.md: -------------------------------------------------------------------------------- 1 | # Butchers Tableau 2 | 3 | The Butcher tableau is a way of representing the coefficients of a Runge-Kutta method (see the [wikipedia page](https://en.wikipedia.org/wiki/Butcher_tableau)). Diffsol uses the [`Tableau`](https://docs.rs/diffsol/latest/diffsol/ode_solver/tableau/struct.Tableau.html) struct to represent the tableau, and this is used to create any of the SDIRK or ERK solvers. Diffsol has a few inbuilt tableaus that you can use, otherwise you can create your own by constructing an instance of `Tableau` 4 | 5 | To create an SDIRK or ERK solver using a pre-defined tableau, you can use methods on the `OdeSolverProblem` struct like so: 6 | 7 | ```rust,ignore 8 | {{#include ../../../examples/intro-logistic-closures/src/create_solvers_tableau.rs}} 9 | ``` -------------------------------------------------------------------------------- /book/src/specify/closure/explicit.md: -------------------------------------------------------------------------------- 1 | # An explicit example 2 | 3 | Below is an example of how to create an explicit ODE problem using the `OdeBuilder` struct. This type of problem is suitable only for explicit solver methods. 4 | 5 | The specific problem we will solve is the logistic equation 6 | 7 | \\[\frac{dy}{dt} = r y (1 - y/K),\\] 8 | 9 | where \\(r\\) is the growth rate and \\(K\\) is the carrying capacity. 10 | To specify the problem, we need to provide the \\(dy/dt\\) function \\(f(y, p, t)\\), 11 | 12 | \\[f(y, p, t) = r y (1 - y/K),\\] 13 | 14 | and the initial state 15 | 16 | \\[y_0(p, t) = 0.1\\] 17 | 18 | This can be done using the following code: 19 | 20 | ```rust,ignore 21 | {{#include ../../../../examples/intro-logistic-closures/src/problem_explicit.rs}} 22 | ``` 23 | 24 | The return type of this function, `OdeSolverProblem>` details the type of problem that we are returning. The `OdeSolverProblem` struct contains our equations, which are of type `impl OdeEquations`, where `M` is the matrix type, `V` is the vector type, `T` is the time type, and `C` is the context type. We need to specify the matrix, vector, scalar and context types as these are used for the underlying linear algebra containers and operations. The `OdeEquations` trait specifies that this system of equations is only suitable for explicit solver methods. 25 | 26 | The `rhs` method is used to specify the \\(f(y, p, t)\\) function, whereas the `init` method is used to specify the initial state vector \\(y_0(p, t)\\). -------------------------------------------------------------------------------- /book/src/specify/closure/forward_sensitivity.md: -------------------------------------------------------------------------------- 1 | # Forward Sensitivity 2 | 3 | In this section we will discuss how to compute the forward sensitivity of the solution of an ODE problem. The forward sensitivity is the derivative of the solution with respect to the parameters of the problem. This is useful for many applications, such as parameter estimation, optimal control, and uncertainty quantification. 4 | 5 | ## Specifying the sensitivity problem 6 | 7 | We will again use the example of the logistic growth equation, but this time we will compute the sensitivity of the solution \\(y\\) with respect to the parameters \\(r\\) and \\(K\\) (i.e. \\(\frac{dy}{dr}\\) and \\(\frac{dy}{dK}\\)). 8 | The logistic growth equation is: 9 | 10 | \\[\frac{dy}{dt} = r y (1 - y/K),\\] 11 | \\[y(0) = 0.1\\] 12 | 13 | Recall from [ODE equations](ode_equations.md) that we also need to provide the jacobian of the right hand side of the ODE with respect to the state vector \\(y\\) and the gradient vector \\(v\\), which we will call \\(J\\). This is: 14 | 15 | \\[J v = \begin{bmatrix} r v (1 - 2 y/K) \end{bmatrix}.\\] 16 | 17 | Using the logistic growth equation above, we can compute the partial derivative of the right hand side of the ODE with respect to the vector \\([r, K]\\) multiplied by a vector \\(v = [v_r, v_K]\\), which we will call \\(J_p v\\). This is: 18 | 19 | \\[J_p v = v_r y (1 - y/K) + v_K r y^2 / K^2 .\\] 20 | 21 | We also need the partial derivative of the initial state vector with respect to the parameters multiplied by a vector \\(v\\), which we will call \\(J_{y_0} v\\). Since the initial state vector is constant, this is just zero 22 | 23 | \\[J_{y_0} v = 0.\\] 24 | 25 | We can then use the `OdeBuilder` struct to specify the sensitivity problem. The `rhs_sens_implicit` and `init_sens` methods are used to create a new problem that includes the sensitivity equations. 26 | 27 | ```rust,ignore 28 | {{#include ../../../../examples/intro-logistic-closures/src/problem_fwd_sens.rs}} 29 | ``` 30 | 31 | ## Solving the sensitivity problem 32 | 33 | Once the sensitivity problem has been specified, we can solve it using the [`OdeSolverMethod`](https://docs.rs/diffsol/latest/diffsol/ode_solver/method/trait.OdeSolverMethod.html) trait. 34 | Lets imagine we want to solve the sensitivity problem up to a time \\(t_o = 10\\). We can use the `OdeSolverMethod` trait to solve the problem as normal, stepping forward in time until we reach \\(t_o\\). 35 | 36 | ```rust,ignore 37 | {{#include ../../../../examples/intro-logistic-closures/src/solve_fwd_sens_step.rs}} 38 | ``` 39 | 40 | We can then obtain the sensitivity vectors at time \\(t_o\\) using the `interpolate_sens` method on the `OdeSolverMethod` trait. 41 | This method returns a `Vec>` where each element of the vector is the sensitivity vector for element \\(i\\) of the parameter vector at time \\(t_o\\). 42 | If we need the sensitivity at the current internal time step, we can get this from the `s` field of the [`OdeSolverState`](https://docs.rs/diffsol/latest/diffsol/ode_solver/method/struct.OdeSolverState.html) struct. -------------------------------------------------------------------------------- /book/src/specify/closure/implicit.md: -------------------------------------------------------------------------------- 1 | # An implicit example 2 | 3 | We will now create an implicit ODE problem, again using the logistic equation as an example. This problem can be used in both the explicit and implicit diffsol solvers. 4 | 5 | \\[\frac{dy}{dt} = r y (1 - y/K),\\] 6 | 7 | where \\(r\\) is the growth rate and \\(K\\) is the carrying capacity. 8 | To specify the problem, we need to provide the \\(dy/dt\\) function \\(f(y, p, t)\\), 9 | and the jacobian of \\(f\\) multiplied by a vector \\(v\\) function, which we will call \\(f'(y, p, t, v)\\). That is 10 | 11 | \\[f(y, p, t) = r y (1 - y/K),\\] 12 | \\[f'(y, p, t, v) = rv (1 - 2y/K),\\] 13 | 14 | and the initial state 15 | 16 | \\[y_0(p, t) = 0.1\\] 17 | 18 | This can be done using the following code: 19 | 20 | ```rust,ignore 21 | {{#include ../../../../examples/intro-logistic-closures/src/problem_implicit.rs}} 22 | ``` 23 | 24 | The `rhs_implicit` method is used to specify the \\(f(y, p, t)\\) and \\(f'(y, p, t, v)\\) functions, whereas the `init` method is used to specify the initial state vector \\(y_0(p, t)\\). 25 | -------------------------------------------------------------------------------- /book/src/specify/closure/mass_matrix.md: -------------------------------------------------------------------------------- 1 | # Mass matrix 2 | 3 | In some cases, it is necessary to include a mass matrix in the problem, such that the problem is of the form 4 | 5 | \\[M(t) \frac{dy}{dt} = f(t, y, p).\\] 6 | 7 | A mass matrix is useful for PDE discretisation that lead to a non-identity mass matrix, or for DAE problems that can be transformed into ODEs with a singular mass matrix. 8 | Diffsol can handle singular and non-singular mass matrices, and the mass matrix can be time-dependent. 9 | 10 | ## Example 11 | 12 | To illustrate the addition of a mass matrix to a problem, we will once again take the logistic equation, but this time we will add an additional variable that is set via an algebraic equation. 13 | This system is written as 14 | 15 | \\[\frac{dy}{dt} = r y (1 - y/K),\\] 16 | \\[0 = y - z,\\] 17 | 18 | where \\(z\\) is the additional variable with a solution \\(z = y\\). When this system is put in the form \\(M(t) \frac{dy}{dt} = f(t, y, p)\\), the mass matrix is 19 | 20 | \\[M(t) = \begin{bmatrix} 1 & 0 \\\\ 0 & 0 \end{bmatrix}.\\] 21 | 22 | Like the Jacobian, the Diffsol builder does not require the full mass matrix, instead users can provide a function that gives a GEMV (General Matrix-Vector) product of the mass matrix with a vector. 23 | 24 | \\[m(\mathbf{v}, \mathbf{p}, t, \beta, \mathbf{y}) = M(p, t) \mathbf{v} + \beta \mathbf{y}. \\] 25 | 26 | Thus, to specify this problem using Diffsol, we can use the [`OdeBuilder`](https://docs.rs/diffsol/latest/diffsol/ode_solver/builder/struct.OdeBuilder.html) struct and provide the functions: 27 | 28 | \\[f(\mathbf{y}, \mathbf{p}, t) = \begin{bmatrix} r y_0 (1 - y_0/K) \\\\ y_0 - y_1 \end{bmatrix},\\] 29 | \\[f'(\mathbf{y}, \mathbf{p}, t, \mathbf{v}) = \begin{bmatrix} r v_0 (1 - 2 y_0/K) \\\\ v_0 - v_1 \end{bmatrix},\\] 30 | \\[m(\mathbf{v}, \mathbf{p}, t, \beta, \mathbf{y}) = \begin{bmatrix} v_0 + \beta y_0 \\\\ \beta y_1 \end{bmatrix}.\\] 31 | 32 | where \\(f\\) is the right-hand side of the ODE, \\(f'\\) is the Jacobian of \\(f\\) multiplied by a vector, and \\(m\\) is the mass matrix multiplied by a vector. 33 | 34 | ```rust,ignore 35 | {{#include ../../../../examples/intro-logistic-closures/src/problem_mass.rs}} 36 | ``` 37 | -------------------------------------------------------------------------------- /book/src/specify/closure/root_finding.md: -------------------------------------------------------------------------------- 1 | # Root finding 2 | 3 | Root finding is the process of finding the values of the variables that make a set of equations equal to zero. This is a common problem where you want to 4 | stop the solver or perform some action when a certain condition is met. 5 | 6 | ## Specifying the root finding function 7 | 8 | Using the logistic example, we can add a root finding function \\(r(y, p, t)\\) that will stop the solver when the value of \\(y\\) is such that \\(r(y, p, t) = 0\\). 9 | For this example we'll use the root finding function \\(r(y, p, t) = y - 0.5\\), which will stop the solver when the value of \\(y\\) is 0.5. 10 | 11 | 12 | \\[\frac{dy}{dt} = r y (1 - y/K),\\] 13 | \\[r(y, p, t) = y - 0.5,\\] 14 | 15 | This can be done using the [`OdeBuilder`](https://docs.rs/diffsol/latest/diffsol/ode_solver/builder/struct.OdeBuilder.html) via the following code: 16 | 17 | ```rust,ignore 18 | {{#include ../../../../examples/intro-logistic-closures/src/problem_root.rs}} 19 | ``` 20 | 21 | here we have added the root finding function \\(r(y, p, t) = y - 0.5\\), and also let Diffsol know that we have one root function by passing `1` as the last argument to the `root` method. 22 | If we had specified more than one root function, the solver would stop when any of the root functions are zero. 23 | 24 | ## Detecting roots during the solve 25 | 26 | To detect the root during the solve, we can use the return type on the [`step`](https://docs.rs/diffsol/latest/diffsol/ode_solver/method/trait.OdeSolverMethod.html#tymethod.step) method of the solver. 27 | If successful the `step` method returns an [`OdeSolverStopReason`](https://docs.rs/diffsol/latest/diffsol/ode_solver/method/enum.OdeSolverStopReason.html) enum that contains the reason the solver stopped. 28 | 29 | 30 | ```rust,ignore 31 | {{#include ../../../../examples/intro-logistic-closures/src/solve_match_step.rs}} 32 | ``` -------------------------------------------------------------------------------- /book/src/specify/closure/rust_closures.md: -------------------------------------------------------------------------------- 1 | # Rust closures 2 | 3 | To create a new ode problem, use the [`OdeBuilder`](https://docs.rs/diffsol/latest/diffsol/ode_solver/builder/struct.OdeBuilder.html) struct. You can create a builder using the `OdeBuilder::new` method, and then chain methods to set the equations to be solve, initial time, initial step size, relative & absolute tolerances, and parameters, or leave them at their default values. Then, call the `build` method to create a new problem. 4 | 5 | ## Explicit vs implicit methods 6 | 7 | Diffsol contains a variety of methods for solving ODEs, including both explicit and implicit methods. An example of an explicit method is the euler method, which is a simple first-order method that uses the current state to calculate the next state. The only information needed to use this method is the right-hand side of the ODE, which is provided by the user. An example of an implicit method is the backward euler method, which uses the current state and the next state to calculate the next state, by solving an implicit non-linear equation. This method requires the user to provide the right-hand side of the ODE, as well as the jacobian of the right-hand side with respect to the state vector. 8 | 9 | Diffsol uses the Rust type system to distinguish between user-provided systems of equations that are either explicit or implicit. Any system of equations that is suitable for an explicit method implements the [`OdeEquations`](https://docs.rs/diffsol/latest/diffsol/ode_solver/equations/trait.OdeEquations.html) trait, whereas a system of equations that is suitable for an implicit method implements the [`OdeEquationsImplicit`](https://docs.rs/diffsol/latest/diffsol/ode_solver/equations/trait.OdeEquationsImplicit.html) trait. If you are using the rust closures API, this is largely hidden from you, but you still need to be aware of these traits when when specifying your own functions or structs. -------------------------------------------------------------------------------- /book/src/specify/closure/sparse_problems.md: -------------------------------------------------------------------------------- 1 | # Sparse problems 2 | 3 | Lets consider a large system of equations that have a jacobian matrix that is sparse. For simplicity we will start with the logistic equation from the ["Specifying the Problem"](./specifying_the_problem.md) section, 4 | but we will duplicate this equation 10 times to create a system of 10 equations. This system will have a jacobian matrix that is a diagonal matrix with 10 diagonal elements, and all other elements are zero. 5 | 6 | Since this system is sparse, we choose a sparse matrix type to represent the jacobian matrix. We will use the `diffsol::FaerSparseMat` type, which is a thin wrapper around `faer::sparse::FaerSparseMat`, a sparse compressed sparse column matrix type. 7 | 8 | ```rust,ignore 9 | {{#include ../../../../examples/intro-logistic-closures/src/problem_sparse.rs}} 10 | ``` 11 | 12 | Note that we have not specified the jacobian itself, but instead we have specified the jacobian multiplied by a vector function \\(f'(y, p, t, v)\\). 13 | Diffsol will use this function to generate a jacobian matrix, and since we have specified a sparse matrix type, Diffsol will attempt to 14 | guess the sparsity pattern of the jacobian matrix and use this to efficiently generate the jacobian matrix. 15 | 16 | To illustrate this, we can calculate the jacobian matrix from the `rhs` function contained in the `problem` object: 17 | 18 | ```rust,ignore 19 | {{#include ../../../../examples/intro-logistic-closures/src/print_jacobian.rs}} 20 | ``` 21 | 22 | which will print the jacobian matrix in triplet format: 23 | 24 | ``` 25 | (0, 0) = 0.98 26 | (1, 1) = 0.98 27 | (2, 2) = 0.98 28 | (3, 3) = 0.98 29 | (4, 4) = 0.98 30 | (5, 5) = 0.98 31 | (6, 6) = 0.98 32 | (7, 7) = 0.98 33 | (8, 8) = 0.98 34 | (9, 9) = 0.98 35 | ``` 36 | 37 | Diffsol attempts to guess the sparsity pattern of your jacobian matrix by calling the \\(f'(y, p, t, v)\\) function repeatedly with different one-hot vectors \\(v\\) 38 | with a `NaN` value at each hot index. The output of this function (i.e. which elements are `0` and which are `NaN`) is then used to determine the sparsity pattern of the jacobian matrix. 39 | Due to the fact that for IEEE 754 floating point numbers, `NaN` is propagated through most operations, this method is able to detect which output elements are dependent on which input elements. 40 | 41 | However, this method is not foolproof, and it may fail to detect the correct sparsity pattern in some cases, particularly if values of `v` are used in control-flow statements. 42 | If Diffsol does not detect the correct sparsity pattern, you can manually specify the jacobian. To do this, you need to use a custom struct that implements the `OdeEquations` trait, 43 | This is described in more detail in the ["OdeEquations trait"](../trait/ode_equations_trait.md) section. -------------------------------------------------------------------------------- /book/src/specify/diffsl/diffsl.md: -------------------------------------------------------------------------------- 1 | # DiffSL 2 | 3 | Many ODE libraries allow you to specify your ODE system using the host language, for example an ODE library written in C might allow you to write a C function for your RHS equations. However, this has limitations if you want to wrap this library in a higher level language like Python or R, how then do you provide the RHS functions? 4 | 5 | For this usecase we have designed a Domain Specific Language (DSL) called DiffSL that can be used to specify the problem. DiffSL is not a general purpose language but is tightly constrained to 6 | the specification of a system of ordinary differential equations. It features a relatively simple syntax that consists of writing a series of tensors (dense or sparse) that represent the equations of the system. 7 | 8 | ## DiffSL syntax 9 | 10 | For more detail on the syntax of DiffSL see the [DiffSL book](https://martinjrobins.github.io/diffsl/). This section will focus on how to use DiffSL to specify a problem in Diffsol. 11 | 12 | ## Creating a DiffSL problem 13 | 14 | To create a DiffSL problem you simply need to use the `build_from_eqn` method on the [`OdeBuilder`](https://docs.rs/diffsol/latest/diffsol/ode_solver/builder/struct.OdeBuilder.html) struct, passing in a `str` containing the DiffSL code. The DiffSL code is then parsed and compiled into native machine code using either the LLVM or Cranelift backends. The `CG` type parameter specifies the backend that you want to use to compile the DiffSL code. The `CraneliftJitModule` backend is behind the `diffsl-cranelift` feature flag. The faster [`LlvmModule`](https://docs.rs/diffsol/latest/diffsol/struct.LlvmModule.html) backend is behind one of the `diffsl-llvm*` feature flags (currently `diffsl-llvm15`, `diffsl-llvm16`, `diffsl-llvm17` or `diffsl-llvm18`), depending on the version of LLVM you have installed. 15 | 16 | For example, here is an example of speciying a simple logistic equation using DiffSL: 17 | 18 | ```rust,ignore 19 | {{#include ../../../../examples/intro-logistic-diffsl/src/main.rs}} 20 | ``` 21 | -------------------------------------------------------------------------------- /book/src/specify/specifying_the_problem.md: -------------------------------------------------------------------------------- 1 | # Diffsol APIs for specifying problems 2 | 3 | Most of the Diffsol user-facing API revolves around specifying the problem you want to solve, thus a large part of this book will be dedicated to explaining how to specify a problem. All the examples presented in [the primer](../primer/modelling_with_odes.md) used the DiffSL DSL to specify the problem, but Diffsol also provides a pure Rust API for specifying problems. This API is sometimes more verbose than the DSL, but is more flexible, more performant, and easier to use if you have a model already written in Rust or another language that you can easily port or call from Rust. 4 | 5 | ## ODE equations 6 | 7 | The class of ODE equations that Diffsol can solve are of the form 8 | 9 | \\[M(t) \frac{dy}{dt} = f(t, y, p),\\] 10 | \\[y(t_0) = y_0(t_0, p),\\] 11 | \\[z(t) = g(t, y, p),\\] 12 | 13 | where: 14 | 15 | - \\(f(t, y, p)\\) is the right-hand side of the ODE, 16 | - \\(y\\) is the state vector, 17 | - \\(p\\) are the parameters, 18 | - \\(t\\) is the time. 19 | - \\(y_0(t_0, p)\\) is the initial state vector at time \\(t_0\\). 20 | - \\(M(t)\\) is the mass matrix (this is optional, and is implicitly the identity matrix if not specified), 21 | - \\(g(t, y, p)\\) is an output function that can be used to calculate additional outputs from the state vector (this is optional, and is implicitly \\(g(t, y, p) = y\\) if not specified). 22 | 23 | The user can also optionally specify a root function \\(r(t, y, p)\\) that can be used to find the time at which a root occurs. 24 | 25 | ## Diffsol problem APIs 26 | 27 | Specifying a problem in Diffsol is done via the [`OdeBuilder`](https://docs.rs/diffsol/latest/diffsol/ode_solver/builder/struct.OdeBuilder.html) struct, using the `OdeBuilder::new` method to create a new builder, and then chaining methods to set the equations to be solved, initial time, initial step size, relative & absolute tolerances, and parameters, or leaving them at their default values. Then, call a `build` method to create a new problem. 28 | 29 | Users can specify the equations to be solved via three main APIs, ranging from the simplest to the most complex (but also the most flexible): 30 | 31 | - The [`DiffSl`](https://docs.rs/diffsol/latest/diffsol/ode_solver/diffsl/struct.DiffSl.html) struct allows users to specify the equations above using the [DiffSL](https://martinjrobins.github.io/diffsl/) 32 | Domain Specific Language (DSL). This API is behind a feature flag (`diffsl-cranelift` if you want to use the slower cranelift backend, `diffsl-llvm*` if you want to use the faster LLVM backend). This is the easiest API to use as it can use automatic differentiation to calculate the neccessary gradients, and is the best API if you want to use DiffSL from a higher-level language like Python or R while still having similar performance to Rust. 33 | - The [`OdeBuilder`](https://docs.rs/diffsol/latest/diffsol/ode_solver/builder/struct.OdeBuilder.html) struct also has methods to set the equations using rust closures (see e.g. [OdeBuilder::rhs_implicit](https://docs.rs/diffsol/latest/diffsol/ode_solver/builder/struct.OdeBuilder.html#method.rhs_implicit)). This API is convenient if you want to stick to pure rust code without using DiffSL and the JIT compiler, but requires you to calculate the gradients of the equations yourself. 34 | - Implementing the [`OdeEquations`](https://docs.rs/diffsol/latest/diffsol/ode_solver/equations/trait.OdeEquations.html) trait allows users to implement the equations on their own structs. This API is the most flexible as it allows users to use custom data structures and code that might not fit within the `OdeBuilder` API. However, it is the most verbose API and requires users to be more familiar with the various Diffsol traits. 35 | -------------------------------------------------------------------------------- /book/src/specify/trait/constant_functions.md: -------------------------------------------------------------------------------- 1 | # Constant functions 2 | 3 | Now we've implemented the rhs function, but how about the initial condition? We can implement the `ConstantOp` trait to specify the initial condition. Since this is quite similar to the `NonLinearOp` trait, we will do it all in one go. 4 | 5 | ```rust,ignore 6 | {{#include ../../../../examples/custom-ode-equations/src/my_init.rs}} 7 | ``` -------------------------------------------------------------------------------- /book/src/specify/trait/linear_functions.md: -------------------------------------------------------------------------------- 1 | # Linear functions 2 | 3 | Naturally, we can also implement the `LinearOp` trait if we want to include a mass matrix in our model. A common use case for implementing this trait is to store the mass matrix in a custom struct, like so: 4 | 5 | ```rust,ignore 6 | {{#include ../../../../examples/custom-ode-equations/src/my_mass.rs}} 7 | ``` 8 | -------------------------------------------------------------------------------- /book/src/specify/trait/non_linear_functions.md: -------------------------------------------------------------------------------- 1 | # Non-linear functions 2 | 3 | To illustrate how to implement a custom problem struct, we will take the familar logistic equation: 4 | 5 | \\[\frac{dy}{dt} = r y (1 - y/K),\\] 6 | 7 | Our goal is to implement a custom struct that can evaluate the rhs function \\(f(y, p, t)\\) and the jacobian multiplied by a vector \\(f'(y, p, t, v)\\). 8 | 9 | To start with, lets define a few linear algebra types that we will use in our function. We need four types: 10 | - `T` is the scalar type (e.g. `f64`) 11 | - `V` is the vector type (e.g. `NalgebraVec`) 12 | - `M` is the matrix type (e.g. `NalgebraMat`) 13 | - `C` is the context type for the rest (e.g. `NalgebraContext`) 14 | 15 | ```rust,ignore 16 | {{#include ../../../../examples/custom-ode-equations/src/common.rs}} 17 | ``` 18 | 19 | Next, we'll define a struct that we'll use to calculate our RHS equations \\(f(y, p, t)\\). We'll pretend that this struct has a reference to a parameter vector \\(p\\) that we'll use to calculate the rhs function. This makes sense since we'll have multiple functions that make up our systems of equations, and they will probably share some parameters. 20 | 21 | ```rust,ignore 22 | {{#include ../../../../examples/custom-ode-equations/src/my_rhs.rs}} 23 | ``` 24 | 25 | Now we will implement the base [`Op`](https://docs.rs/diffsol/latest/diffsol/op/trait.Op.html) trait for our struct. The `Op` trait specifies the types of the vectors and matrices that will be used, as well as the number of states and outputs in the rhs function. 26 | 27 | ```rust,ignore 28 | {{#include ../../../../examples/custom-ode-equations/src/my_rhs_impl_op.rs}} 29 | ``` 30 | 31 | Next we implement the [`NonLinearOp`](https://docs.rs/diffsol/latest/diffsol/op/nonlinear_op/trait.NonLinearOp.html) and [`NonLinearOpJacobian`](https://docs.rs/diffsol/latest/diffsol/op/nonlinear_op/trait.NonLinearOpJacobian.html) trait for our struct. This trait specifies the functions that will be used to evaluate the rhs function and the jacobian multiplied by a vector. 32 | 33 | ```rust,ignore 34 | {{#include ../../../../examples/custom-ode-equations/src/my_rhs_impl_nonlinear.rs}} 35 | ``` 36 | 37 | There we go, all done! This demonstrates how to implement a custom struct to specify a rhs function. 38 | 39 | -------------------------------------------------------------------------------- /book/src/specify/trait/ode_equations_trait.md: -------------------------------------------------------------------------------- 1 | # OdeEquations trait 2 | 3 | While the [`OdeBuilder`](https://docs.rs/diffsol/latest/diffsol/ode_solver/builder/struct.OdeBuilder.html) struct is a convenient way to specify the problem, it may not be suitable in all cases. 4 | Often users will want to provide their own structs that can hold custom data structures and methods for evaluating the right-hand side of the ODE, the jacobian, and other functions. 5 | 6 | ## Traits 7 | 8 | To create your own structs for the ode system, the final goal is to implement the [`OdeEquations`](https://docs.rs/diffsol/latest/diffsol/ode_solver/equations/trait.OdeEquations.html) trait. 9 | When you have done this, you can use the `build_from_eqn` method on the `OdeBuilder` struct to create the problem. 10 | 11 | For each function in your system of equations, you will need to implement the appropriate trait for each function. 12 | - Non-linear functions (rhs, out, root). In this case the [`NonLinearOp`](https://docs.rs/diffsol/latest/diffsol/op/trait.NonLinearOp.html) trait needs to be implemented. 13 | - Linear functions (mass). In this case the [`LinearOp`](https://docs.rs/diffsol/latest/diffsol/op/trait.LinearOp.html) trait needs to be implemented. 14 | - Constant functions (init). In this case the [`ConstantOp`](https://docs.rs/diffsol/latest/diffsol/op/trait.ConstantOp.html) trait needs to be implemented. 15 | 16 | Additionally, each function needs to implement the base operation trait [`Op`](https://docs.rs/diffsol/latest/diffsol/op/trait.Op.html). 17 | 18 | Once you have implemented the appropriate traits for your custom struct, you can use the [`OdeBuilder`](https://docs.rs/diffsol/latest/diffsol/ode_solver/builder/struct.OdeBuilder.html) struct to specify the problem. 19 | 20 | 21 | -------------------------------------------------------------------------------- /book/src/specify/trait/ode_systems.md: -------------------------------------------------------------------------------- 1 | # ODE systems 2 | 3 | So far we've focused on using custom structs to specify individual equations, now we need to put these together to specify the entire system of equations. 4 | 5 | ## Implementing the OdeEquations trait 6 | 7 | To specify the entire system of equations, you need to implement the `Op`, [`OdeEquations`](https://docs.rs/diffsol/latest/diffsol/ode_solver/equations/trait.OdeEquations.html) 8 | and [`OdeEquationsRef`](https://docs.rs/diffsol/latest/diffsol/ode_solver/equations/trait.OdeEquationsRef.html) traits for your struct. 9 | 10 | ## Getting all your traits in order 11 | 12 | The `OdeEquations` trait requires methods that return objects corresponding to the right-hand side function, mass matrix, root function, initial condition, and output functions. 13 | Therefore, you need to already have structs that implement the `NonLinearOp`, `LinearOp`, and `ConstantOp` traits for these functions. For the purposes of this example, we will assume that 14 | you have already implemented these traits for your structs. 15 | 16 | Often, the structs that implement these traits will have to use data defined in the struct that implements the `OdeEquations` trait. For example, they might wish to have a reference to the same parameter vector `p`. Therefore, you will often need to define lifetimes for these structs to ensure that they can access the data they need. 17 | 18 | Note that these struct will need to be lightweight and should not contain a significant amount of data. The data should be stored in the struct that implements the `OdeEquations` trait. This is because these structs will be created and destroyed many times during the course of the simulation (e.g. every time the right-hand side function is called). 19 | 20 | ## My custom ode equations 21 | 22 | We'll define a central struct `MyEquations` that will hold the data for the entire system of equations. In this example, `MyEquations` will own a parameter vector `p` that is used by all the equations. 23 | 24 | ```rust,ignore 25 | {{#include ../../../../examples/custom-ode-equations/src/my_equations.rs}} 26 | ``` 27 | 28 | As with individual functions, we also need to implement the `Op` trait for this struct. 29 | 30 | ```rust,ignore 31 | {{#include ../../../../examples/custom-ode-equations/src/my_equations_impl_op.rs}} 32 | ``` 33 | 34 | ## Implementing the OdeEquations traits 35 | 36 | `OdeEquations` and `OdeEquationsRef` are a pair of traits that together define the system of equations using the individual function structs that we defined earlier. The `OdeEquationsRef` trait is designed to allow individual function structs to be able to reference the data in the main `OdeEquations` struct, allowing them to share data. 37 | 38 | 39 | ```rust,ignore 40 | {{#include ../../../../examples/custom-ode-equations/src/my_equations_impl_ode_equations.rs}} 41 | ``` 42 | 43 | ## Creating the problem 44 | 45 | Now that we have our custom `OdeEquations` struct, we can use it in an `OdeBuilder` to create the problem. 46 | 47 | ```rust,ignore 48 | {{#include ../../../../examples/custom-ode-equations/src/build.rs}} 49 | ``` -------------------------------------------------------------------------------- /book/theme/head.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /diffsol/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "diffsol" 3 | version = "0.6.0" 4 | edition.workspace = true 5 | description = "A library for solving ordinary differential equations (ODEs) in Rust." 6 | license.workspace = true 7 | authors.workspace = true 8 | repository.workspace = true 9 | readme.workspace = true 10 | 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [features] 15 | default = ["nalgebra", "faer"] 16 | faer = [] 17 | nalgebra = [] 18 | cuda = ["dep:cudarc"] 19 | sundials = ["suitesparse_sys", "bindgen", "cc"] 20 | suitesparse = ["suitesparse_sys"] 21 | diffsl = [] 22 | diffsl-cranelift = ["diffsl/cranelift", "diffsl"] 23 | diffsl-llvm = [] 24 | diffsl-llvm15 = ["diffsl/llvm15-0", "diffsl", "diffsl-llvm"] 25 | diffsl-llvm16 = ["diffsl/llvm16-0", "diffsl", "diffsl-llvm"] 26 | diffsl-llvm17 = ["diffsl/llvm17-0", "diffsl", "diffsl-llvm"] 27 | diffsl-llvm18 = ["diffsl/llvm18-1", "diffsl", "diffsl-llvm"] 28 | 29 | [dependencies] 30 | nalgebra = { workspace = true } 31 | faer = { workspace = true } 32 | nalgebra-sparse = { version = "0.10", features = ["io"] } 33 | num-traits = "0.2.17" 34 | serde = { version = "1.0.219", features = ["derive"] } 35 | diffsl = { package = "diffsl", version = "0.5.1", optional = true, features = ["rayon"] } 36 | petgraph = "0.8.1" 37 | suitesparse_sys = { version = "0.1.3", optional = true } 38 | thiserror = "2.0.12" 39 | faer-traits = "0.22.1" 40 | cudarc = { workspace = true, optional = true, default-features = false, features = [ 41 | "cuda-version-from-build-system", "cusolver", 42 | "dynamic-linking", 43 | "std", 44 | "cublas", 45 | "cublaslt", 46 | "curand", 47 | "driver", 48 | "runtime", 49 | "nvrtc", 50 | ] } 51 | 52 | [dev-dependencies] 53 | insta = { version = "1.43.1", features = ["yaml"] } 54 | criterion = { version = "0.5.1" } 55 | skeptic = "0.13.7" 56 | 57 | [build-dependencies] 58 | bindgen = { version = "0.71.1", optional = true } 59 | cc = { version = "1.2.22", optional = true } 60 | 61 | [[bench]] 62 | name = "ode_solvers" 63 | harness = false 64 | 65 | [package.metadata.docs.rs] 66 | features = ["diffsl-llvm15", "diffsl-cranelift", "rayon"] 67 | 68 | -------------------------------------------------------------------------------- /diffsol/benches/sundials/cvRoberts_block_klu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #if SUNDIALS_VERSION_MAJOR > 5 3 | #include "cvRoberts_block_klu_v6.c" 4 | #else 5 | #include "cvRoberts_block_klu_v5.c" 6 | #endif -------------------------------------------------------------------------------- /diffsol/benches/sundials/idaFoodWeb_bnd_10.c: -------------------------------------------------------------------------------- 1 | #include "mangle.h" 2 | #define SIZE 10 3 | #define FUNC_NAME MANGLE(idaFoodWeb_bnd, SIZE) 4 | 5 | #include 6 | #if SUNDIALS_VERSION_MAJOR > 5 7 | #include "idaFoodWeb_bnd_v6.c" 8 | #else 9 | #include "idaFoodWeb_bnd_v5.c" 10 | #endif 11 | 12 | -------------------------------------------------------------------------------- /diffsol/benches/sundials/idaFoodWeb_bnd_20.c: -------------------------------------------------------------------------------- 1 | #include "mangle.h" 2 | #define SIZE 20 3 | #define FUNC_NAME MANGLE(idaFoodWeb_bnd, SIZE) 4 | 5 | #include 6 | #if SUNDIALS_VERSION_MAJOR > 5 7 | #include "idaFoodWeb_bnd_v6.c" 8 | #else 9 | #include "idaFoodWeb_bnd_v5.c" 10 | #endif 11 | 12 | 13 | -------------------------------------------------------------------------------- /diffsol/benches/sundials/idaFoodWeb_bnd_30.c: -------------------------------------------------------------------------------- 1 | #include "mangle.h" 2 | #define SIZE 30 3 | #define FUNC_NAME MANGLE(idaFoodWeb_bnd, SIZE) 4 | 5 | #include 6 | #if SUNDIALS_VERSION_MAJOR > 5 7 | #include "idaFoodWeb_bnd_v6.c" 8 | #else 9 | #include "idaFoodWeb_bnd_v5.c" 10 | #endif 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /diffsol/benches/sundials/idaFoodWeb_bnd_5.c: -------------------------------------------------------------------------------- 1 | #include "mangle.h" 2 | #define SIZE 5 3 | #define FUNC_NAME MANGLE(idaFoodWeb_bnd, SIZE) 4 | 5 | #include 6 | #if SUNDIALS_VERSION_MAJOR > 5 7 | #include "idaFoodWeb_bnd_v6.c" 8 | #else 9 | #include "idaFoodWeb_bnd_v5.c" 10 | #endif 11 | -------------------------------------------------------------------------------- /diffsol/benches/sundials/idaHeat2d_bnd_10.c: -------------------------------------------------------------------------------- 1 | #include "mangle.h" 2 | #define SIZE 10 3 | #define FUNC_NAME MANGLE(idaHeat2d_klu, SIZE) 4 | 5 | #include 6 | #if SUNDIALS_VERSION_MAJOR > 5 7 | #include "idaHeat2d_klu_v6.c" 8 | #else 9 | #include "idaHeat2d_klu_v5.c" 10 | #endif 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /diffsol/benches/sundials/idaHeat2d_bnd_20.c: -------------------------------------------------------------------------------- 1 | #include "mangle.h" 2 | #define SIZE 20 3 | #define FUNC_NAME MANGLE(idaHeat2d_klu, SIZE) 4 | 5 | #include 6 | #if SUNDIALS_VERSION_MAJOR > 5 7 | #include "idaHeat2d_klu_v6.c" 8 | #else 9 | #include "idaHeat2d_klu_v5.c" 10 | #endif 11 | 12 | 13 | -------------------------------------------------------------------------------- /diffsol/benches/sundials/idaHeat2d_bnd_30.c: -------------------------------------------------------------------------------- 1 | #include "mangle.h" 2 | #define SIZE 30 3 | #define FUNC_NAME MANGLE(idaHeat2d_klu, SIZE) 4 | 5 | #include 6 | #if SUNDIALS_VERSION_MAJOR > 5 7 | #include "idaHeat2d_klu_v6.c" 8 | #else 9 | #include "idaHeat2d_klu_v5.c" 10 | #endif 11 | 12 | 13 | -------------------------------------------------------------------------------- /diffsol/benches/sundials/idaHeat2d_bnd_5.c: -------------------------------------------------------------------------------- 1 | #include "mangle.h" 2 | #define SIZE 5 3 | #define FUNC_NAME MANGLE(idaHeat2d_klu, SIZE) 4 | 5 | #include 6 | #if SUNDIALS_VERSION_MAJOR > 5 7 | #include "idaHeat2d_klu_v6.c" 8 | #else 9 | #include "idaHeat2d_klu_v5.c" 10 | #endif 11 | 12 | 13 | -------------------------------------------------------------------------------- /diffsol/benches/sundials/idaRoberts_dns.c: -------------------------------------------------------------------------------- 1 | #include 2 | #if SUNDIALS_VERSION_MAJOR > 5 3 | #include "idaRoberts_dns_v6.c" 4 | #else 5 | #include "idaRoberts_dns_v5.c" 6 | #endif -------------------------------------------------------------------------------- /diffsol/benches/sundials/mangle.h: -------------------------------------------------------------------------------- 1 | #define MANGLE_HELPER(A, B) A##_##B 2 | #define MANGLE(A, B) MANGLE_HELPER(A, B) -------------------------------------------------------------------------------- /diffsol/benches/sundials_benches.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_int; 2 | 3 | extern "C" { 4 | pub fn cvRoberts_block_klu(ngroups: i32) -> c_int; 5 | pub fn idaFoodWeb_bnd_5() -> c_int; 6 | pub fn idaFoodWeb_bnd_10() -> c_int; 7 | pub fn idaFoodWeb_bnd_20() -> c_int; 8 | pub fn idaFoodWeb_bnd_30() -> c_int; 9 | pub fn idaHeat2d_klu_5() -> c_int; 10 | pub fn idaHeat2d_klu_10() -> c_int; 11 | pub fn idaHeat2d_klu_20() -> c_int; 12 | pub fn idaHeat2d_klu_30() -> c_int; 13 | pub fn idaRoberts_dns() -> c_int; 14 | } 15 | -------------------------------------------------------------------------------- /diffsol/src/context/faer.rs: -------------------------------------------------------------------------------- 1 | use faer::{get_global_parallelism, Par}; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub struct FaerContext { 5 | par: Par, 6 | } 7 | 8 | impl Default for FaerContext { 9 | fn default() -> Self { 10 | Self { 11 | par: get_global_parallelism(), 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /diffsol/src/context/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{DefaultDenseMatrix, Matrix, Vector}; 2 | 3 | #[cfg(feature = "cuda")] 4 | pub mod cuda; 5 | 6 | #[cfg(feature = "nalgebra")] 7 | pub mod nalgebra; 8 | 9 | #[cfg(feature = "faer")] 10 | pub mod faer; 11 | 12 | /// defines the current execution and allocation context of an operator / vector / matrix 13 | /// for example: 14 | /// - threading model (e.g. single-threaded, multi-threaded, GPU) 15 | /// - custom allocators, host/device memory 16 | /// - etc. 17 | /// 18 | /// It will generally be the case that all the operators / vectors / matrices for the current ode problem 19 | /// share the same context 20 | pub trait Context: Clone + Default { 21 | fn vector_from_element>(&self, len: usize, value: V::T) -> V { 22 | V::from_element(len, value, self.clone()) 23 | } 24 | fn vector_from_vec>(&self, vec: Vec) -> V { 25 | V::from_vec(vec, self.clone()) 26 | } 27 | fn vector_zeros>(&self, len: usize) -> V { 28 | V::zeros(len, self.clone()) 29 | } 30 | fn dense_mat_zeros + DefaultDenseMatrix>( 31 | &self, 32 | rows: usize, 33 | cols: usize, 34 | ) -> ::M { 35 | <::M as Matrix>::zeros(rows, cols, self.clone()) 36 | } 37 | } 38 | 39 | impl Context for T {} 40 | -------------------------------------------------------------------------------- /diffsol/src/context/nalgebra.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq)] 2 | pub struct NalgebraContext; 3 | 4 | impl Default for NalgebraContext { 5 | fn default() -> Self { 6 | Self 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/all.cu: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | #include "mat_from_diagonal.cu" 3 | #include "mat_get_diagonal.cu" 4 | #include "mat_scale_add_assign.cu" 5 | #include "mat_set_column.cu" 6 | #include "mat_set_data_with_indices.cu" 7 | #include "vec_add_assign_rhs.cu" 8 | #include "vec_add_assign.cu" 9 | #include "vec_add.cu" 10 | #include "vec_assign_at_indices.cu" 11 | #include "vec_copy_from_indices.cu" 12 | #include "vec_copy.cu" 13 | #include "vec_div_assign.cu" 14 | #include "vec_fill.cu" 15 | #include "vec_gather.cu" 16 | #include "vec_mul_assign_scalar.cu" 17 | #include "vec_mul_assign.cu" 18 | #include "vec_mul_scalar.cu" 19 | #include "vec_root_finding.cu" 20 | #include "vec_scatter.cu" 21 | #include "vec_squared_norm.cu" 22 | #include "vec_sub_assign_rhs.cu" 23 | #include "vec_sub_assign.cu" 24 | #include "vec_sub.cu" 25 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/mat_from_diagonal.cu: -------------------------------------------------------------------------------- 1 | // copies from a diagonal vector to a matrix (self, initialised with zeros, column-major order) 2 | __global__ 3 | void mat_from_diagonal_f64(double* self_zeros, 4 | const double* __restrict__ diagonal_vector, 5 | int nrows) { 6 | int tid = blockIdx.x * blockDim.x + threadIdx.x; 7 | 8 | for (int i = tid; i < nrows; i += blockDim.x * gridDim.x) { 9 | int idx = i * nrows + i; // column-major index 10 | self_zeros[idx] = diagonal_vector[i]; 11 | } 12 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/mat_get_diagonal.cu: -------------------------------------------------------------------------------- 1 | // copies from a diagonal vector to a matrix (self, initialised with zeros, column-major order) 2 | __global__ 3 | void mat_get_diagonal_f64(const double* __restrict__ self, 4 | double* diagonal_vector, 5 | int nrows) { 6 | int tid = blockIdx.x * blockDim.x + threadIdx.x; 7 | 8 | for (int i = tid; i < nrows; i += blockDim.x * gridDim.x) { 9 | int idx = i * nrows + i; // column-major index 10 | diagonal_vector[i] = self[idx]; 11 | } 12 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/mat_scale_add_assign.cu: -------------------------------------------------------------------------------- 1 | /// Perform the assignment self = x + beta * y where x and y are matrices and beta is a scalar 2 | __global__ void mat_scale_add_assign_f64(double* self, 3 | const double* __restrict__ x, 4 | const double* __restrict__ y, 5 | double beta, 6 | int n) { 7 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 8 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 9 | self[i] = x[i] + beta * y[i]; 10 | } 11 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/mat_set_column.cu: -------------------------------------------------------------------------------- 1 | // copies from a diagonal vector to a matrix (self, initialised with zeros, column-major order) 2 | __global__ 3 | void mat_set_column_f64(double* self, 4 | const double* __restrict__ column_vector, 5 | int column_index, 6 | int nrows) { 7 | int tid = blockIdx.x * blockDim.x + threadIdx.x; 8 | 9 | for (int i = tid; i < nrows; i += blockDim.x * gridDim.x) { 10 | int idx = column_index * nrows + i; // column-major index 11 | self[idx] = column_vector[i]; 12 | } 13 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/mat_set_data_with_indices.cu: -------------------------------------------------------------------------------- 1 | // assign the values in the `data` vector to the matrix at the indices in `dst_indices` from the indices in `src_indices` 2 | // dst_index can be obtained using the `get_index` method on the Sparsity type 3 | // - for dense matrices, the dst_index is the data index in column-major order 4 | __global__ 5 | void mat_set_data_with_indices_f64(double* self, 6 | const double* __restrict__ other, 7 | const int* __restrict__ dst_indices, 8 | const int* __restrict__ src_indices, 9 | int n) { 10 | int tid = blockIdx.x * blockDim.x + threadIdx.x; 11 | 12 | for (int i = tid; i < n; i += blockDim.x * gridDim.x) { 13 | int dst_idx = dst_indices[i]; 14 | int src_idx = src_indices[i]; 15 | self[dst_idx] = other[src_idx]; 16 | } 17 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_add.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_add_f64(double* lhs, double* rhs, double *ret, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | ret[i] = lhs[i] + rhs[i]; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_add_assign.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_add_assign_f64(double* lhs, double* rhs, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | lhs[i] += rhs[i]; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_add_assign_rhs.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_add_assign_rhs_f64(double* lhs, double* rhs, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | rhs[i] += lhs[i]; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_assign_at_indices.cu: -------------------------------------------------------------------------------- 1 | 2 | /// assign `value` to the elements of `self` at the indices specified by `indices` 3 | /// i.e. `self[indices[i]] = value` for all i 4 | __global__ 5 | void vec_assign_at_indices_f64(double* self, 6 | const int* __restrict__ indices, 7 | double value, 8 | int n) { 9 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 10 | 11 | // Grid-stride loop for flexibility 12 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 13 | self[indices[i]] = value; 14 | } 15 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_copy.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_copy_f64(double* lhs, const double* rhs, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | lhs[i] = rhs[i]; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_copy_from_indices.cu: -------------------------------------------------------------------------------- 1 | 2 | /// copy from `other` at the indices specified by `indices` 3 | /// generaly `self` and `other` have the same length 4 | /// i.e. `self[indices[i]] = other[indices[i]]` for all i 5 | __global__ 6 | void vec_copy_from_indices_f64(double* self, 7 | const double* __restrict__ other, 8 | const int* __restrict__ indices, 9 | int n) { 10 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 11 | 12 | // Grid-stride loop 13 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 14 | int target_idx = indices[i]; 15 | self[target_idx] = other[target_idx]; 16 | } 17 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_div_assign.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_div_assign_f64(double* lhs, double* rhs, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | lhs[i] /= rhs[i]; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_fill.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_fill_f64(double* lhs, double value, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | lhs[i] = value; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_gather.cu: -------------------------------------------------------------------------------- 1 | /// gather values from `other` at the indices specified by `indices` 2 | /// i.e. `self[i] = other[indices[i]]` for all i 3 | __global__ 4 | void vec_gather_f64(double* self, 5 | const double* __restrict__ other, 6 | const int* __restrict__ indices, 7 | int n) { 8 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 9 | 10 | // Grid-stride loop for flexibility 11 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 12 | int src_idx = indices[i]; 13 | self[i] = other[src_idx]; 14 | } 15 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_mul_assign.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_mul_assign_f64(double* lhs, double* rhs, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | lhs[i] *= rhs[i]; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_mul_assign_scalar.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_mul_assign_scalar_f64(double* vec, double scalar, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | vec[i] *= scalar; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_mul_scalar.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_mul_scalar_f64(double* vec, double scalar, double* res, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | res[i] = vec[i] * scalar; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_root_finding.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /// given two vectors `g0=self` and `g1`, return: 6 | /// - `true` if a root is found in g1 (i.e. g1_i == 0) 7 | /// - for all values of i where a zero crossing is found (i.e. g0_i * g1_i < 0), return: 8 | /// - max_i(abs(g1_i / (g1_i - g0_i))), 0 otherwise 9 | /// - the index i at the maximum value, -1 otherwise 10 | __global__ 11 | void vec_root_finding_f64(const double* __restrict__ g0, 12 | const double* __restrict__ g1, 13 | int n, 14 | int* root_flag, 15 | double* max_vals, 16 | int* max_idxs) { 17 | extern __shared__ double sdata[]; 18 | 19 | double* svals = sdata; 20 | int* sidxs = (int*)&svals[blockDim.x]; 21 | 22 | int tid = threadIdx.x; 23 | int global_idx = blockIdx.x * blockDim.x + threadIdx.x; 24 | int stride = blockDim.x * gridDim.x; 25 | 26 | double local_max = 0.0; 27 | int local_index = -1; 28 | 29 | // Grid-stride loop 30 | for (int i = global_idx; i < n; i += stride) { 31 | double v0 = g0[i]; 32 | double v1 = g1[i]; 33 | 34 | if (v1 == 0.0) { 35 | atomicExch(root_flag, 1); 36 | } 37 | 38 | if (v0 * v1 < 0.0) { 39 | double val = fabs(v1 / (v1 - v0)); 40 | if (val > local_max) { 41 | local_max = val; 42 | local_index = i; 43 | } 44 | } 45 | } 46 | 47 | // Store local results in shared memory 48 | svals[tid] = local_max; 49 | sidxs[tid] = local_index; 50 | __syncthreads(); 51 | 52 | // In-block parallel reduction for max val and corresponding index 53 | for (int s = blockDim.x / 2; s > 0; s >>= 1) { 54 | if (tid < s) { 55 | if (svals[tid] < svals[tid + s]) { 56 | svals[tid] = svals[tid + s]; 57 | sidxs[tid] = sidxs[tid + s]; 58 | } 59 | } 60 | __syncthreads(); 61 | } 62 | 63 | if (tid == 0) { 64 | max_vals[blockIdx.x] = svals[0]; 65 | max_idxs[blockIdx.x] = sidxs[0]; 66 | } 67 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_scatter.cu: -------------------------------------------------------------------------------- 1 | /// scatter values from `self` to `other` at the indices specified by `indices` 2 | /// i.e. `other[indices[i]] = self[i]` for all i 3 | __global__ 4 | void vec_scatter_f64(const double* __restrict__ self, 5 | const int* __restrict__ indices, 6 | double* other, 7 | int num_indices) { 8 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 9 | 10 | // Grid-stride loop 11 | for (int i = idx; i < num_indices; i += blockDim.x * gridDim.x) { 12 | other[indices[i]] = self[i]; 13 | } 14 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_squared_norm.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // CUDA kernel to compute the squared norm of a vector 5 | // using a relative and absolute tolerance 6 | // The squared norm is computed as: 7 | // sum_i (y_i / (|y0_i| * rel_tol + abs_tol_i))^2 8 | // where y0 is the reference vector, abs_tol is the absolute tolerance vector, 9 | // and rel_tol is the relative tolerance scalar. 10 | // The result is stored in the partial_sums array, which is assumed to be large enough 11 | // to hold the results of all blocks. 12 | __global__ 13 | void vec_squared_norm_f64(const double* __restrict__ y, 14 | const double* __restrict__ y0, 15 | const double* __restrict__ abs_tol, 16 | double rel_tol, 17 | int n, 18 | double* partial_sums) { 19 | extern __shared__ double sdata[]; 20 | 21 | int tid = threadIdx.x; 22 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 23 | double local_sum = 0.0; 24 | 25 | // Grid-stride loop 26 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 27 | double denom = fabs(y0[i]) * rel_tol + abs_tol[i]; 28 | double ratio = y[i] / denom; 29 | local_sum += ratio * ratio; 30 | } 31 | 32 | // Store local sum in shared memory 33 | sdata[tid] = local_sum; 34 | __syncthreads(); 35 | 36 | // Parallel reduction in shared memory 37 | for (int s = blockDim.x / 2; s > 0; s >>= 1) { 38 | if (tid < s) 39 | sdata[tid] += sdata[tid + s]; 40 | __syncthreads(); 41 | } 42 | 43 | // Write result of this block to global memory 44 | if (tid == 0) 45 | partial_sums[blockIdx.x] = sdata[0]; 46 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_sub.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_sub_f64(double* lhs, double* rhs, double *ret, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | ret[i] = lhs[i] - rhs[i]; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_sub_assign.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_sub_assign_f64(double* lhs, double* rhs, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | lhs[i] -= rhs[i]; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/cuda_kernels/vec_sub_assign_rhs.cu: -------------------------------------------------------------------------------- 1 | __global__ void vec_sub_assign_rhs_f64(double* lhs, double* rhs, int n) { 2 | int idx = blockIdx.x * blockDim.x + threadIdx.x; 3 | for (int i = idx; i < n; i += blockDim.x * gridDim.x) { 4 | rhs[i] = lhs[i] - rhs[i]; 5 | } 6 | } -------------------------------------------------------------------------------- /diffsol/src/jacobian/coloring.rs: -------------------------------------------------------------------------------- 1 | // Translated from https://github.com/JuliaDiff/SparseDiffTools.jl under an MIT license 2 | 3 | use petgraph::graph::NodeIndex; 4 | 5 | use super::graph::Graph; 6 | 7 | /// Returns a vector of rows where each row contains 8 | /// a vector of its column indices. 9 | fn cols_by_rows(non_zeros: &[(usize, usize)]) -> Vec> { 10 | let nrows = if non_zeros.is_empty() { 11 | 0 12 | } else { 13 | *non_zeros.iter().map(|(i, _j)| i).max().unwrap() + 1 14 | }; 15 | let mut cols_by_rows = vec![Vec::new(); nrows]; 16 | for (i, j) in non_zeros.iter() { 17 | cols_by_rows[*i].push(*j); 18 | } 19 | cols_by_rows 20 | } 21 | 22 | /// A utility function to generate a graph from input sparse matrix, columns are represented 23 | /// with vertices and 2 vertices are connected with an edge only if the two columns are mutually 24 | /// orthogonal. 25 | /// 26 | /// non_zeros: A vector of indices (i, j) where i is the row index and j is the column index 27 | pub fn nonzeros2graph(non_zeros: &[(usize, usize)], ncols: usize) -> Graph { 28 | let cols_by_rows = cols_by_rows(non_zeros); 29 | let mut edges = Vec::new(); 30 | for (cur_row, cur_col) in non_zeros.iter() { 31 | if !cols_by_rows[*cur_row].is_empty() { 32 | for next_col in cols_by_rows[*cur_row].iter() { 33 | if next_col < cur_col { 34 | edges.push((*cur_col, *next_col)); 35 | } 36 | } 37 | } 38 | } 39 | let mut graph = Graph::with_capacity(ncols, edges.len()); 40 | for _ in 0..ncols { 41 | graph.add_node(()); 42 | } 43 | for (i, j) in edges.iter() { 44 | graph.add_edge(NodeIndex::new(*i), NodeIndex::new(*j), ()); 45 | } 46 | graph 47 | } 48 | -------------------------------------------------------------------------------- /diffsol/src/jacobian/graph.rs: -------------------------------------------------------------------------------- 1 | pub type Graph = petgraph::graph::Graph<(), (), petgraph::Directed>; 2 | -------------------------------------------------------------------------------- /diffsol/src/jacobian/greedy_coloring.rs: -------------------------------------------------------------------------------- 1 | // Translated from https://github.com/JuliaDiff/SparseDiffTools.jl under an MIT license 2 | 3 | use petgraph::graph::NodeIndex; 4 | 5 | use super::graph::Graph; 6 | 7 | /// Greedy graph coloring algorithm. 8 | /// 9 | /// Find a coloring of a given input graph such that 10 | /// no two vertices connected by an edge have the same 11 | /// color using greedy approach. The number of colors 12 | /// used may be equal or greater than the chromatic 13 | /// number `χ(G)` of the graph. 14 | pub fn color_graph_greedy(graph: &Graph) -> Vec { 15 | let mut result = vec![0; graph.node_count()]; 16 | result[0] = 1; 17 | let mut available = vec![false; graph.node_count()]; 18 | 19 | for ii in 1..graph.node_count() { 20 | for j in graph.neighbors(NodeIndex::new(ii)) { 21 | if result[j.index()] != 0 { 22 | available[result[j.index()] - 1] = true; 23 | } 24 | } 25 | for (i, a) in available.iter().enumerate() { 26 | if !a { 27 | result[ii] = i + 1; 28 | break; 29 | } 30 | } 31 | available.iter_mut().for_each(|x| *x = false); 32 | } 33 | result 34 | } 35 | -------------------------------------------------------------------------------- /diffsol/src/linear_solver/cuda/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod lu; 2 | -------------------------------------------------------------------------------- /diffsol/src/linear_solver/faer/lu.rs: -------------------------------------------------------------------------------- 1 | use crate::FaerContext; 2 | use crate::{error::LinearSolverError, linear_solver_error}; 3 | 4 | use crate::{ 5 | error::DiffsolError, linear_solver::LinearSolver, FaerMat, FaerVec, Matrix, 6 | NonLinearOpJacobian, Scalar, 7 | }; 8 | 9 | use faer::{linalg::solvers::FullPivLu, linalg::solvers::Solve}; 10 | /// A [LinearSolver] that uses the LU decomposition in the [`faer`](https://github.com/sarah-ek/faer-rs) library to solve the linear system. 11 | pub struct LU 12 | where 13 | T: Scalar, 14 | { 15 | lu: Option>, 16 | matrix: Option>, 17 | } 18 | 19 | impl Default for LU 20 | where 21 | T: Scalar, 22 | { 23 | fn default() -> Self { 24 | Self { 25 | lu: None, 26 | matrix: None, 27 | } 28 | } 29 | } 30 | 31 | impl LinearSolver> for LU { 32 | fn set_linearisation, M = FaerMat>>( 33 | &mut self, 34 | op: &C, 35 | x: &FaerVec, 36 | t: T, 37 | ) { 38 | let matrix = self.matrix.as_mut().expect("Matrix not set"); 39 | op.jacobian_inplace(x, t, matrix); 40 | self.lu = Some(matrix.data.full_piv_lu()); 41 | } 42 | 43 | fn solve_in_place(&self, x: &mut FaerVec) -> Result<(), DiffsolError> { 44 | if self.lu.is_none() { 45 | return Err(linear_solver_error!(LuNotInitialized))?; 46 | } 47 | let lu = self.lu.as_ref().unwrap(); 48 | lu.solve_in_place(x.data.as_mut()); 49 | Ok(()) 50 | } 51 | 52 | fn set_problem< 53 | C: NonLinearOpJacobian, M = FaerMat, C = FaerContext>, 54 | >( 55 | &mut self, 56 | op: &C, 57 | ) { 58 | let ncols = op.nstates(); 59 | let nrows = op.nout(); 60 | let matrix = 61 | C::M::new_from_sparsity(nrows, ncols, op.jacobian_sparsity(), op.context().clone()); 62 | self.matrix = Some(matrix); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /diffsol/src/linear_solver/faer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod lu; 2 | pub mod sparse_lu; 3 | -------------------------------------------------------------------------------- /diffsol/src/linear_solver/faer/sparse_lu.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{DiffsolError, LinearSolverError}, 3 | linear_solver::LinearSolver, 4 | linear_solver_error, 5 | scalar::IndexType, 6 | FaerContext, FaerSparseMat, FaerVec, Matrix, NonLinearOpJacobian, Scalar, 7 | }; 8 | 9 | use faer::{ 10 | linalg::solvers::Solve, 11 | reborrow::Reborrow, 12 | sparse::linalg::{solvers::Lu, solvers::SymbolicLu}, 13 | }; 14 | 15 | /// A [LinearSolver] that uses the LU decomposition in the [`faer`](https://github.com/sarah-ek/faer-rs) library to solve the linear system. 16 | pub struct FaerSparseLU 17 | where 18 | T: Scalar, 19 | { 20 | lu: Option>, 21 | lu_symbolic: Option>, 22 | matrix: Option>, 23 | } 24 | 25 | impl Default for FaerSparseLU 26 | where 27 | T: Scalar, 28 | { 29 | fn default() -> Self { 30 | Self { 31 | lu: None, 32 | matrix: None, 33 | lu_symbolic: None, 34 | } 35 | } 36 | } 37 | 38 | impl LinearSolver> for FaerSparseLU { 39 | fn set_linearisation, M = FaerSparseMat>>( 40 | &mut self, 41 | op: &C, 42 | x: &FaerVec, 43 | t: T, 44 | ) { 45 | let matrix = self.matrix.as_mut().expect("Matrix not set"); 46 | op.jacobian_inplace(x, t, matrix); 47 | self.lu = Some( 48 | Lu::try_new_with_symbolic(self.lu_symbolic.as_ref().unwrap().clone(), matrix.data.rb()) 49 | .expect("Failed to factorise matrix"), 50 | ) 51 | } 52 | 53 | fn solve_in_place(&self, x: &mut FaerVec) -> Result<(), DiffsolError> { 54 | if self.lu.is_none() { 55 | return Err(linear_solver_error!(LuNotInitialized))?; 56 | } 57 | let lu = self.lu.as_ref().unwrap(); 58 | lu.solve_in_place(&mut x.data); 59 | Ok(()) 60 | } 61 | 62 | fn set_problem< 63 | C: NonLinearOpJacobian, M = FaerSparseMat, C = FaerContext>, 64 | >( 65 | &mut self, 66 | op: &C, 67 | ) { 68 | let ncols = op.nstates(); 69 | let nrows = op.nout(); 70 | let matrix = 71 | C::M::new_from_sparsity(nrows, ncols, op.jacobian_sparsity(), op.context().clone()); 72 | self.matrix = Some(matrix); 73 | self.lu_symbolic = Some( 74 | SymbolicLu::try_new(self.matrix.as_ref().unwrap().data.symbolic()) 75 | .expect("Failed to create symbolic LU"), 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /diffsol/src/linear_solver/nalgebra/lu.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::Dyn; 2 | 3 | use crate::{ 4 | error::{DiffsolError, LinearSolverError}, 5 | linear_solver_error, 6 | matrix::dense_nalgebra_serial::NalgebraMat, 7 | LinearSolver, Matrix, NalgebraContext, NalgebraVec, NonLinearOpJacobian, Scalar, 8 | }; 9 | 10 | /// A [LinearSolver] that uses the LU decomposition in the [`nalgebra` library](https://nalgebra.org/) to solve the linear system. 11 | #[derive(Clone)] 12 | pub struct LU 13 | where 14 | T: Scalar, 15 | { 16 | matrix: Option>, 17 | lu: Option>, 18 | } 19 | 20 | impl Default for LU 21 | where 22 | T: Scalar, 23 | { 24 | fn default() -> Self { 25 | Self { 26 | lu: None, 27 | matrix: None, 28 | } 29 | } 30 | } 31 | 32 | impl LinearSolver> for LU { 33 | fn solve_in_place(&self, state: &mut NalgebraVec) -> Result<(), DiffsolError> { 34 | if self.lu.is_none() { 35 | return Err(linear_solver_error!(LuNotInitialized))?; 36 | } 37 | let lu = self.lu.as_ref().unwrap(); 38 | match lu.solve_mut(&mut state.data) { 39 | true => Ok(()), 40 | false => Err(linear_solver_error!(LuSolveFailed))?, 41 | } 42 | } 43 | 44 | fn set_linearisation, M = NalgebraMat>>( 45 | &mut self, 46 | op: &C, 47 | x: &NalgebraVec, 48 | t: T, 49 | ) { 50 | let matrix = self.matrix.as_mut().expect("Matrix not set"); 51 | op.jacobian_inplace(x, t, matrix); 52 | self.lu = Some(matrix.data.clone().lu()); 53 | } 54 | 55 | fn set_problem< 56 | C: NonLinearOpJacobian, M = NalgebraMat, C = NalgebraContext>, 57 | >( 58 | &mut self, 59 | op: &C, 60 | ) { 61 | let ncols = op.nstates(); 62 | let nrows = op.nout(); 63 | let matrix = 64 | C::M::new_from_sparsity(nrows, ncols, op.jacobian_sparsity(), op.context().clone()); 65 | self.matrix = Some(matrix); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /diffsol/src/linear_solver/nalgebra/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod lu; 2 | -------------------------------------------------------------------------------- /diffsol/src/linear_solver/suitesparse/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod klu; 2 | -------------------------------------------------------------------------------- /diffsol/src/matrix/default_solver.rs: -------------------------------------------------------------------------------- 1 | use crate::LinearSolver; 2 | 3 | use super::Matrix; 4 | 5 | pub trait DefaultSolver: Matrix { 6 | type LS: LinearSolver + Default; 7 | fn default_solver() -> Self::LS { 8 | Self::LS::default() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /diffsol/src/matrix/utils.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_matrix_common { 2 | ($mat:ty, $vec:ty, $con:ty, $in:ty) => { 3 | impl MatrixCommon for $mat { 4 | type T = T; 5 | type V = $vec; 6 | type C = $con; 7 | type Inner = $in; 8 | 9 | fn nrows(&self) -> IndexType { 10 | self.data.nrows() 11 | } 12 | fn ncols(&self) -> IndexType { 13 | self.data.ncols() 14 | } 15 | fn inner(&self) -> &Self::Inner { 16 | &self.data 17 | } 18 | } 19 | }; 20 | } 21 | 22 | pub(crate) use impl_matrix_common; 23 | 24 | macro_rules! impl_matrix_common_ref { 25 | ($mat:ty, $vec:ty, $con:ty, $in:ty) => { 26 | impl<'a, T: Scalar> MatrixCommon for $mat { 27 | type T = T; 28 | type V = $vec; 29 | type C = $con; 30 | type Inner = $in; 31 | 32 | fn nrows(&self) -> IndexType { 33 | self.data.nrows() 34 | } 35 | fn ncols(&self) -> IndexType { 36 | self.data.ncols() 37 | } 38 | fn inner(&self) -> &Self::Inner { 39 | &self.data 40 | } 41 | } 42 | }; 43 | } 44 | 45 | pub(crate) use impl_matrix_common_ref; 46 | 47 | macro_rules! impl_add { 48 | ($lhs:ty, $rhs:ty, $out:ty) => { 49 | impl Add<$rhs> for $lhs { 50 | type Output = $out; 51 | 52 | fn add(self, rhs: $rhs) -> Self::Output { 53 | Self::Output { 54 | data: self.data + &rhs.data, 55 | context: self.context, 56 | } 57 | } 58 | } 59 | }; 60 | } 61 | pub(crate) use impl_add; 62 | 63 | macro_rules! impl_sub { 64 | ($lhs:ty, $rhs:ty, $out:ty) => { 65 | impl Sub<$rhs> for $lhs { 66 | type Output = $out; 67 | 68 | fn sub(self, rhs: $rhs) -> Self::Output { 69 | Self::Output { 70 | data: self.data - &rhs.data, 71 | context: self.context, 72 | } 73 | } 74 | } 75 | }; 76 | } 77 | pub(crate) use impl_sub; 78 | 79 | macro_rules! impl_add_assign { 80 | ($lhs:ty, $rhs:ty) => { 81 | impl AddAssign<$rhs> for $lhs { 82 | fn add_assign(&mut self, rhs: $rhs) { 83 | self.data += &rhs.data; 84 | } 85 | } 86 | }; 87 | } 88 | pub(crate) use impl_add_assign; 89 | 90 | macro_rules! impl_sub_assign { 91 | ($lhs:ty, $rhs:ty) => { 92 | impl SubAssign<$rhs> for $lhs { 93 | fn sub_assign(&mut self, rhs: $rhs) { 94 | self.data -= &rhs.data; 95 | } 96 | } 97 | }; 98 | } 99 | pub(crate) use impl_sub_assign; 100 | 101 | macro_rules! impl_index { 102 | ($lhs:ty) => { 103 | impl Index<(IndexType, IndexType)> for $lhs { 104 | type Output = T; 105 | fn index(&self, index: (IndexType, IndexType)) -> &T { 106 | &self.data[index] 107 | } 108 | } 109 | }; 110 | } 111 | pub(crate) use impl_index; 112 | 113 | macro_rules! impl_index_mut { 114 | ($lhs:ty) => { 115 | impl IndexMut<(IndexType, IndexType)> for $lhs { 116 | fn index_mut(&mut self, index: (IndexType, IndexType)) -> &mut T { 117 | &mut self.data[index] 118 | } 119 | } 120 | }; 121 | } 122 | pub(crate) use impl_index_mut; 123 | -------------------------------------------------------------------------------- /diffsol/src/nonlinear_solver/convergence.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::ComplexField; 2 | use num_traits::{One, Pow}; 3 | 4 | use crate::{scalar::IndexType, Scalar, Vector}; 5 | 6 | #[derive(Clone)] 7 | pub struct Convergence<'a, V: Vector> { 8 | pub rtol: V::T, 9 | pub atol: &'a V, 10 | tol: V::T, 11 | max_iter: IndexType, 12 | niter: IndexType, 13 | old_norm: Option, 14 | } 15 | 16 | pub enum ConvergenceStatus { 17 | Converged, 18 | Diverged, 19 | Continue, 20 | MaximumIterations, 21 | } 22 | 23 | impl<'a, V: Vector> Convergence<'a, V> { 24 | pub fn max_iter(&self) -> IndexType { 25 | self.max_iter 26 | } 27 | pub fn set_max_iter(&mut self, value: IndexType) { 28 | self.max_iter = value; 29 | } 30 | pub fn niter(&self) -> IndexType { 31 | self.niter 32 | } 33 | pub fn new(rtol: V::T, atol: &'a V) -> Self { 34 | let minimum_tol = V::T::from(10.0) * V::T::EPSILON / rtol; 35 | let maximum_tol = V::T::from(0.03); 36 | let mut tol = V::T::from(0.33); 37 | if tol > maximum_tol { 38 | tol = maximum_tol; 39 | } 40 | if tol < minimum_tol { 41 | tol = minimum_tol; 42 | } 43 | Self { 44 | rtol, 45 | atol, 46 | tol, 47 | max_iter: 10, 48 | old_norm: None, 49 | niter: 0, 50 | } 51 | } 52 | pub fn reset(&mut self) { 53 | self.niter = 0; 54 | self.old_norm = None; 55 | } 56 | 57 | pub fn check_new_iteration(&mut self, dy: &mut V, y: &V) -> ConvergenceStatus { 58 | self.niter += 1; 59 | let norm = dy.squared_norm(y, self.atol, self.rtol).sqrt(); 60 | // if norm is zero then we are done 61 | if norm <= V::T::EPSILON { 62 | return ConvergenceStatus::Converged; 63 | } 64 | if let Some(old_norm) = self.old_norm { 65 | let rate = norm / old_norm; 66 | 67 | // check if iteration is diverging 68 | if rate > V::T::from(0.9) { 69 | return ConvergenceStatus::Diverged; 70 | } 71 | 72 | let eta = rate / (V::T::one() - rate); 73 | 74 | // check if iteration is converging 75 | if eta * norm < self.tol { 76 | return ConvergenceStatus::Converged; 77 | } 78 | 79 | // if iteration is not going to converge in max_iter 80 | // (assuming the current rate), then abort 81 | if rate.pow(i32::try_from(self.max_iter - self.niter).unwrap()) 82 | / (V::T::from(1.0) - rate) 83 | * norm 84 | > self.tol 85 | { 86 | return ConvergenceStatus::Diverged; 87 | } 88 | } else { 89 | // no rate, just test with a large eta 90 | if V::T::from(1000.0) * norm < self.tol { 91 | return ConvergenceStatus::Converged; 92 | } 93 | }; 94 | 95 | // we havn't converged, so store norm for next iteration 96 | self.old_norm = Some(norm); 97 | 98 | // check if we have reached the maximum 99 | if self.niter >= self.max_iter { 100 | ConvergenceStatus::MaximumIterations 101 | } else { 102 | ConvergenceStatus::Continue 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /diffsol/src/nonlinear_solver/newton.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{DiffsolError, NonLinearSolverError}, 3 | non_linear_solver_error, Convergence, ConvergenceStatus, LinearSolver, Matrix, NonLinearOp, 4 | NonLinearOpJacobian, NonLinearSolver, Vector, 5 | }; 6 | 7 | pub fn newton_iteration( 8 | xn: &mut V, 9 | tmp: &mut V, 10 | error_y: &V, 11 | fun: impl Fn(&V, &mut V), 12 | linear_solver: impl Fn(&mut V) -> Result<(), DiffsolError>, 13 | convergence: &mut Convergence, 14 | ) -> Result<(), DiffsolError> { 15 | convergence.reset(); 16 | loop { 17 | fun(xn, tmp); 18 | //tmp = f_at_n 19 | 20 | linear_solver(tmp)?; 21 | //tmp = -delta_n 22 | 23 | xn.sub_assign(&*tmp); 24 | // xn = xn + delta_n 25 | 26 | let res = convergence.check_new_iteration(tmp, error_y); 27 | match res { 28 | ConvergenceStatus::Continue => continue, 29 | ConvergenceStatus::Converged => return Ok(()), 30 | ConvergenceStatus::Diverged => break, 31 | ConvergenceStatus::MaximumIterations => break, 32 | } 33 | } 34 | Err(non_linear_solver_error!(NewtonDidNotConverge)) 35 | } 36 | 37 | pub struct NewtonNonlinearSolver> { 38 | linear_solver: Ls, 39 | is_jacobian_set: bool, 40 | tmp: M::V, 41 | } 42 | 43 | impl> NewtonNonlinearSolver { 44 | pub fn new(linear_solver: Ls) -> Self { 45 | Self { 46 | linear_solver, 47 | is_jacobian_set: false, 48 | tmp: M::V::zeros(0, Default::default()), 49 | } 50 | } 51 | pub fn linear_solver(&self) -> &Ls { 52 | &self.linear_solver 53 | } 54 | } 55 | 56 | impl> Default for NewtonNonlinearSolver { 57 | fn default() -> Self { 58 | Self::new(Ls::default()) 59 | } 60 | } 61 | 62 | impl> NonLinearSolver for NewtonNonlinearSolver { 63 | fn set_problem>(&mut self, op: &C) { 64 | self.linear_solver.set_problem(op); 65 | self.is_jacobian_set = false; 66 | self.tmp = C::V::zeros(op.nstates(), op.context().clone()); 67 | } 68 | 69 | fn reset_jacobian>( 70 | &mut self, 71 | op: &C, 72 | x: &C::V, 73 | t: C::T, 74 | ) { 75 | self.linear_solver.set_linearisation(op, x, t); 76 | self.is_jacobian_set = true; 77 | } 78 | 79 | fn solve_linearised_in_place(&self, x: &mut M::V) -> Result<(), DiffsolError> { 80 | self.linear_solver.solve_in_place(x) 81 | } 82 | 83 | fn solve_in_place>( 84 | &mut self, 85 | op: &C, 86 | xn: &mut M::V, 87 | t: M::T, 88 | error_y: &M::V, 89 | convergence: &mut Convergence, 90 | ) -> Result<(), DiffsolError> { 91 | if !self.is_jacobian_set { 92 | panic!("NewtonNonlinearSolver::solve_in_place() called before reset_jacobian"); 93 | } 94 | if xn.len() != op.nstates() { 95 | panic!("NewtonNonlinearSolver::solve() called with state of wrong size, expected {}, got {}", op.nstates(), xn.len()); 96 | } 97 | let linear_solver = |x: &mut C::V| self.linear_solver.solve_in_place(x); 98 | let fun = |x: &C::V, y: &mut C::V| op.call_inplace(x, t, y); 99 | newton_iteration(xn, &mut self.tmp, error_y, fun, linear_solver, convergence) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /diffsol/src/ode_solver/jacobian_update.rs: -------------------------------------------------------------------------------- 1 | use crate::Scalar; 2 | 3 | pub enum SolverState { 4 | StepSuccess, 5 | FirstConvergenceFail, 6 | SecondConvergenceFail, 7 | ErrorTestFail, 8 | Checkpoint, 9 | } 10 | 11 | #[derive(Clone)] 12 | pub struct JacobianUpdate { 13 | steps_since_jacobian_eval: usize, 14 | steps_since_rhs_jacobian_eval: usize, 15 | h_at_last_jacobian_update: T, 16 | threshold_to_update_jacobian: T, 17 | threshold_to_update_rhs_jacobian: T, 18 | update_jacobian_after_steps: usize, 19 | update_rhs_jacobian_after_steps: usize, 20 | } 21 | 22 | impl JacobianUpdate { 23 | pub fn new() -> Self { 24 | Self { 25 | steps_since_jacobian_eval: 0, 26 | steps_since_rhs_jacobian_eval: 0, 27 | h_at_last_jacobian_update: T::one(), 28 | threshold_to_update_jacobian: T::from(0.3), 29 | threshold_to_update_rhs_jacobian: T::from(0.2), 30 | update_jacobian_after_steps: 20, 31 | update_rhs_jacobian_after_steps: 50, 32 | } 33 | } 34 | 35 | pub fn update_jacobian(&mut self, h: T) { 36 | self.steps_since_jacobian_eval = 0; 37 | self.h_at_last_jacobian_update = h; 38 | } 39 | 40 | pub fn update_rhs_jacobian(&mut self) { 41 | self.steps_since_rhs_jacobian_eval = 0; 42 | } 43 | 44 | pub fn step(&mut self) { 45 | self.steps_since_jacobian_eval += 1; 46 | self.steps_since_rhs_jacobian_eval += 1; 47 | } 48 | 49 | pub fn check_jacobian_update(&mut self, h: T, state: &SolverState) -> bool { 50 | match state { 51 | SolverState::StepSuccess => { 52 | self.steps_since_jacobian_eval >= self.update_jacobian_after_steps 53 | || (h / self.h_at_last_jacobian_update - T::one()).abs() 54 | > self.threshold_to_update_jacobian 55 | } 56 | SolverState::FirstConvergenceFail => true, 57 | SolverState::SecondConvergenceFail => true, 58 | SolverState::ErrorTestFail => true, 59 | SolverState::Checkpoint => true, 60 | } 61 | } 62 | 63 | pub fn check_rhs_jacobian_update(&mut self, h: T, state: &SolverState) -> bool { 64 | match state { 65 | SolverState::StepSuccess => { 66 | self.steps_since_rhs_jacobian_eval >= self.update_rhs_jacobian_after_steps 67 | } 68 | SolverState::FirstConvergenceFail => { 69 | (h / self.h_at_last_jacobian_update - T::one()).abs() 70 | < self.threshold_to_update_rhs_jacobian 71 | } 72 | SolverState::SecondConvergenceFail => self.steps_since_rhs_jacobian_eval > 0, 73 | SolverState::ErrorTestFail => false, 74 | SolverState::Checkpoint => true, 75 | } 76 | } 77 | } 78 | 79 | impl Default for JacobianUpdate { 80 | fn default() -> Self { 81 | Self::new() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /diffsol/src/ode_solver/sdirk_state.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::DiffsolError, OdeEquations, OdeSolverProblem, OdeSolverState, StateRef, StateRefMut, 3 | Vector, 4 | }; 5 | 6 | use super::state::StateCommon; 7 | 8 | #[derive(Clone)] 9 | pub struct RkState { 10 | pub(crate) y: V, 11 | pub(crate) dy: V, 12 | pub(crate) g: V, 13 | pub(crate) dg: V, 14 | pub(crate) s: Vec, 15 | pub(crate) ds: Vec, 16 | pub(crate) sg: Vec, 17 | pub(crate) dsg: Vec, 18 | pub(crate) t: V::T, 19 | pub(crate) h: V::T, 20 | } 21 | 22 | impl RkState where V: Vector {} 23 | 24 | impl OdeSolverState for RkState 25 | where 26 | V: Vector, 27 | { 28 | fn set_problem( 29 | &mut self, 30 | _ode_problem: &OdeSolverProblem, 31 | ) -> Result<(), DiffsolError> { 32 | Ok(()) 33 | } 34 | 35 | fn set_augmented_problem>( 36 | &mut self, 37 | _ode_problem: &OdeSolverProblem, 38 | _augmented_eqn: &AugmentedEqn, 39 | ) -> Result<(), DiffsolError> { 40 | Ok(()) 41 | } 42 | 43 | fn new_from_common(state: StateCommon) -> Self { 44 | Self { 45 | y: state.y, 46 | dy: state.dy, 47 | g: state.g, 48 | dg: state.dg, 49 | s: state.s, 50 | ds: state.ds, 51 | sg: state.sg, 52 | dsg: state.dsg, 53 | t: state.t, 54 | h: state.h, 55 | } 56 | } 57 | 58 | fn into_common(self) -> StateCommon { 59 | StateCommon { 60 | y: self.y, 61 | dy: self.dy, 62 | g: self.g, 63 | dg: self.dg, 64 | s: self.s, 65 | ds: self.ds, 66 | sg: self.sg, 67 | dsg: self.dsg, 68 | t: self.t, 69 | h: self.h, 70 | } 71 | } 72 | 73 | fn as_mut(&mut self) -> StateRefMut { 74 | StateRefMut { 75 | y: &mut self.y, 76 | dy: &mut self.dy, 77 | g: &mut self.g, 78 | dg: &mut self.dg, 79 | s: &mut self.s, 80 | ds: &mut self.ds, 81 | sg: &mut self.sg, 82 | dsg: &mut self.dsg, 83 | t: &mut self.t, 84 | h: &mut self.h, 85 | } 86 | } 87 | 88 | fn as_ref(&self) -> StateRef { 89 | StateRef { 90 | y: &self.y, 91 | dy: &self.dy, 92 | g: &self.g, 93 | dg: &self.dg, 94 | s: &self.s, 95 | ds: &self.ds, 96 | sg: &self.sg, 97 | dsg: &self.dsg, 98 | t: self.t, 99 | h: self.h, 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /diffsol/src/ode_solver/sensitivities.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::DiffsolError, error::OdeSolverError, ode_solver_error, AugmentedOdeSolverMethod, 3 | Context, DefaultDenseMatrix, DefaultSolver, DenseMatrix, OdeEquationsImplicitSens, 4 | OdeSolverStopReason, Op, SensEquations, VectorViewMut, 5 | }; 6 | 7 | pub trait SensitivitiesOdeSolverMethod<'a, Eqn>: 8 | AugmentedOdeSolverMethod<'a, Eqn, SensEquations<'a, Eqn>> 9 | where 10 | Eqn: OdeEquationsImplicitSens + 'a, 11 | { 12 | /// Using the provided state, solve the problem up to time `t_eval[t_eval.len()-1]` 13 | /// Returns a tuple `(y, sens)`, where `y` is a dense matrix of solution values at timepoints given by `t_eval`, 14 | /// and `sens` is a Vec of dense matrices, the ith element of the Vec are the the sensitivities with respect to the ith parameter. 15 | /// After the solver has finished, the internal state of the solver is at time `t_eval[t_eval.len()-1]`. 16 | #[allow(clippy::type_complexity)] 17 | fn solve_dense_sensitivities( 18 | &mut self, 19 | t_eval: &[Eqn::T], 20 | ) -> Result< 21 | ( 22 | ::M, 23 | Vec<::M>, 24 | ), 25 | DiffsolError, 26 | > 27 | where 28 | Eqn: OdeEquationsImplicitSens, 29 | Eqn::M: DefaultSolver, 30 | Eqn::V: DefaultDenseMatrix, 31 | Self: Sized, 32 | { 33 | if self.problem().integrate_out { 34 | return Err(ode_solver_error!( 35 | Other, 36 | "Cannot integrate out when solving for sensitivities" 37 | )); 38 | } 39 | let nrows = self.problem().eqn.rhs().nstates(); 40 | let mut ret = self 41 | .problem() 42 | .context() 43 | .dense_mat_zeros::(nrows, t_eval.len()); 44 | let mut ret_sens = vec![ 45 | self.problem() 46 | .context() 47 | .dense_mat_zeros::(nrows, t_eval.len()); 48 | self.problem().eqn.rhs().nparams() 49 | ]; 50 | 51 | // check t_eval is increasing and all values are greater than or equal to the current time 52 | let t0 = self.state().t; 53 | if t_eval.windows(2).any(|w| w[0] > w[1] || w[0] < t0) { 54 | return Err(ode_solver_error!(InvalidTEval)); 55 | } 56 | 57 | // do loop 58 | self.set_stop_time(t_eval[t_eval.len() - 1])?; 59 | let mut step_reason = OdeSolverStopReason::InternalTimestep; 60 | for (i, t) in t_eval.iter().take(t_eval.len() - 1).enumerate() { 61 | while self.state().t < *t { 62 | step_reason = self.step()?; 63 | } 64 | let y = self.interpolate(*t)?; 65 | ret.column_mut(i).copy_from(&y); 66 | let s = self.interpolate_sens(*t)?; 67 | for (j, s_j) in s.iter().enumerate() { 68 | ret_sens[j].column_mut(i).copy_from(s_j); 69 | } 70 | } 71 | 72 | // do final step 73 | while step_reason != OdeSolverStopReason::TstopReached { 74 | step_reason = self.step()?; 75 | } 76 | let y = self.state().y; 77 | ret.column_mut(t_eval.len() - 1).copy_from(y); 78 | let s = self.state().s; 79 | for (j, s_j) in s.iter().enumerate() { 80 | ret_sens[j].column_mut(t_eval.len() - 1).copy_from(s_j); 81 | } 82 | Ok((ret, ret_sens)) 83 | } 84 | } 85 | 86 | impl<'a, M, Eqn> SensitivitiesOdeSolverMethod<'a, Eqn> for M 87 | where 88 | M: AugmentedOdeSolverMethod<'a, Eqn, SensEquations<'a, Eqn>>, 89 | Eqn: OdeEquationsImplicitSens + 'a, 90 | { 91 | } 92 | -------------------------------------------------------------------------------- /diffsol/src/ode_solver/test.rs: -------------------------------------------------------------------------------- 1 | type V = Vec; 2 | 3 | struct Data { 4 | y: V, 5 | g: V 6 | } 7 | 8 | struct DataThingWrapperMut<'a> { 9 | y: &'a mut V, 10 | g: &'a mut V 11 | } 12 | 13 | trait DataThing { 14 | fn get_mut(&mut self) -> DataThingWrapperMut; 15 | } 16 | 17 | impl DataThing for Data { 18 | fn get_mut(&mut self) -> DataThingWrapperMut { 19 | DataThingWrapperMut { 20 | y: &mut self.y, 21 | g: &mut self.g 22 | } 23 | } 24 | } 25 | 26 | fn test1(a: &V) { 27 | } 28 | 29 | fn test2(b: &mut V) { 30 | } 31 | 32 | fn test3(a: &V, b: &mut V) { 33 | } 34 | 35 | fn make_data() -> Data { 36 | Data{ 37 | y: vec![1, 2, 3], 38 | g: vec![] 39 | } 40 | } 41 | 42 | fn main() { 43 | { 44 | let d1 = make_data(); 45 | test1(&d1.get_mut().y); 46 | } 47 | 48 | { 49 | let mut d2 = make_data(); 50 | test2(&mut d2.get_mut().g); 51 | } 52 | 53 | { 54 | let mut d3 = make_data(); 55 | let mut d3_mut = d3.get_mut(); 56 | test3(&d3_mut.y, &mut d3_mut.g); 57 | } 58 | } -------------------------------------------------------------------------------- /diffsol/src/ode_solver/test_models/dydt_y2.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ode_solver::problem::OdeSolverSolution, scalar::scale, Context, DenseMatrix, OdeBuilder, 3 | OdeEquationsImplicit, OdeSolverProblem, Vector, 4 | }; 5 | use num_traits::One; 6 | use std::ops::MulAssign; 7 | 8 | // dy/dt = y^2 9 | fn rhs(x: &M::V, _p: &M::V, _t: M::T, y: &mut M::V) { 10 | y.copy_from(x); 11 | y.component_mul_assign(x); 12 | } 13 | 14 | // Jv = 2yv 15 | fn rhs_jac(x: &M::V, _p: &M::V, _t: M::T, v: &M::V, y: &mut M::V) { 16 | y.copy_from(v); 17 | y.component_mul_assign(x); 18 | y.mul_assign(scale(M::T::from(2.))); 19 | } 20 | 21 | #[allow(clippy::type_complexity)] 22 | pub fn dydt_y2_problem( 23 | use_coloring: bool, 24 | size: usize, 25 | ) -> ( 26 | OdeSolverProblem>, 27 | OdeSolverSolution, 28 | ) { 29 | let y0 = -200.; 30 | let tlast = 20.0; 31 | let problem = OdeBuilder::::new() 32 | .use_coloring(use_coloring) 33 | .rtol(1e-4) 34 | .rhs_implicit(rhs::, rhs_jac::) 35 | .init(move |_p, _t, y| y.fill(y0.into()), size) 36 | .build() 37 | .unwrap(); 38 | let mut soln = OdeSolverSolution::default(); 39 | let y0: Vec = [y0.into()].repeat(size); 40 | let n = 10; 41 | let dt = tlast / n as f64; 42 | for i in 0..=n { 43 | let t = M::T::from(i as f64 * dt); 44 | // y = y0 / (1 - y0 * t) 45 | let y = y0 46 | .iter() 47 | .map(|&y| y / (M::T::one() - y * t)) 48 | .collect::>(); 49 | soln.push(problem.context().vector_from_vec(y), t); 50 | } 51 | (problem, soln) 52 | } 53 | -------------------------------------------------------------------------------- /diffsol/src/ode_solver/test_models/gaussian_decay.rs: -------------------------------------------------------------------------------- 1 | use crate::ode_solver::problem::OdeSolverSolution; 2 | use crate::OdeSolverProblem; 3 | use crate::{ 4 | scalar::scale, ConstantOp, DenseMatrix, OdeBuilder, OdeEquations, OdeEquationsImplicit, Vector, 5 | }; 6 | use nalgebra::ComplexField; 7 | use num_traits::Pow; 8 | use num_traits::Zero; 9 | use std::ops::MulAssign; 10 | 11 | // dy/dt = -aty (p = [a]) 12 | fn gaussian_decay(x: &M::V, p: &M::V, t: M::T, y: &mut M::V) { 13 | y.copy_from(x); 14 | y.component_mul_assign(p); 15 | y.mul_assign(scale(-t)); 16 | } 17 | 18 | // Jv = -atv 19 | fn gaussian_decay_jacobian(_x: &M::V, p: &M::V, t: M::T, v: &M::V, y: &mut M::V) { 20 | y.copy_from(v); 21 | y.component_mul_assign(p); 22 | y.mul_assign(scale(-t)); 23 | } 24 | 25 | #[allow(clippy::type_complexity)] 26 | pub fn gaussian_decay_problem( 27 | use_coloring: bool, 28 | size: usize, 29 | ) -> ( 30 | OdeSolverProblem>, 31 | OdeSolverSolution, 32 | ) { 33 | let problem = OdeBuilder::::new() 34 | .p([0.1].repeat(size)) 35 | .use_coloring(use_coloring) 36 | .rhs_implicit(gaussian_decay::, gaussian_decay_jacobian::) 37 | .init(move |_p, _t, y| y.fill(1.0.into()), size) 38 | .build() 39 | .unwrap(); 40 | let p = [M::T::from(0.1)].repeat(size); 41 | let mut soln = OdeSolverSolution::default(); 42 | for i in 0..10 { 43 | let t = M::T::from(i as f64 / 1.0); 44 | let mut y: M::V = problem.eqn.init().call(M::T::zero()); 45 | let px = M::V::from_vec( 46 | p.iter() 47 | .map(|&x| (x * t.pow(2) / M::T::from(-2.0)).exp()) 48 | .collect::>(), 49 | problem.context().clone(), 50 | ); 51 | y.component_mul_assign(&px); 52 | soln.push(y, t); 53 | } 54 | (problem, soln) 55 | } 56 | -------------------------------------------------------------------------------- /diffsol/src/ode_solver/test_models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dydt_y2; 2 | pub mod exponential_decay; 3 | pub mod exponential_decay_with_algebraic; 4 | pub mod foodweb; 5 | pub mod gaussian_decay; 6 | pub mod heat2d; 7 | pub mod robertson; 8 | pub mod robertson_ode; 9 | pub mod robertson_ode_with_sens; 10 | -------------------------------------------------------------------------------- /diffsol/src/op/closure_no_jac.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use crate::{Matrix, NonLinearOp, Op}; 4 | 5 | use super::{BuilderOp, OpStatistics, ParameterisedOp}; 6 | 7 | pub struct ClosureNoJac 8 | where 9 | M: Matrix, 10 | F: Fn(&M::V, &M::V, M::T, &mut M::V), 11 | { 12 | func: F, 13 | nstates: usize, 14 | nout: usize, 15 | nparams: usize, 16 | statistics: RefCell, 17 | ctx: M::C, 18 | } 19 | 20 | impl ClosureNoJac 21 | where 22 | M: Matrix, 23 | F: Fn(&M::V, &M::V, M::T, &mut M::V), 24 | { 25 | pub fn new(func: F, nstates: usize, nout: usize, nparams: usize, ctx: M::C) -> Self { 26 | Self { 27 | func, 28 | nstates, 29 | nparams, 30 | nout, 31 | statistics: RefCell::new(OpStatistics::default()), 32 | ctx, 33 | } 34 | } 35 | } 36 | 37 | impl BuilderOp for ClosureNoJac 38 | where 39 | M: Matrix, 40 | F: Fn(&M::V, &M::V, M::T, &mut M::V), 41 | { 42 | fn calculate_sparsity(&mut self, _y0: &Self::V, _t0: Self::T, _p: &Self::V) { 43 | // Do nothing 44 | } 45 | fn set_nstates(&mut self, nstates: usize) { 46 | self.nstates = nstates; 47 | } 48 | fn set_nout(&mut self, nout: usize) { 49 | self.nout = nout; 50 | } 51 | fn set_nparams(&mut self, nparams: usize) { 52 | self.nparams = nparams; 53 | } 54 | } 55 | 56 | impl Op for ClosureNoJac 57 | where 58 | M: Matrix, 59 | F: Fn(&M::V, &M::V, M::T, &mut M::V), 60 | { 61 | type V = M::V; 62 | type T = M::T; 63 | type M = M; 64 | type C = M::C; 65 | fn nstates(&self) -> usize { 66 | self.nstates 67 | } 68 | fn context(&self) -> &Self::C { 69 | &self.ctx 70 | } 71 | fn nout(&self) -> usize { 72 | self.nout 73 | } 74 | fn nparams(&self) -> usize { 75 | self.nparams 76 | } 77 | fn statistics(&self) -> OpStatistics { 78 | self.statistics.borrow().clone() 79 | } 80 | } 81 | 82 | impl NonLinearOp for ParameterisedOp<'_, ClosureNoJac> 83 | where 84 | M: Matrix, 85 | F: Fn(&M::V, &M::V, M::T, &mut M::V), 86 | { 87 | fn call_inplace(&self, x: &M::V, t: M::T, y: &mut M::V) { 88 | self.op.statistics.borrow_mut().increment_call(); 89 | (self.op.func)(x, self.p, t, y) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /diffsol/src/op/constant_closure.rs: -------------------------------------------------------------------------------- 1 | use super::{BuilderOp, ParameterisedOp}; 2 | use crate::{ConstantOp, Matrix, Op}; 3 | 4 | pub struct ConstantClosure 5 | where 6 | M: Matrix, 7 | I: Fn(&M::V, M::T, &mut M::V), 8 | { 9 | func: I, 10 | nout: usize, 11 | nparams: usize, 12 | ctx: M::C, 13 | } 14 | 15 | impl ConstantClosure 16 | where 17 | M: Matrix, 18 | I: Fn(&M::V, M::T, &mut M::V), 19 | { 20 | pub fn new(func: I, nout: usize, nparams: usize, ctx: M::C) -> Self { 21 | Self { 22 | func, 23 | nout, 24 | nparams, 25 | ctx, 26 | } 27 | } 28 | } 29 | 30 | impl Op for ConstantClosure 31 | where 32 | M: Matrix, 33 | I: Fn(&M::V, M::T, &mut M::V), 34 | { 35 | type V = M::V; 36 | type T = M::T; 37 | type M = M; 38 | type C = M::C; 39 | fn nstates(&self) -> usize { 40 | 0 41 | } 42 | fn nout(&self) -> usize { 43 | self.nout 44 | } 45 | fn nparams(&self) -> usize { 46 | self.nparams 47 | } 48 | fn context(&self) -> &Self::C { 49 | &self.ctx 50 | } 51 | } 52 | 53 | impl BuilderOp for ConstantClosure 54 | where 55 | M: Matrix, 56 | I: Fn(&M::V, M::T, &mut M::V), 57 | { 58 | fn calculate_sparsity(&mut self, _y0: &Self::V, _t0: Self::T, _p: &Self::V) { 59 | // do nothing 60 | } 61 | fn set_nstates(&mut self, _nstates: usize) { 62 | // do nothing 63 | } 64 | fn set_nout(&mut self, nout: usize) { 65 | self.nout = nout; 66 | } 67 | fn set_nparams(&mut self, nparams: usize) { 68 | self.nparams = nparams; 69 | } 70 | } 71 | 72 | impl ConstantOp for ParameterisedOp<'_, ConstantClosure> 73 | where 74 | M: Matrix, 75 | I: Fn(&M::V, M::T, &mut M::V), 76 | { 77 | fn call_inplace(&self, t: Self::T, y: &mut Self::V) { 78 | (self.op.func)(self.p, t, y) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /diffsol/src/op/constant_closure_with_adjoint.rs: -------------------------------------------------------------------------------- 1 | use crate::{ConstantOp, ConstantOpSensAdjoint, Matrix, Op}; 2 | 3 | use super::{BuilderOp, ParameterisedOp}; 4 | 5 | pub struct ConstantClosureWithAdjoint 6 | where 7 | M: Matrix, 8 | I: Fn(&M::V, M::T, &mut M::V), 9 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 10 | { 11 | func: I, 12 | func_sens_adjoint: J, 13 | nout: usize, 14 | nparams: usize, 15 | ctx: M::C, 16 | } 17 | 18 | impl ConstantClosureWithAdjoint 19 | where 20 | M: Matrix, 21 | I: Fn(&M::V, M::T, &mut M::V), 22 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 23 | { 24 | pub fn new(func: I, func_sens_adjoint: J, nout: usize, nparams: usize, ctx: M::C) -> Self { 25 | Self { 26 | func, 27 | func_sens_adjoint, 28 | nout, 29 | nparams, 30 | ctx, 31 | } 32 | } 33 | } 34 | 35 | impl BuilderOp for ConstantClosureWithAdjoint 36 | where 37 | M: Matrix, 38 | I: Fn(&M::V, M::T, &mut M::V), 39 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 40 | { 41 | fn calculate_sparsity(&mut self, _y0: &Self::V, _t0: Self::T, _p: &Self::V) { 42 | // Do nothing 43 | } 44 | fn set_nstates(&mut self, _nstates: usize) { 45 | // Do nothing 46 | } 47 | fn set_nout(&mut self, nout: usize) { 48 | self.nout = nout; 49 | } 50 | fn set_nparams(&mut self, nparams: usize) { 51 | self.nparams = nparams; 52 | } 53 | } 54 | 55 | impl Op for ConstantClosureWithAdjoint 56 | where 57 | M: Matrix, 58 | I: Fn(&M::V, M::T, &mut M::V), 59 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 60 | { 61 | type V = M::V; 62 | type T = M::T; 63 | type M = M; 64 | type C = M::C; 65 | fn nstates(&self) -> usize { 66 | 0 67 | } 68 | fn nout(&self) -> usize { 69 | self.nout 70 | } 71 | fn nparams(&self) -> usize { 72 | self.nparams 73 | } 74 | fn context(&self) -> &Self::C { 75 | &self.ctx 76 | } 77 | } 78 | 79 | impl ConstantOp for ParameterisedOp<'_, ConstantClosureWithAdjoint> 80 | where 81 | M: Matrix, 82 | I: Fn(&M::V, M::T, &mut M::V), 83 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 84 | { 85 | fn call_inplace(&self, t: Self::T, y: &mut Self::V) { 86 | (self.op.func)(self.p, t, y) 87 | } 88 | } 89 | 90 | impl ConstantOpSensAdjoint for ParameterisedOp<'_, ConstantClosureWithAdjoint> 91 | where 92 | M: Matrix, 93 | I: Fn(&M::V, M::T, &mut M::V), 94 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 95 | { 96 | fn sens_transpose_mul_inplace(&self, t: Self::T, v: &Self::V, y: &mut Self::V) { 97 | (self.op.func_sens_adjoint)(self.p, t, v, y); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /diffsol/src/op/constant_closure_with_sens.rs: -------------------------------------------------------------------------------- 1 | use crate::{ConstantOp, ConstantOpSens, Matrix, Op}; 2 | 3 | use super::{BuilderOp, ParameterisedOp}; 4 | 5 | pub struct ConstantClosureWithSens 6 | where 7 | M: Matrix, 8 | I: Fn(&M::V, M::T, &mut M::V), 9 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 10 | { 11 | func: I, 12 | func_sens: J, 13 | nout: usize, 14 | nparams: usize, 15 | ctx: M::C, 16 | } 17 | 18 | impl ConstantClosureWithSens 19 | where 20 | M: Matrix, 21 | I: Fn(&M::V, M::T, &mut M::V), 22 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 23 | { 24 | pub fn new(func: I, func_sens: J, nout: usize, nparams: usize, ctx: M::C) -> Self { 25 | Self { 26 | func, 27 | func_sens, 28 | nout, 29 | nparams, 30 | ctx, 31 | } 32 | } 33 | } 34 | 35 | impl Op for ConstantClosureWithSens 36 | where 37 | M: Matrix, 38 | I: Fn(&M::V, M::T, &mut M::V), 39 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 40 | { 41 | type V = M::V; 42 | type T = M::T; 43 | type M = M; 44 | type C = M::C; 45 | fn nstates(&self) -> usize { 46 | 0 47 | } 48 | fn nout(&self) -> usize { 49 | self.nout 50 | } 51 | fn nparams(&self) -> usize { 52 | self.nparams 53 | } 54 | fn context(&self) -> &Self::C { 55 | &self.ctx 56 | } 57 | } 58 | 59 | impl BuilderOp for ConstantClosureWithSens 60 | where 61 | M: Matrix, 62 | I: Fn(&M::V, M::T, &mut M::V), 63 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 64 | { 65 | fn calculate_sparsity(&mut self, _y0: &Self::V, _t0: Self::T, _p: &Self::V) { 66 | // do nothing 67 | } 68 | fn set_nstates(&mut self, _nstates: usize) { 69 | // do nothing 70 | } 71 | fn set_nout(&mut self, nout: usize) { 72 | self.nout = nout; 73 | } 74 | fn set_nparams(&mut self, nparams: usize) { 75 | self.nparams = nparams; 76 | } 77 | } 78 | 79 | impl ConstantOp for ParameterisedOp<'_, ConstantClosureWithSens> 80 | where 81 | M: Matrix, 82 | I: Fn(&M::V, M::T, &mut M::V), 83 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 84 | { 85 | fn call_inplace(&self, t: Self::T, y: &mut Self::V) { 86 | (self.op.func)(self.p, t, y) 87 | } 88 | } 89 | 90 | impl ConstantOpSens for ParameterisedOp<'_, ConstantClosureWithSens> 91 | where 92 | M: Matrix, 93 | I: Fn(&M::V, M::T, &mut M::V), 94 | J: Fn(&M::V, M::T, &M::V, &mut M::V), 95 | { 96 | fn sens_mul_inplace(&self, t: Self::T, v: &Self::V, y: &mut Self::V) { 97 | (self.op.func_sens)(self.p, t, v, y); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /diffsol/src/op/linear_closure.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use crate::{ 4 | find_matrix_non_zeros, jacobian::JacobianColoring, matrix::sparsity::MatrixSparsity, LinearOp, 5 | Matrix, Op, 6 | }; 7 | 8 | use super::{BuilderOp, OpStatistics, ParameterisedOp}; 9 | 10 | pub struct LinearClosure 11 | where 12 | M: Matrix, 13 | F: Fn(&M::V, &M::V, M::T, M::T, &mut M::V), 14 | { 15 | func: F, 16 | nstates: usize, 17 | nout: usize, 18 | nparams: usize, 19 | coloring: Option>, 20 | sparsity: Option, 21 | statistics: RefCell, 22 | ctx: M::C, 23 | } 24 | 25 | impl LinearClosure 26 | where 27 | M: Matrix, 28 | F: Fn(&M::V, &M::V, M::T, M::T, &mut M::V), 29 | { 30 | pub fn new(func: F, nstates: usize, nout: usize, nparams: usize, ctx: M::C) -> Self { 31 | Self { 32 | func, 33 | nstates, 34 | statistics: RefCell::new(OpStatistics::default()), 35 | nout, 36 | nparams, 37 | coloring: None, 38 | sparsity: None, 39 | ctx, 40 | } 41 | } 42 | 43 | pub fn calculate_sparsity(&mut self, t0: M::T, p: &M::V) { 44 | let op = ParameterisedOp { op: self, p }; 45 | let non_zeros = find_matrix_non_zeros(&op, t0); 46 | self.sparsity = Some( 47 | MatrixSparsity::try_from_indices(self.nout(), self.nstates(), non_zeros.clone()) 48 | .expect("invalid sparsity pattern"), 49 | ); 50 | self.coloring = Some(JacobianColoring::new( 51 | self.sparsity.as_ref().unwrap(), 52 | &non_zeros, 53 | self.ctx.clone(), 54 | )); 55 | } 56 | } 57 | 58 | impl Op for LinearClosure 59 | where 60 | M: Matrix, 61 | F: Fn(&M::V, &M::V, M::T, M::T, &mut M::V), 62 | { 63 | type V = M::V; 64 | type T = M::T; 65 | type M = M; 66 | type C = M::C; 67 | fn nstates(&self) -> usize { 68 | self.nstates 69 | } 70 | fn nout(&self) -> usize { 71 | self.nout 72 | } 73 | fn nparams(&self) -> usize { 74 | self.nparams 75 | } 76 | fn context(&self) -> &Self::C { 77 | &self.ctx 78 | } 79 | 80 | fn statistics(&self) -> OpStatistics { 81 | self.statistics.borrow().clone() 82 | } 83 | } 84 | 85 | impl BuilderOp for LinearClosure 86 | where 87 | M: Matrix, 88 | F: Fn(&M::V, &M::V, M::T, M::T, &mut M::V), 89 | { 90 | fn calculate_sparsity(&mut self, _y0: &Self::V, t0: Self::T, p: &Self::V) { 91 | self.calculate_sparsity(t0, p); 92 | } 93 | fn set_nout(&mut self, nout: usize) { 94 | self.nout = nout; 95 | } 96 | fn set_nparams(&mut self, nparams: usize) { 97 | self.nparams = nparams; 98 | } 99 | fn set_nstates(&mut self, nstates: usize) { 100 | self.nstates = nstates; 101 | } 102 | } 103 | 104 | impl LinearOp for ParameterisedOp<'_, LinearClosure> 105 | where 106 | M: Matrix, 107 | F: Fn(&M::V, &M::V, M::T, M::T, &mut M::V), 108 | { 109 | fn gemv_inplace(&self, x: &M::V, t: M::T, beta: M::T, y: &mut M::V) { 110 | self.op.statistics.borrow_mut().increment_call(); 111 | (self.op.func)(x, self.p, t, beta, y) 112 | } 113 | 114 | fn matrix_inplace(&self, t: Self::T, y: &mut Self::M) { 115 | self.op.statistics.borrow_mut().increment_matrix(); 116 | if let Some(coloring) = &self.op.coloring { 117 | coloring.matrix_inplace(self, t, y); 118 | } else { 119 | self._default_matrix_inplace(t, y); 120 | } 121 | } 122 | fn sparsity(&self) -> Option<::Sparsity> { 123 | self.op.sparsity.clone() 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /diffsol/src/op/linearise.rs: -------------------------------------------------------------------------------- 1 | use num_traits::One; 2 | use std::{cell::RefCell, rc::Rc}; 3 | 4 | use crate::{LinearOp, Matrix, Op, Vector}; 5 | 6 | use super::nonlinear_op::NonLinearOpJacobian; 7 | 8 | pub struct LinearisedOp { 9 | callable: Rc, 10 | x: C::V, 11 | tmp: RefCell, 12 | x_is_set: bool, 13 | } 14 | 15 | impl LinearisedOp { 16 | pub fn new(callable: Rc) -> Self { 17 | let x = C::V::zeros(callable.nstates(), callable.context().clone()); 18 | let tmp = RefCell::new(C::V::zeros(callable.nstates(), callable.context().clone())); 19 | Self { 20 | callable, 21 | x, 22 | tmp, 23 | x_is_set: false, 24 | } 25 | } 26 | 27 | pub fn set_x(&mut self, x: &C::V) { 28 | self.x.copy_from(x); 29 | self.x_is_set = true; 30 | } 31 | 32 | pub fn unset_x(&mut self) { 33 | self.x_is_set = false; 34 | } 35 | 36 | pub fn x_is_set(&self) -> bool { 37 | self.x_is_set 38 | } 39 | } 40 | 41 | impl Op for LinearisedOp { 42 | type V = C::V; 43 | type T = C::T; 44 | type M = C::M; 45 | type C = C::C; 46 | fn nstates(&self) -> usize { 47 | self.callable.nstates() 48 | } 49 | fn nout(&self) -> usize { 50 | self.callable.nout() 51 | } 52 | fn nparams(&self) -> usize { 53 | self.callable.nparams() 54 | } 55 | fn context(&self) -> &Self::C { 56 | self.callable.context() 57 | } 58 | } 59 | 60 | impl LinearOp for LinearisedOp { 61 | fn call_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::V) { 62 | self.callable.jac_mul_inplace(&self.x, t, x, y); 63 | } 64 | fn gemv_inplace(&self, x: &Self::V, t: Self::T, beta: Self::T, y: &mut Self::V) { 65 | let mut tmp = self.tmp.borrow_mut(); 66 | tmp.copy_from(y); 67 | self.callable.jac_mul_inplace(&self.x, t, x, y); 68 | y.axpy(beta, &tmp, Self::T::one()); 69 | } 70 | fn matrix_inplace(&self, t: Self::T, y: &mut Self::M) { 71 | self.callable.jacobian_inplace(&self.x, t, y); 72 | } 73 | fn sparsity(&self) -> Option<::Sparsity> { 74 | self.callable.jacobian_sparsity() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /diffsol/src/op/matrix.rs: -------------------------------------------------------------------------------- 1 | use crate::{LinearOp, Matrix, MatrixSparsityRef, NonLinearOp, NonLinearOpJacobian, Op}; 2 | use num_traits::Zero; 3 | 4 | pub struct MatrixOp { 5 | m: M, 6 | } 7 | 8 | impl MatrixOp { 9 | pub fn new(m: M) -> Self { 10 | Self { m } 11 | } 12 | pub fn m_mut(&mut self) -> &mut M { 13 | &mut self.m 14 | } 15 | pub fn m(&self) -> &M { 16 | &self.m 17 | } 18 | } 19 | 20 | impl Op for MatrixOp { 21 | type V = M::V; 22 | type T = M::T; 23 | type M = M; 24 | type C = M::C; 25 | fn nstates(&self) -> usize { 26 | self.m.nrows() 27 | } 28 | fn nout(&self) -> usize { 29 | self.m.ncols() 30 | } 31 | fn nparams(&self) -> usize { 32 | 0 33 | } 34 | fn context(&self) -> &Self::C { 35 | self.m.context() 36 | } 37 | } 38 | 39 | impl LinearOp for MatrixOp { 40 | fn gemv_inplace(&self, x: &Self::V, t: Self::T, beta: Self::T, y: &mut Self::V) { 41 | self.m.gemv(t, x, beta, y); 42 | } 43 | fn sparsity(&self) -> Option<::Sparsity> { 44 | self.m.sparsity().map(|s| s.to_owned()) 45 | } 46 | } 47 | 48 | impl NonLinearOp for MatrixOp { 49 | fn call_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::V) { 50 | self.m.gemv(t, x, Self::T::zero(), y); 51 | } 52 | } 53 | 54 | impl NonLinearOpJacobian for MatrixOp { 55 | fn jac_mul_inplace(&self, _x: &Self::V, t: Self::T, v: &Self::V, y: &mut Self::V) { 56 | self.m.gemv(t, v, Self::T::zero(), y); 57 | } 58 | 59 | fn jacobian(&self, _x: &Self::V, _t: Self::T) -> Self::M { 60 | self.m.clone() 61 | } 62 | 63 | fn jacobian_inplace(&self, _x: &Self::V, _t: Self::T, y: &mut Self::M) { 64 | y.copy_from(&self.m); 65 | } 66 | 67 | fn jacobian_sparsity(&self) -> Option<::Sparsity> { 68 | self.m.sparsity().map(|s| s.to_owned()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /diffsol/src/scalar/cuda.rs: -------------------------------------------------------------------------------- 1 | use cudarc::driver::{DeviceRepr, ValidAsZeroBits}; 2 | 3 | use super::Scalar; 4 | 5 | pub enum CudaType { 6 | F64, 7 | } 8 | 9 | pub trait ScalarCuda: Scalar + ValidAsZeroBits + DeviceRepr { 10 | fn as_enum() -> CudaType; 11 | fn as_f64(self) -> f64 { 12 | panic!("Unsupported type for as_f64"); 13 | } 14 | fn as_str() -> &'static str { 15 | match Self::as_enum() { 16 | CudaType::F64 => "f64", 17 | } 18 | } 19 | } 20 | 21 | impl ScalarCuda for f64 { 22 | fn as_enum() -> CudaType { 23 | CudaType::F64 24 | } 25 | fn as_f64(self) -> f64 { 26 | self 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /diffsol/src/scalar/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Display, 3 | ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}, 4 | }; 5 | 6 | #[cfg(feature = "cuda")] 7 | pub mod cuda; 8 | 9 | use crate::vector::VectorView; 10 | pub trait Scalar: 11 | nalgebra::Scalar 12 | + faer_traits::ComplexField 13 | + faer_traits::RealField 14 | + nalgebra::SimdRealField 15 | + nalgebra::ComplexField 16 | + num_traits::Signed 17 | + num_traits::Pow 18 | + num_traits::Pow 19 | + From 20 | + Display 21 | + Copy 22 | + From 23 | + Into 24 | + PartialOrd 25 | { 26 | const EPSILON: Self; 27 | const INFINITY: Self; 28 | const NAN: Self; 29 | fn is_nan(self) -> bool; 30 | } 31 | 32 | pub type IndexType = usize; 33 | 34 | impl Scalar for f64 { 35 | const EPSILON: Self = f64::EPSILON; 36 | const INFINITY: Self = f64::INFINITY; 37 | const NAN: Self = f64::NAN; 38 | fn is_nan(self) -> bool { 39 | self.is_nan() 40 | } 41 | } 42 | 43 | impl From> for Scale { 44 | fn from(s: faer::Scale) -> Self { 45 | Scale(s.0) 46 | } 47 | } 48 | impl From> for faer::Scale { 49 | fn from(s: Scale) -> Self { 50 | faer::Scale(s.value()) 51 | } 52 | } 53 | impl From for Scale { 54 | fn from(s: T) -> Self { 55 | Scale(s) 56 | } 57 | } 58 | 59 | #[derive(Copy, Clone, Debug)] 60 | pub struct Scale(pub E); 61 | 62 | impl Scale { 63 | #[inline] 64 | pub fn value(self) -> E { 65 | self.0 66 | } 67 | } 68 | 69 | #[inline] 70 | pub fn scale(value: E) -> Scale { 71 | Scale(value) 72 | } 73 | 74 | macro_rules! impl_bin_op { 75 | ($trait:ident, $method:ident, $operator:tt) => { 76 | impl $trait> for Scale { 77 | type Output = Scale; 78 | 79 | #[inline] 80 | fn $method(self, rhs: Scale) -> Self::Output { 81 | Scale(self.0 $operator rhs.0) 82 | } 83 | } 84 | }; 85 | } 86 | 87 | macro_rules! impl_assign_bin_op { 88 | ($trait:ident, $method:ident, $operator:tt) => { 89 | impl $trait> for Scale { 90 | #[inline] 91 | fn $method(&mut self, rhs: Scale) { 92 | self.0 = self.0 $operator rhs.0 93 | } 94 | } 95 | }; 96 | } 97 | 98 | impl_bin_op!(Mul, mul, *); 99 | impl_bin_op!(Add, add, +); 100 | impl_bin_op!(Sub, sub, -); 101 | 102 | impl_assign_bin_op!(MulAssign, mul_assign, *); 103 | impl_assign_bin_op!(AddAssign, add_assign, +); 104 | impl_assign_bin_op!(SubAssign, sub_assign, -); 105 | 106 | impl PartialEq for Scale { 107 | #[inline] 108 | fn eq(&self, rhs: &Self) -> bool { 109 | self.0 == rhs.0 110 | } 111 | } 112 | 113 | impl, E: Scalar> Mul for Scale { 114 | type Output = V::Owned; 115 | #[inline] 116 | fn mul(self, rhs: V) -> Self::Output { 117 | rhs * scale(self.0) 118 | } 119 | } 120 | 121 | #[test] 122 | fn test_scale() { 123 | assert_eq!(scale(2.0) * scale(3.0), scale(6.0)); 124 | } 125 | -------------------------------------------------------------------------------- /diffsol/src/solver/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::IndexType; 2 | 3 | pub struct SolverStatistics { 4 | pub niter: IndexType, 5 | pub nmaxiter: IndexType, 6 | } 7 | -------------------------------------------------------------------------------- /diffsol/src/sundials_sys.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | non_upper_case_globals, 3 | non_camel_case_types, 4 | non_snake_case, 5 | improper_ctypes, 6 | clippy::all 7 | )] 8 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 9 | -------------------------------------------------------------------------------- /diffsol/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | -------------------------------------------------------------------------------- /examples/bouncing-ball/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bouncing-ball" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [dependencies] 8 | diffsol = { path = "../../diffsol", features = ["diffsl-cranelift"] } 9 | nalgebra = { workspace = true } 10 | plotly = { workspace = true } -------------------------------------------------------------------------------- /examples/bouncing-ball/src/main.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{CraneliftJitModule, OdeBuilder, OdeSolverMethod, OdeSolverStopReason, Vector}; 2 | use plotly::{common::Mode, layout::Axis, layout::Layout, Plot, Scatter}; 3 | use std::fs; 4 | type M = diffsol::NalgebraMat; 5 | type CG = CraneliftJitModule; 6 | type LS = diffsol::NalgebraLU; 7 | 8 | fn main() { 9 | let e = 0.8; 10 | let problem = OdeBuilder::::new() 11 | .build_from_diffsl::( 12 | " 13 | g { 9.81 } h { 10.0 } 14 | u_i { 15 | x = h, 16 | v = 0, 17 | } 18 | F_i { 19 | v, 20 | -g, 21 | } 22 | stop { 23 | x, 24 | } 25 | ", 26 | ) 27 | .unwrap(); 28 | let mut solver = problem.bdf::().unwrap(); 29 | 30 | let mut x = Vec::new(); 31 | let mut v = Vec::new(); 32 | let mut t = Vec::new(); 33 | let final_time = 10.0; 34 | 35 | // save the initial state 36 | x.push(solver.state().y[0]); 37 | v.push(solver.state().y[1]); 38 | t.push(0.0); 39 | 40 | // solve until the final time is reached 41 | solver.set_stop_time(final_time).unwrap(); 42 | loop { 43 | match solver.step() { 44 | Ok(OdeSolverStopReason::InternalTimestep) => (), 45 | Ok(OdeSolverStopReason::RootFound(t)) => { 46 | // get the state when the event occurred 47 | let mut y = solver.interpolate(t).unwrap(); 48 | 49 | // update the velocity of the ball 50 | y[1] *= -e; 51 | 52 | // make sure the ball is above the ground 53 | y[0] = y[0].max(f64::EPSILON); 54 | 55 | // set the state to the updated state 56 | solver.state_mut().y.copy_from(&y); 57 | solver.state_mut().dy[0] = y[1]; 58 | *solver.state_mut().t = t; 59 | } 60 | Ok(OdeSolverStopReason::TstopReached) => break, 61 | Err(_) => panic!("unexpected solver error"), 62 | } 63 | x.push(solver.state().y[0]); 64 | v.push(solver.state().y[1]); 65 | t.push(solver.state().t); 66 | } 67 | let mut plot = Plot::new(); 68 | let x = Scatter::new(t.clone(), x).mode(Mode::Lines).name("x"); 69 | let v = Scatter::new(t, v).mode(Mode::Lines).name("v"); 70 | plot.add_trace(x); 71 | plot.add_trace(v); 72 | 73 | let layout = Layout::new() 74 | .x_axis(Axis::new().title("t")) 75 | .y_axis(Axis::new()); 76 | plot.set_layout(layout); 77 | let plot_html = plot.to_inline_html(Some("bouncing-ball")); 78 | fs::write("../src/primer/images/bouncing-ball.html", plot_html).expect("Unable to write file"); 79 | } 80 | -------------------------------------------------------------------------------- /examples/compartmental-models-drug-delivery/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compartmental-models-of-drug-delivery" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [dependencies] 8 | diffsol = { path = "../../diffsol", features = ["diffsl-cranelift"] } 9 | nalgebra = { workspace = true } 10 | plotly = { workspace = true } -------------------------------------------------------------------------------- /examples/compartmental-models-drug-delivery/src/main.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{CraneliftJitModule, OdeBuilder, OdeSolverMethod, OdeSolverStopReason}; 2 | use plotly::{common::Mode, layout::Axis, layout::Layout, Plot, Scatter}; 3 | use std::fs; 4 | type M = diffsol::NalgebraMat; 5 | type CG = CraneliftJitModule; 6 | type LS = diffsol::NalgebraLU; 7 | 8 | fn main() { 9 | let problem = OdeBuilder::::new() 10 | .build_from_diffsl::( 11 | " 12 | Vc { 1000.0 } Vp1 { 1000.0 } CL { 100.0 } Qp1 { 50.0 } 13 | u_i { 14 | qc = 0, 15 | qp1 = 0, 16 | } 17 | F_i { 18 | - qc / Vc * CL - Qp1 * (qc / Vc - qp1 / Vp1), 19 | Qp1 * (qc / Vc - qp1 / Vp1), 20 | } 21 | ", 22 | ) 23 | .unwrap(); 24 | let mut solver = problem.bdf::().unwrap(); 25 | let doses = vec![(0.0, 1000.0), (6.0, 1000.0), (12.0, 1000.0), (18.0, 1000.0)]; 26 | 27 | let mut q_c = Vec::new(); 28 | let mut q_p1 = Vec::new(); 29 | let mut time = Vec::new(); 30 | 31 | // apply the first dose and save the initial state 32 | solver.state_mut().y[0] = doses[0].1; 33 | q_c.push(solver.state().y[0]); 34 | q_p1.push(solver.state().y[1]); 35 | time.push(0.0); 36 | 37 | // solve and apply the remaining doses 38 | for (t, dose) in doses.into_iter().skip(1) { 39 | solver.set_stop_time(t).unwrap(); 40 | loop { 41 | let ret = solver.step(); 42 | q_c.push(solver.state().y[0]); 43 | q_p1.push(solver.state().y[1]); 44 | time.push(solver.state().t); 45 | match ret { 46 | Ok(OdeSolverStopReason::InternalTimestep) => continue, 47 | Ok(OdeSolverStopReason::TstopReached) => break, 48 | _ => panic!("unexpected solver error"), 49 | } 50 | } 51 | solver.state_mut().y[0] += dose; 52 | } 53 | let mut plot = Plot::new(); 54 | let q_c = Scatter::new(time.clone(), q_c) 55 | .mode(Mode::Lines) 56 | .name("q_c"); 57 | let q_p1 = Scatter::new(time, q_p1).mode(Mode::Lines).name("q_p1"); 58 | plot.add_trace(q_c); 59 | plot.add_trace(q_p1); 60 | 61 | let layout = Layout::new() 62 | .x_axis(Axis::new().title("t [h]")) 63 | .y_axis(Axis::new().title("amount [ng]")); 64 | plot.set_layout(layout); 65 | let plot_html = plot.to_inline_html(Some("drug-delivery")); 66 | fs::write("../src/primer/images/drug-delivery.html", plot_html).expect("Unable to write file"); 67 | } 68 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom-ode-equations" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [dependencies] 8 | diffsol = { path = "../../diffsol" } 9 | nalgebra = { workspace = true } -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/build.rs: -------------------------------------------------------------------------------- 1 | use crate::{MyEquations, M}; 2 | use diffsol::OdeBuilder; 3 | 4 | pub fn build() { 5 | OdeBuilder::::new() 6 | .p(vec![1.0, 10.0]) 7 | .build_from_eqn(MyEquations::new()) 8 | .unwrap(); 9 | } 10 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/common.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{NalgebraContext, NalgebraMat, NalgebraVec}; 2 | pub type T = f64; 3 | pub type V = NalgebraVec; 4 | pub type M = NalgebraMat; 5 | pub type C = NalgebraContext; 6 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/main.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use common::{C, M, T, V}; 3 | mod my_rhs; 4 | use my_rhs::MyRhs; 5 | mod my_mass; 6 | mod my_rhs_impl_nonlinear; 7 | mod my_rhs_impl_op; 8 | use my_mass::MyMass; 9 | mod my_init; 10 | use my_init::MyInit; 11 | mod my_root; 12 | use my_root::MyRoot; 13 | mod my_out; 14 | use my_out::MyOut; 15 | mod my_equations; 16 | use my_equations::MyEquations; 17 | mod build; 18 | mod my_equations_impl_ode_equations; 19 | mod my_equations_impl_op; 20 | use build::build; 21 | 22 | fn main() { 23 | build(); 24 | } 25 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/my_equations.rs: -------------------------------------------------------------------------------- 1 | use crate::{C, V}; 2 | use diffsol::Vector; 3 | pub struct MyEquations { 4 | pub p: V, 5 | } 6 | 7 | impl MyEquations { 8 | pub fn new() -> Self { 9 | MyEquations { 10 | p: V::zeros(2, C::default()), 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/my_equations_impl_ode_equations.rs: -------------------------------------------------------------------------------- 1 | use crate::{MyEquations, MyInit, MyMass, MyOut, MyRhs, MyRoot, V}; 2 | use diffsol::{OdeEquations, OdeEquationsRef, Vector}; 3 | 4 | impl<'a> OdeEquationsRef<'a> for MyEquations { 5 | type Rhs = MyRhs<'a>; 6 | type Mass = MyMass<'a>; 7 | type Init = MyInit<'a>; 8 | type Root = MyRoot<'a>; 9 | type Out = MyOut<'a>; 10 | } 11 | 12 | impl OdeEquations for MyEquations { 13 | fn rhs(&self) -> >::Rhs { 14 | MyRhs { p: &self.p } 15 | } 16 | fn mass(&self) -> Option<>::Mass> { 17 | Some(MyMass { p: &self.p }) 18 | } 19 | fn init(&self) -> >::Init { 20 | MyInit { p: &self.p } 21 | } 22 | fn root(&self) -> Option<>::Root> { 23 | Some(MyRoot { p: &self.p }) 24 | } 25 | fn out(&self) -> Option<>::Out> { 26 | Some(MyOut { p: &self.p }) 27 | } 28 | fn set_params(&mut self, p: &V) { 29 | self.p.copy_from(p); 30 | } 31 | fn get_params(&self, p: &mut Self::V) { 32 | p.copy_from(&self.p); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/my_equations_impl_op.rs: -------------------------------------------------------------------------------- 1 | use crate::{MyEquations, C, M, T, V}; 2 | use diffsol::{Op, Vector}; 3 | 4 | impl Op for MyEquations { 5 | type T = T; 6 | type V = V; 7 | type M = M; 8 | type C = C; 9 | fn nstates(&self) -> usize { 10 | 1 11 | } 12 | fn nout(&self) -> usize { 13 | 1 14 | } 15 | fn nparams(&self) -> usize { 16 | 2 17 | } 18 | fn context(&self) -> &Self::C { 19 | self.p.context() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/my_init.rs: -------------------------------------------------------------------------------- 1 | use crate::{C, M, T, V}; 2 | use diffsol::{ConstantOp, Op, Vector}; 3 | 4 | pub struct MyInit<'a> { 5 | pub p: &'a V, 6 | } 7 | 8 | impl Op for MyInit<'_> { 9 | type T = T; 10 | type V = V; 11 | type M = M; 12 | type C = C; 13 | fn nstates(&self) -> usize { 14 | 1 15 | } 16 | fn nout(&self) -> usize { 17 | 1 18 | } 19 | fn nparams(&self) -> usize { 20 | 0 21 | } 22 | fn context(&self) -> &Self::C { 23 | self.p.context() 24 | } 25 | } 26 | 27 | impl ConstantOp for MyInit<'_> { 28 | fn call_inplace(&self, _t: T, y: &mut V) { 29 | y[0] = 0.1; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/my_mass.rs: -------------------------------------------------------------------------------- 1 | use crate::{C, M, T, V}; 2 | use diffsol::{LinearOp, Op, Vector}; 3 | 4 | pub struct MyMass<'a> { 5 | pub p: &'a V, 6 | } 7 | 8 | impl Op for MyMass<'_> { 9 | type T = T; 10 | type V = V; 11 | type M = M; 12 | type C = C; 13 | fn nstates(&self) -> usize { 14 | 1 15 | } 16 | fn nout(&self) -> usize { 17 | 1 18 | } 19 | fn nparams(&self) -> usize { 20 | 0 21 | } 22 | fn context(&self) -> &Self::C { 23 | self.p.context() 24 | } 25 | } 26 | 27 | impl LinearOp for MyMass<'_> { 28 | fn gemv_inplace(&self, x: &V, _t: T, beta: T, y: &mut V) { 29 | y[0] = x[0] * beta; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/my_out.rs: -------------------------------------------------------------------------------- 1 | use crate::{C, M, T, V}; 2 | use diffsol::{NonLinearOp, Op, Vector}; 3 | 4 | pub struct MyOut<'a> { 5 | pub p: &'a V, 6 | } 7 | 8 | impl Op for MyOut<'_> { 9 | type T = T; 10 | type V = V; 11 | type M = M; 12 | type C = C; 13 | fn nstates(&self) -> usize { 14 | 1 15 | } 16 | fn nout(&self) -> usize { 17 | 1 18 | } 19 | fn nparams(&self) -> usize { 20 | 0 21 | } 22 | fn context(&self) -> &Self::C { 23 | self.p.context() 24 | } 25 | } 26 | 27 | impl NonLinearOp for MyOut<'_> { 28 | fn call_inplace(&self, x: &V, _t: T, y: &mut V) { 29 | y[0] = x[0]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/my_rhs.rs: -------------------------------------------------------------------------------- 1 | use crate::V; 2 | 3 | pub struct MyRhs<'a> { 4 | pub p: &'a V, 5 | } 6 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/my_rhs_impl_nonlinear.rs: -------------------------------------------------------------------------------- 1 | use crate::{MyRhs, T, V}; 2 | use diffsol::{NonLinearOp, NonLinearOpJacobian}; 3 | 4 | impl NonLinearOp for MyRhs<'_> { 5 | fn call_inplace(&self, x: &V, _t: T, y: &mut V) { 6 | y[0] = x[0] * x[0]; 7 | } 8 | } 9 | 10 | impl NonLinearOpJacobian for MyRhs<'_> { 11 | fn jac_mul_inplace(&self, x: &V, _t: T, v: &V, y: &mut V) { 12 | y[0] = v[0] * (1.0 - 2.0 * x[0]); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/my_rhs_impl_op.rs: -------------------------------------------------------------------------------- 1 | use crate::{MyRhs, C, M, T, V}; 2 | use diffsol::{Op, Vector}; 3 | 4 | impl Op for MyRhs<'_> { 5 | type T = T; 6 | type V = V; 7 | type M = M; 8 | type C = C; 9 | fn nstates(&self) -> usize { 10 | 1 11 | } 12 | fn nout(&self) -> usize { 13 | 1 14 | } 15 | fn nparams(&self) -> usize { 16 | 2 17 | } 18 | fn context(&self) -> &Self::C { 19 | self.p.context() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/custom-ode-equations/src/my_root.rs: -------------------------------------------------------------------------------- 1 | use crate::{C, M, T, V}; 2 | use diffsol::{NonLinearOp, Op, Vector}; 3 | 4 | pub struct MyRoot<'a> { 5 | pub p: &'a V, 6 | } 7 | 8 | impl Op for MyRoot<'_> { 9 | type T = T; 10 | type V = V; 11 | type M = M; 12 | type C = C; 13 | fn nstates(&self) -> usize { 14 | 1 15 | } 16 | fn nout(&self) -> usize { 17 | 1 18 | } 19 | fn nparams(&self) -> usize { 20 | 0 21 | } 22 | fn context(&self) -> &Self::C { 23 | self.p.context() 24 | } 25 | } 26 | 27 | impl NonLinearOp for MyRoot<'_> { 28 | fn call_inplace(&self, x: &V, _t: T, y: &mut V) { 29 | y[0] = x[0] - 1.0; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/electrical-circuits/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "electrical-circuits" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [dependencies] 8 | diffsol = { path = "../../diffsol", features = ["diffsl-cranelift"] } 9 | nalgebra = { workspace = true } 10 | plotly = { workspace = true } -------------------------------------------------------------------------------- /examples/electrical-circuits/src/main.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{CraneliftJitModule, MatrixCommon, OdeBuilder, OdeSolverMethod}; 2 | use plotly::{common::Mode, layout::Axis, layout::Layout, Plot, Scatter}; 3 | use std::fs; 4 | type M = diffsol::NalgebraMat; 5 | type CG = CraneliftJitModule; 6 | type LS = diffsol::NalgebraLU; 7 | 8 | fn main() { 9 | let problem = OdeBuilder::::new() 10 | .build_from_diffsl::( 11 | " 12 | R { 100.0 } L { 1.0 } C { 0.001 } V0 { 10 } omega { 100.0 } 13 | Vs { V0 * sin(omega * t) } 14 | u_i { 15 | iR = 0, 16 | iL = 0, 17 | iC = 0, 18 | V = 0, 19 | } 20 | dudt_i { 21 | diRdt = 0, 22 | diLdt = 0, 23 | diCdt = 0, 24 | dVdt = 0, 25 | } 26 | M_i { 27 | 0, 28 | diLdt, 29 | 0, 30 | dVdt, 31 | } 32 | F_i { 33 | V - R * iR, 34 | (Vs - V) / L, 35 | iL - iR - iC, 36 | iC / C, 37 | } 38 | out_i { 39 | iR, 40 | } 41 | ", 42 | ) 43 | .unwrap(); 44 | let mut solver = problem.bdf::().unwrap(); 45 | let (ys, ts) = solver.solve(1.0).unwrap(); 46 | 47 | let ir: Vec<_> = ys.inner().row(0).into_iter().copied().collect(); 48 | let t: Vec<_> = ts.into_iter().collect(); 49 | 50 | let ir = Scatter::new(t.clone(), ir).mode(Mode::Lines); 51 | 52 | let mut plot = Plot::new(); 53 | plot.add_trace(ir); 54 | 55 | let layout = Layout::new() 56 | .x_axis(Axis::new().title("t")) 57 | .y_axis(Axis::new().title("current")); 58 | plot.set_layout(layout); 59 | let plot_html = plot.to_inline_html(Some("electrical-circuit")); 60 | fs::write("../src/primer/images/electrical-circuit.html", plot_html) 61 | .expect("Unable to write file"); 62 | } 63 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "intro-logistic-closures" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [dependencies] 8 | diffsol = { path = "../../diffsol" } 9 | nalgebra = { workspace = true } 10 | plotly = { workspace = true } -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/create_solvers.rs: -------------------------------------------------------------------------------- 1 | use crate::{problem_implicit, LS}; 2 | 3 | pub fn create_solvers() { 4 | let problem = problem_implicit(); 5 | 6 | // BDF method using 7 | let _bdf = problem.bdf::().unwrap(); 8 | 9 | // Create a tr_bdf2 or esdirk34 solvers directly (both are SDIRK solvers with different tableaus) 10 | let _tr_bdf2 = problem.tr_bdf2::(); 11 | let _esdirk34 = problem.esdirk34::(); 12 | 13 | // Create a TSIT45 solver (a ERK method), this does not require a linear solver 14 | let _tsit45 = problem.tsit45(); 15 | } 16 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/create_solvers_tableau.rs: -------------------------------------------------------------------------------- 1 | use crate::{problem_implicit, LS, M}; 2 | use diffsol::Tableau; 3 | 4 | pub fn create_solvers_tableau() { 5 | let problem = problem_implicit(); 6 | 7 | // Create a SDIRK solver with a pre-defined tableau 8 | let tableau = Tableau::::tr_bdf2(problem.context().clone()); 9 | let state = problem.rk_state(&tableau).unwrap(); 10 | let _solver = problem.sdirk_solver::(state, tableau); 11 | 12 | // Create an ERK solver with a pre-defined tableau 13 | let tableau = Tableau::::tsit45(problem.context().clone()); 14 | let state = problem.rk_state(&tableau).unwrap(); 15 | let _solver = problem.explicit_rk_solver(state, tableau); 16 | } 17 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/create_solvers_uninit.rs: -------------------------------------------------------------------------------- 1 | use crate::{problem_implicit, LS}; 2 | use diffsol::{BdfState, OdeSolverState, RkState}; 3 | 4 | pub fn create_solvers_uninit() { 5 | let problem = problem_implicit(); 6 | 7 | // Create a non-initialised state and manually set the values before 8 | // creating the solver 9 | let mut state = RkState::new_without_initialise(&problem).unwrap(); 10 | // ... set the state values manually 11 | state.as_mut().y[0] = 0.1; 12 | let _solver = problem.tr_bdf2_solver::(state); 13 | 14 | // Do the same for a BDF solver 15 | let mut state = BdfState::new_without_initialise(&problem).unwrap(); 16 | state.as_mut().y[0] = 0.1; 17 | let _solver = problem.bdf_solver::(state); 18 | } 19 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/main.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{NalgebraContext, NalgebraVec}; 2 | use diffsol::{NalgebraLU, NalgebraMat}; 3 | mod problem_implicit; 4 | use problem_implicit::problem_implicit; 5 | mod problem_explicit; 6 | use problem_explicit::problem_explicit; 7 | mod problem_mass; 8 | use problem_mass::problem_mass; 9 | mod problem_root; 10 | use problem_root::problem_root; 11 | mod problem_fwd_sens; 12 | use problem_fwd_sens::problem_fwd_sens; 13 | mod problem_sparse; 14 | use problem_sparse::problem_sparse; 15 | mod solve; 16 | use solve::solve; 17 | mod solve_dense; 18 | use solve_dense::solve_dense; 19 | mod solve_interpolate; 20 | use solve_interpolate::solve_interpolate; 21 | mod solve_step; 22 | use solve_step::solve_step; 23 | mod solve_match_step; 24 | use solve_match_step::solve_match_step; 25 | mod solve_fwd_sens; 26 | use solve_fwd_sens::solve_fwd_sens; 27 | mod solve_fwd_sens_step; 28 | use solve_fwd_sens_step::solve_fwd_sens_step; 29 | mod print_jacobian; 30 | mod solve_adjoint_sens; 31 | mod solve_adjoint_sens_sum_squares; 32 | use print_jacobian::print_jacobian; 33 | mod create_solvers; 34 | use create_solvers::create_solvers; 35 | mod create_solvers_uninit; 36 | use create_solvers_uninit::create_solvers_uninit; 37 | mod create_solvers_tableau; 38 | use create_solvers_tableau::create_solvers_tableau; 39 | type M = NalgebraMat; 40 | type V = NalgebraVec; 41 | type T = f64; 42 | type LS = NalgebraLU; 43 | type C = NalgebraContext; 44 | 45 | fn main() { 46 | // 47 | // SPECIFYING THE PROBLEM 48 | // 49 | let problem = problem_fwd_sens(); 50 | let mut solver = problem.bdf_sens::().unwrap(); 51 | solve_fwd_sens(&mut solver); 52 | let mut solver = problem.bdf_sens::().unwrap(); 53 | solve_fwd_sens_step(&mut solver); 54 | let _problem = problem_root(); 55 | let _problem = problem_mass(); 56 | let _problem = problem_explicit(); 57 | let problem = problem_sparse(); 58 | print_jacobian(&problem); 59 | let problem = problem_implicit(); 60 | let mut solver = problem.bdf::().unwrap(); 61 | solve_match_step(&mut solver); 62 | 63 | // 64 | // CHOOSING A SOLVER 65 | // 66 | create_solvers(); 67 | create_solvers_uninit(); 68 | create_solvers_tableau(); 69 | 70 | // 71 | // SOLVING THE PROBLEM 72 | // 73 | let mut solver = problem.bdf::().unwrap(); 74 | solve(&mut solver); 75 | let mut solver = problem.bdf::().unwrap(); 76 | solve_dense(&mut solver); 77 | let mut solver = problem.bdf::().unwrap(); 78 | solve_step(&mut solver); 79 | let mut solver = problem.bdf::().unwrap(); 80 | solve_interpolate(&mut solver); 81 | } 82 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/print_jacobian.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{ConstantOp, Matrix, NonLinearOpJacobian, OdeEquationsImplicit, OdeSolverProblem}; 2 | 3 | pub fn print_jacobian(problem: &OdeSolverProblem) { 4 | let t0 = problem.t0; 5 | let y0 = problem.eqn.init().call(t0); 6 | let jacobian = problem.eqn.rhs().jacobian(&y0, t0); 7 | for (i, j, v) in jacobian.triplet_iter() { 8 | println!("({}, {}) = {}", i, j, v); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/problem_explicit.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{MatrixCommon, OdeBuilder}; 2 | use diffsol::{NalgebraMat, OdeEquations, OdeSolverProblem, Vector}; 3 | type M = NalgebraMat; 4 | type V = ::V; 5 | type C = ::C; 6 | type T = ::T; 7 | 8 | pub fn problem_explicit() -> OdeSolverProblem> { 9 | OdeBuilder::::new() 10 | .p(vec![1.0, 10.0]) 11 | .rhs(|x, p, _t, y| y[0] = p[0] * x[0] * (1.0 - x[0] / p[1])) 12 | .init(|_p, _t, y| y.fill(0.1), 1) 13 | .build() 14 | .unwrap() 15 | } 16 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/problem_fwd_sens.rs: -------------------------------------------------------------------------------- 1 | use crate::{C, M, T, V}; 2 | use diffsol::{OdeBuilder, OdeEquationsImplicitSens, OdeSolverProblem}; 3 | 4 | pub fn problem_fwd_sens( 5 | ) -> OdeSolverProblem> { 6 | OdeBuilder::::new() 7 | .p(vec![1.0, 10.0]) 8 | .rhs_sens_implicit( 9 | |x, p, _t, y| y[0] = p[0] * x[0] * (1.0 - x[0] / p[1]), 10 | |x, p, _t, v, y| y[0] = p[0] * v[0] * (1.0 - 2.0 * x[0] / p[1]), 11 | |x, p, _t, v, y| { 12 | y[0] = v[0] * x[0] * (1.0 - x[0] / p[1]) + v[1] * p[0] * x[0] * x[0] / (p[1] * p[1]) 13 | }, 14 | ) 15 | .init_sens(|_p, _t, y| y[0] = 0.1, |_p, _t, _v, y| y[0] = 0.0, 1) 16 | .build() 17 | .unwrap() 18 | } 19 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/problem_implicit.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{MatrixCommon, OdeBuilder}; 2 | use diffsol::{NalgebraMat, OdeEquationsImplicit, OdeSolverProblem, Vector}; 3 | type M = NalgebraMat; 4 | type V = ::V; 5 | type C = ::C; 6 | type T = ::T; 7 | 8 | pub fn problem_implicit() -> OdeSolverProblem> 9 | { 10 | OdeBuilder::::new() 11 | .p(vec![1.0, 10.0]) 12 | .rhs_implicit( 13 | |x, p, _t, y| y[0] = p[0] * x[0] * (1.0 - x[0] / p[1]), 14 | |x, p, _t, v, y| y[0] = p[0] * v[0] * (1.0 - 2.0 * x[0] / p[1]), 15 | ) 16 | .init(|_p, _t, y| y.fill(0.1), 1) 17 | .build() 18 | .unwrap() 19 | } 20 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/problem_mass.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{MatrixCommon, OdeBuilder}; 2 | use diffsol::{NalgebraMat, OdeEquationsImplicit, OdeSolverProblem, Vector}; 3 | type M = NalgebraMat; 4 | type V = ::V; 5 | type C = ::C; 6 | type T = ::T; 7 | 8 | pub fn problem_mass() -> OdeSolverProblem> { 9 | OdeBuilder::::new() 10 | .t0(0.0) 11 | .rtol(1e-6) 12 | .atol([1e-6]) 13 | .p(vec![1.0, 10.0]) 14 | .rhs_implicit( 15 | |x, p, _t, y| { 16 | y[0] = p[0] * x[0] * (1.0 - x[0] / p[1]); 17 | y[1] = x[0] - x[1]; 18 | }, 19 | |x, p, _t, v, y| { 20 | y[0] = p[0] * v[0] * (1.0 - 2.0 * x[0] / p[1]); 21 | y[1] = v[0] - v[1]; 22 | }, 23 | ) 24 | .mass(|v, _p, _t, beta, y| { 25 | y[0] = v[0] + beta * y[0]; 26 | y[1] *= beta; 27 | }) 28 | .init(|_p, _t, y| y.fill(0.1), 2) 29 | .build() 30 | .unwrap() 31 | } 32 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/problem_root.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{MatrixCommon, OdeBuilder}; 2 | use diffsol::{NalgebraMat, OdeEquationsImplicit, OdeSolverProblem}; 3 | type M = NalgebraMat; 4 | type V = ::V; 5 | type C = ::C; 6 | type T = ::T; 7 | 8 | pub fn problem_root() -> OdeSolverProblem> { 9 | OdeBuilder::::new() 10 | .t0(0.0) 11 | .rtol(1e-6) 12 | .atol([1e-6]) 13 | .p(vec![1.0, 10.0]) 14 | .rhs_implicit( 15 | |x, p, _t, y| y[0] = p[0] * x[0] * (1.0 - x[0] / p[1]), 16 | |x, p, _t, v, y| y[0] = p[0] * v[0] * (1.0 - 2.0 * x[0] / p[1]), 17 | ) 18 | .init(|_p, _t, y| y[0] = 0.1, 1) 19 | .root(|x, _p, _t, y| y[0] = x[0] - 0.5, 1) 20 | .build() 21 | .unwrap() 22 | } 23 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/problem_sparse.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{FaerSparseMat, MatrixCommon, OdeBuilder}; 2 | use diffsol::{OdeEquationsImplicit, OdeSolverProblem, Vector}; 3 | type M = FaerSparseMat; 4 | type V = ::V; 5 | type C = ::C; 6 | type T = ::T; 7 | 8 | pub fn problem_sparse() -> OdeSolverProblem> { 9 | OdeBuilder::::new() 10 | .t0(0.0) 11 | .rtol(1e-6) 12 | .atol([1e-6]) 13 | .p(vec![1.0, 10.0]) 14 | .rhs_implicit( 15 | |x, p, _t, y| { 16 | for i in 0..10 { 17 | y[i] = p[0] * x[i] * (1.0 - x[i] / p[1]); 18 | } 19 | }, 20 | |x, p, _t, v, y| { 21 | for i in 0..10 { 22 | y[i] = p[0] * v[i] * (1.0 - 2.0 * x[i] / p[1]); 23 | } 24 | }, 25 | ) 26 | .init(|_p, _t, y| y.fill(0.1), 10) 27 | .build() 28 | .unwrap() 29 | } 30 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/solve.rs: -------------------------------------------------------------------------------- 1 | use crate::{T, V}; 2 | use diffsol::{OdeEquations, OdeSolverMethod}; 3 | 4 | pub fn solve<'a, Solver, Eqn>(solver: &mut Solver) 5 | where 6 | Solver: OdeSolverMethod<'a, Eqn>, 7 | Eqn: OdeEquations + 'a, 8 | { 9 | let (_ys, _ts) = solver.solve(10.0).unwrap(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/solve_adjoint_sens.rs: -------------------------------------------------------------------------------- 1 | use crate::{C, LS, M, T, V}; 2 | use diffsol::{ 3 | AdjointOdeSolverMethod, OdeEquationsImplicitAdjoint, OdeSolverMethod, OdeSolverState, 4 | }; 5 | 6 | #[allow(dead_code)] 7 | pub fn solve_adjoint_sens<'a, Solver, Eqn>(solver: &mut Solver) 8 | where 9 | Solver: OdeSolverMethod<'a, Eqn>, 10 | Eqn: OdeEquationsImplicitAdjoint + 'a, 11 | { 12 | let (checkpointing, _soln, _times) = solver.solve_with_checkpointing(10.0, None).unwrap(); 13 | let adjoint_solver = solver 14 | .problem() 15 | .bdf_solver_adjoint::(checkpointing, Some(1)) 16 | .unwrap(); 17 | let final_state = adjoint_solver 18 | .solve_adjoint_backwards_pass(&[], &[]) 19 | .unwrap(); 20 | for (i, dgdp_i) in final_state.as_ref().sg.iter().enumerate() { 21 | println!("sens wrt parameter {}: {:?}", i, dgdp_i); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/solve_adjoint_sens_sum_squares.rs: -------------------------------------------------------------------------------- 1 | use crate::{C, LS, M, T, V}; 2 | use diffsol::{ 3 | AdjointOdeSolverMethod, DenseMatrix, Matrix, MatrixCommon, OdeEquationsImplicitAdjoint, 4 | OdeSolverMethod, OdeSolverState, Scale, VectorViewMut, 5 | }; 6 | 7 | #[allow(dead_code)] 8 | pub fn solve_adjoint_sens_sum_squares<'a, Solver, Eqn>(solver: &mut Solver) 9 | where 10 | Solver: OdeSolverMethod<'a, Eqn>, 11 | Eqn: OdeEquationsImplicitAdjoint + 'a, 12 | { 13 | let t_data = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]; 14 | let y_data = solver.solve_dense(t_data.as_slice()).unwrap(); 15 | let problem = solver.problem(); 16 | 17 | let (checkpointing, soln) = solver 18 | .solve_dense_with_checkpointing(t_data.as_slice(), None) 19 | .unwrap(); 20 | 21 | let mut g_m = M::zeros(2, t_data.len(), solver.problem().eqn().context().clone()); 22 | for j in 0..g_m.ncols() { 23 | let g_m_i = (soln.column(j) - y_data.column(j)) * Scale(2.0); 24 | g_m.column_mut(j).copy_from(&g_m_i); 25 | } 26 | 27 | let adjoint_solver = problem 28 | .bdf_solver_adjoint::(checkpointing, Some(1)) 29 | .unwrap(); 30 | let final_state = adjoint_solver 31 | .solve_adjoint_backwards_pass(t_data.as_slice(), &[&g_m]) 32 | .unwrap(); 33 | for (i, dgdp_i) in final_state.as_ref().sg.iter().enumerate() { 34 | println!("sens wrt parameter {}: {:?}", i, dgdp_i); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/solve_dense.rs: -------------------------------------------------------------------------------- 1 | use crate::{T, V}; 2 | use diffsol::{OdeEquations, OdeSolverMethod}; 3 | 4 | pub fn solve_dense<'a, Solver, Eqn>(solver: &mut Solver) 5 | where 6 | Solver: OdeSolverMethod<'a, Eqn>, 7 | Eqn: OdeEquations + 'a, 8 | { 9 | let times = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]; 10 | let _soln = solver.solve_dense(×).unwrap(); 11 | } 12 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/solve_fwd_sens.rs: -------------------------------------------------------------------------------- 1 | use crate::{M, T, V}; 2 | use diffsol::{OdeEquationsImplicitSens, SensitivitiesOdeSolverMethod}; 3 | 4 | pub fn solve_fwd_sens<'a, Solver, Eqn>(solver: &mut Solver) 5 | where 6 | Solver: SensitivitiesOdeSolverMethod<'a, Eqn>, 7 | Eqn: OdeEquationsImplicitSens + 'a, 8 | { 9 | let t_evals = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]; 10 | let (y, sens) = solver 11 | .solve_dense_sensitivities(t_evals.as_slice()) 12 | .unwrap(); 13 | println!("solution: {:?}", y); 14 | for (i, dydp_i) in sens.iter().enumerate() { 15 | println!("sens wrt parameter {}: {:?}", i, dydp_i); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/solve_fwd_sens_step.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{OdeEquationsImplicitSens, OdeSolverMethod}; 2 | 3 | pub fn solve_fwd_sens_step<'a, Solver, Eqn>(solver: &mut Solver) 4 | where 5 | Solver: OdeSolverMethod<'a, Eqn>, 6 | Eqn: OdeEquationsImplicitSens + 'a, 7 | { 8 | let t_o = 5.0; 9 | while solver.state().t < t_o { 10 | solver.step().unwrap(); 11 | } 12 | let sens_at_t_o = solver.interpolate_sens(t_o).unwrap(); 13 | let sens_at_internal_step = &solver.state().s; 14 | println!("sensitivity at t_o: {:?}", sens_at_t_o); 15 | println!("sensitivity at internal step: {:?}", sens_at_internal_step); 16 | } 17 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/solve_interpolate.rs: -------------------------------------------------------------------------------- 1 | use crate::{T, V}; 2 | use diffsol::{OdeEquations, OdeSolverMethod}; 3 | 4 | pub fn solve_interpolate<'a, Solver, Eqn>(solver: &mut Solver) 5 | where 6 | Solver: OdeSolverMethod<'a, Eqn>, 7 | Eqn: OdeEquations + 'a, 8 | { 9 | let t_o = 10.0; 10 | while solver.state().t < t_o { 11 | solver.step().unwrap(); 12 | } 13 | let _soln = solver.interpolate(t_o).unwrap(); 14 | } 15 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/solve_match_step.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{OdeEquations, OdeSolverMethod, OdeSolverStopReason}; 2 | 3 | pub fn solve_match_step<'a, Solver, Eqn>(solver: &mut Solver) 4 | where 5 | Solver: OdeSolverMethod<'a, Eqn>, 6 | Eqn: OdeEquations + 'a, 7 | { 8 | solver.set_stop_time(10.0).unwrap(); 9 | loop { 10 | match solver.step() { 11 | Ok(OdeSolverStopReason::InternalTimestep) => continue, 12 | Ok(OdeSolverStopReason::TstopReached) => break, 13 | Ok(OdeSolverStopReason::RootFound(_t)) => break, 14 | Err(e) => panic!("Solver failed to converge: {}", e), 15 | } 16 | } 17 | println!("Solver stopped at time: {}", solver.state().t); 18 | } 19 | -------------------------------------------------------------------------------- /examples/intro-logistic-closures/src/solve_step.rs: -------------------------------------------------------------------------------- 1 | use diffsol::OdeSolverMethod; 2 | 3 | pub fn solve_step<'a, Solver, Eqn>(solver: &mut Solver) 4 | where 5 | Solver: OdeSolverMethod<'a, Eqn>, 6 | Eqn: diffsol::OdeEquations + 'a, 7 | { 8 | while solver.state().t < 10.0 { 9 | if solver.step().is_err() { 10 | break; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/intro-logistic-diffsl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "intro-logistic-diffsl" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [dependencies] 8 | diffsol = { path = "../../diffsol", features = ["diffsl-cranelift"] } 9 | nalgebra = { workspace = true } 10 | plotly = { workspace = true } -------------------------------------------------------------------------------- /examples/intro-logistic-diffsl/src/main.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{OdeBuilder, OdeSolverMethod}; 2 | type M = diffsol::NalgebraMat; 3 | type CG = diffsol::CraneliftJitModule; 4 | type LS = diffsol::NalgebraLU; 5 | 6 | fn main() { 7 | let problem = OdeBuilder::::new() 8 | .rtol(1e-6) 9 | .p([1.0, 10.0]) 10 | .build_from_diffsl::( 11 | " 12 | in = [r, k] 13 | r { 1.0 } 14 | k { 1.0 } 15 | u { 0.1 } 16 | F { r * u * (1.0 - u / k) } 17 | ", 18 | ) 19 | .unwrap(); 20 | let mut solver = problem.bdf::().unwrap(); 21 | let t = 0.4; 22 | let _soln = solver.solve(t).unwrap(); 23 | } 24 | -------------------------------------------------------------------------------- /examples/mass-spring-fitting-adjoint/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mass-spring-fitting-adjoint" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [features] 8 | diffsl-llvm15 = ["diffsol/diffsl-llvm15", "diffsl-llvm"] 9 | diffsl-llvm16 = ["diffsol/diffsl-llvm16", "diffsl-llvm"] 10 | diffsl-llvm17 = ["diffsol/diffsl-llvm17", "diffsl-llvm"] 11 | diffsl-llvm18 = ["diffsol/diffsl-llvm18", "diffsl-llvm"] 12 | diffsl-llvm = [] 13 | 14 | [dependencies] 15 | diffsol = { path = "../../diffsol" } 16 | nalgebra = { workspace = true } 17 | plotly = { workspace = true } 18 | argmin = { workspace = true } 19 | argmin-math = { workspace = true } 20 | argmin-observer-slog = { workspace = true } -------------------------------------------------------------------------------- /examples/mass-spring-fitting-adjoint/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "diffsl-llvm")] 2 | mod main_llvm; 3 | 4 | fn main() { 5 | #[cfg(feature = "diffsl-llvm")] 6 | main_llvm::main(); 7 | } 8 | -------------------------------------------------------------------------------- /examples/neural-ode-weather-prediction/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "neural-ode-weather-prediction" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [dependencies] 8 | diffsol = { path = "../../diffsol" } 9 | nalgebra = { workspace = true } 10 | plotly = { workspace = true } 11 | ort = { workspace = true } 12 | ort-sys = { workspace = true } 13 | ndarray = "0.16.1" 14 | csv = "1.3.1" 15 | rand = "0.9.0" -------------------------------------------------------------------------------- /examples/neural-ode-weather-prediction/requirements.txt: -------------------------------------------------------------------------------- 1 | jax 2 | jaxlib 3 | equinox 4 | tensorflow 5 | tf2onnx 6 | pydot 7 | pandas 8 | numpy==1.26.4 9 | -------------------------------------------------------------------------------- /examples/neural-ode-weather-prediction/src/data/.gitignore: -------------------------------------------------------------------------------- 1 | Daily*.csv -------------------------------------------------------------------------------- /examples/neural-ode-weather-prediction/src/data/pre-process.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | import matplotlib.pyplot as plt 4 | import pathlib 5 | 6 | # load data 7 | base_dir = pathlib.Path(__file__).parent.resolve() 8 | test = pd.read_csv(os.path.join(base_dir, f'{base_dir}/DailyDelhiClimateTest.csv')) 9 | train = pd.read_csv(os.path.join(base_dir, f'{base_dir}/DailyDelhiClimateTrain.csv')) 10 | 11 | # concatenate data 12 | data = pd.concat([train, test]) 13 | 14 | # average measurements into months 15 | data['date'] = pd.to_datetime(data['date']) 16 | data = data.set_index('date') 17 | data = data.resample('ME').mean() 18 | 19 | # change data to float indicating month since the start of the data 20 | data['month'] = 12.0 * (data.index.year - data.index.year.min()) + data.index.month - 1.0 21 | data = data.set_index('month') 22 | 23 | # normalize data (meantemp,humidity,wind_speed,meanpressure) 24 | data["meantemp"] = (data["meantemp"] - data["meantemp"].mean()) / data["meantemp"].std() 25 | data["humidity"] = (data["humidity"] - data["humidity"].mean()) / data["humidity"].std() 26 | data["wind_speed"] = (data["wind_speed"] - data["wind_speed"].mean()) / data["wind_speed"].std() 27 | data["meanpressure"] = (data["meanpressure"] - data["meanpressure"].mean()) / data["meanpressure"].std() 28 | 29 | # save data 30 | data.to_csv(os.path.join(base_dir, f'{base_dir}/MonthlyDelhiClimate.csv')) 31 | 32 | # plot data save to file 33 | data.plot() 34 | plt.savefig(os.path.join(base_dir, f'{base_dir}/monthly_delhi_climate.png')) 35 | -------------------------------------------------------------------------------- /examples/neural-ode-weather-prediction/src/model/.gitignore: -------------------------------------------------------------------------------- 1 | *.onnx -------------------------------------------------------------------------------- /examples/neural-ode-weather-prediction/src/model/model.py: -------------------------------------------------------------------------------- 1 | import jax 2 | import jax.numpy as jnp 3 | import equinox as eqx 4 | import functools as ft 5 | import tensorflow as tf 6 | from jax.experimental import jax2tf 7 | from jax.flatten_util import ravel_pytree 8 | import tf2onnx 9 | from onnx.tools.net_drawer import GetOpNodeProducer, GetPydotGraph # noqa: E402 10 | import os # noqa: E402 11 | 12 | 13 | def draw_graph(model_proto, filename): 14 | pydot_graph = GetPydotGraph( 15 | model_proto.graph, 16 | name=model_proto.graph.name, 17 | rankdir="LR", 18 | node_producer=GetOpNodeProducer("docstring"), 19 | ) 20 | pydot_graph.write_dot("graph.dot") 21 | os.system("dot -O -Tpng graph.dot -o " + filename) 22 | os.system("rm graph.dot") 23 | 24 | 25 | def to_onnx(model, inputs, filename): 26 | sig = [tf.TensorSpec(inpt[0].shape, inpt[0].dtype, name=inpt[1]) for inpt in inputs] 27 | inference_tf = jax2tf.convert(model, enable_xla=False) 28 | inference_tf = tf.function(inference_tf, autograph=False) 29 | inference_onnx = tf2onnx.convert.from_function(inference_tf, input_signature=sig) 30 | model_proto, _external_tensor_storage = inference_onnx 31 | with open(filename, "wb") as f: 32 | f.write(model_proto.SerializeToString()) 33 | return model_proto 34 | 35 | 36 | data_dim = 4 37 | 38 | 39 | class NeuralNetwork(eqx.Module): 40 | layers: list 41 | 42 | def __init__(self, data_dim, key): 43 | key1, key2, key3 = jax.random.split(key, 3) 44 | self.layers = [ 45 | eqx.nn.Linear(data_dim, 64, key=key1), 46 | eqx.nn.Linear(64, 32, key=key2), 47 | eqx.nn.Linear(32, data_dim, key=key3), 48 | ] 49 | 50 | def __call__(self, x): 51 | x = jax.nn.silu(self.layers[0](x)) # Swish = SiLU 52 | x = jax.nn.silu(self.layers[1](x)) 53 | x = self.layers[2](x) 54 | return x 55 | 56 | 57 | key = jax.random.PRNGKey(0) 58 | model = NeuralNetwork(data_dim=data_dim, key=key) 59 | y = jnp.zeros((data_dim,)) 60 | v = jnp.zeros((data_dim,)) 61 | params, static = eqx.partition(model, eqx.is_array) 62 | p, unravel_params = ravel_pytree(params) 63 | 64 | 65 | def rhs(p, y): 66 | params = unravel_params(p) 67 | model = eqx.combine(params, static) 68 | return model(y) 69 | 70 | 71 | def rhs_jac_mul(p, y, v): 72 | return jax.jvp(ft.partial(rhs, p), (y,), (v,))[1] 73 | 74 | 75 | def rhs_jac_transpose_mul(p, y, v): 76 | return -jax.vjp(ft.partial(rhs, p), y)[1](v)[0] 77 | 78 | 79 | def rhs_sens_transpose_mul(p, y, v): 80 | return -jax.vjp(ft.partial(rhs, y=y), p)[1](v)[0] 81 | 82 | 83 | # test rhs_sens_transpose_mul 84 | p = jax.random.normal(key, (p.shape[0],)) 85 | p = jnp.zeros((p.shape[0],)) 86 | y = jax.random.normal(key, (data_dim,)) 87 | v = jax.random.normal(key, (data_dim,)) 88 | print(rhs_sens_transpose_mul(p, y, v)) 89 | 90 | 91 | rhs_proto = to_onnx(rhs, ((p, "p"), (y, "y")), "rhs.onnx") 92 | to_onnx(rhs_jac_mul, ((p, "p"), (y, "y"), (v, "v")), "rhs_jac_mul.onnx") 93 | to_onnx( 94 | rhs_jac_transpose_mul, ((p, "p"), (y, "y"), (v, "v")), "rhs_jac_transpose_mul.onnx" 95 | ) 96 | to_onnx( 97 | rhs_sens_transpose_mul, 98 | ((p, "p"), (y, "y"), (v, "v")), 99 | "rhs_sens_transpose_mul.onnx", 100 | ) 101 | -------------------------------------------------------------------------------- /examples/pde-heat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pde-heat" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [features] 8 | diffsl-llvm15 = ["diffsol/diffsl-llvm15"] 9 | diffsl-llvm16 = ["diffsol/diffsl-llvm16"] 10 | diffsl-llvm17 = ["diffsol/diffsl-llvm17"] 11 | diffsl-llvm18 = ["diffsol/diffsl-llvm18"] 12 | 13 | [dependencies] 14 | diffsol = { path = "../../diffsol", features = ["diffsl-cranelift"] } 15 | faer = { workspace = true } 16 | plotly = { workspace = true } -------------------------------------------------------------------------------- /examples/pde-heat/src/main.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{ 2 | CraneliftJitModule, FaerSparseLU, FaerSparseMat, MatrixCommon, OdeBuilder, OdeSolverMethod, 3 | }; 4 | use plotly::{ 5 | layout::{Axis, Layout}, 6 | Plot, Surface, 7 | }; 8 | use std::fs; 9 | 10 | type M = FaerSparseMat; 11 | type LS = FaerSparseLU; 12 | type CG = CraneliftJitModule; 13 | 14 | fn main() { 15 | let problem = OdeBuilder::::new() 16 | .build_from_diffsl::( 17 | " 18 | D { 1.0 } 19 | h { 1.0 } 20 | g { 0.0 } 21 | m { 1.0 } 22 | A_ij { 23 | (0..20, 1..21): 1.0, 24 | (0..21, 0..21): -2.0, 25 | (1..21, 0..20): 1.0, 26 | } 27 | b_i { 28 | (0): g, 29 | (1:20): 0.0, 30 | (20): g, 31 | } 32 | u_i { 33 | (0:5): g, 34 | (5:15): g + m, 35 | (15:21): g, 36 | } 37 | heat_i { A_ij * u_j } 38 | F_i { 39 | D * (heat_i + b_i) / (h * h) 40 | }", 41 | ) 42 | .unwrap(); 43 | let times = (0..50).map(|i| i as f64).collect::>(); 44 | let mut solver = problem.bdf::().unwrap(); 45 | let sol = solver.solve_dense(×).unwrap(); 46 | 47 | let x = (0..21).map(|i| i as f64).collect::>(); 48 | let y = times; 49 | let z = sol 50 | .inner() 51 | .col_iter() 52 | .map(|v| v.iter().copied().collect::>()) 53 | .collect::>>(); 54 | let trace = Surface::new(z).x(x).y(y); 55 | let mut plot = Plot::new(); 56 | plot.add_trace(trace); 57 | let layout = Layout::new() 58 | .x_axis(Axis::new().title("x")) 59 | .y_axis(Axis::new().title("t")) 60 | .z_axis(Axis::new().title("u")); 61 | plot.set_layout(layout); 62 | let plot_html = plot.to_inline_html(Some("heat-equation")); 63 | fs::write("book/src/primer/images/heat-equation.html", plot_html) 64 | .expect("Unable to write file"); 65 | } 66 | -------------------------------------------------------------------------------- /examples/physics-based-battery-simulation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "physics-based-batter-simulation" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [dependencies] 8 | diffsol = { path = "../../diffsol", features = ["diffsl-cranelift"] } 9 | nalgebra = { workspace = true } 10 | plotly = { workspace = true } -------------------------------------------------------------------------------- /examples/physics-based-battery-simulation/src/main.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{ 2 | CraneliftJitModule, FaerSparseLU, FaerSparseMat, FaerVec, NonLinearOp, OdeBuilder, 3 | OdeEquations, OdeSolverMethod, OdeSolverStopReason, Vector, 4 | }; 5 | use plotly::{common::Mode, layout::Axis, layout::Layout, Plot, Scatter}; 6 | use std::fs; 7 | type M = FaerSparseMat; 8 | type V = FaerVec; 9 | type LS = FaerSparseLU; 10 | type CG = CraneliftJitModule; 11 | 12 | fn main() { 13 | let file = std::fs::read_to_string("../src/primer/src/spm.ds").unwrap(); 14 | 15 | let mut problem = OdeBuilder::::new() 16 | .p([1.0]) 17 | .build_from_diffsl::(&file) 18 | .unwrap(); 19 | let currents = vec![0.6, 0.8, 1.0, 1.2, 1.4]; 20 | let final_time = 3600.0; 21 | let delta_t = 3.0; 22 | 23 | let mut plot = Plot::new(); 24 | for current in currents { 25 | problem 26 | .eqn 27 | .set_params(&V::from_vec(vec![current], problem.context().clone())); 28 | 29 | let mut solver = problem.bdf::().unwrap(); 30 | let mut v = Vec::new(); 31 | let mut t = Vec::new(); 32 | 33 | // save the initial output 34 | let mut out = problem 35 | .eqn 36 | .out() 37 | .unwrap() 38 | .call(solver.state().y, solver.state().t); 39 | v.push(out[0]); 40 | t.push(0.0); 41 | 42 | // solve until the final time is reached 43 | // or we reach the stop condition 44 | solver.set_stop_time(final_time).unwrap(); 45 | let mut next_output_time = delta_t; 46 | let mut finished = false; 47 | while !finished { 48 | let curr_t = match solver.step() { 49 | Ok(OdeSolverStopReason::InternalTimestep) => solver.state().t, 50 | Ok(OdeSolverStopReason::RootFound(t)) => { 51 | finished = true; 52 | t 53 | } 54 | Ok(OdeSolverStopReason::TstopReached) => { 55 | finished = true; 56 | final_time 57 | } 58 | Err(_) => panic!("unexpected solver error"), 59 | }; 60 | while curr_t > next_output_time { 61 | let y = solver.interpolate(next_output_time).unwrap(); 62 | problem 63 | .eqn 64 | .out() 65 | .unwrap() 66 | .call_inplace(&y, next_output_time, &mut out); 67 | v.push(out[0]); 68 | t.push(next_output_time); 69 | next_output_time += delta_t; 70 | } 71 | } 72 | 73 | let voltage = Scatter::new(t, v) 74 | .mode(Mode::Lines) 75 | .name(format!("current = {} A", current)); 76 | plot.add_trace(voltage); 77 | } 78 | 79 | let layout = Layout::new() 80 | .x_axis(Axis::new().title("t [sec]")) 81 | .y_axis(Axis::new().title("voltage [V]")); 82 | plot.set_layout(layout); 83 | let plot_html = plot.to_inline_html(Some("battery-simulation")); 84 | fs::write("../src/primer/images/battery-simulation.html", plot_html) 85 | .expect("Unable to write file"); 86 | } 87 | -------------------------------------------------------------------------------- /examples/population-dynamics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "population-dynamics" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [dependencies] 8 | diffsol = { path = "../../diffsol", features = ["diffsl-cranelift"] } 9 | nalgebra = { workspace = true } 10 | plotly = { workspace = true } -------------------------------------------------------------------------------- /examples/population-dynamics/src/main.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{ 2 | CraneliftJitModule, MatrixCommon, NalgebraVec, OdeBuilder, OdeEquations, OdeSolverMethod, 3 | Vector, 4 | }; 5 | use plotly::{common::Mode, layout::Axis, layout::Layout, Plot, Scatter}; 6 | use std::fs; 7 | type M = diffsol::NalgebraMat; 8 | type LS = diffsol::NalgebraLU; 9 | type CG = CraneliftJitModule; 10 | 11 | fn main() { 12 | solve(); 13 | phase_plane(); 14 | } 15 | 16 | fn solve() { 17 | let problem = OdeBuilder::::new() 18 | .build_from_diffsl::( 19 | " 20 | a { 2.0/3.0 } b { 4.0/3.0 } c { 1.0 } d { 1.0 } 21 | u_i { 22 | y1 = 1, 23 | y2 = 1, 24 | } 25 | F_i { 26 | a * y1 - b * y1 * y2, 27 | c * y1 * y2 - d * y2, 28 | } 29 | ", 30 | ) 31 | .unwrap(); 32 | let mut solver = problem.bdf::().unwrap(); 33 | let (ys, ts) = solver.solve(40.0).unwrap(); 34 | 35 | let prey: Vec<_> = ys.inner().row(0).into_iter().copied().collect(); 36 | let predator: Vec<_> = ys.inner().row(1).into_iter().copied().collect(); 37 | let time: Vec<_> = ts.into_iter().collect(); 38 | 39 | let prey = Scatter::new(time.clone(), prey) 40 | .mode(Mode::Lines) 41 | .name("Prey"); 42 | let predator = Scatter::new(time, predator) 43 | .mode(Mode::Lines) 44 | .name("Predator"); 45 | 46 | let mut plot = Plot::new(); 47 | plot.add_trace(prey); 48 | plot.add_trace(predator); 49 | 50 | let layout = Layout::new() 51 | .x_axis(Axis::new().title("t")) 52 | .y_axis(Axis::new().title("population")); 53 | plot.set_layout(layout); 54 | let plot_html = plot.to_inline_html(Some("prey-predator")); 55 | fs::write("book/src/primer/images/prey-predator.html", plot_html) 56 | .expect("Unable to write file"); 57 | } 58 | 59 | fn phase_plane() { 60 | let mut problem = OdeBuilder::::new() 61 | .p([1.0]) 62 | .build_from_diffsl::( 63 | " 64 | in = [ y0 ] 65 | y0 { 1.0 } 66 | a { 2.0/3.0 } b { 4.0/3.0 } c { 1.0 } d { 1.0 } 67 | u_i { 68 | y1 = y0, 69 | y2 = y0, 70 | } 71 | F_i { 72 | a * y1 - b * y1 * y2, 73 | c * y1 * y2 - d * y2, 74 | } 75 | ", 76 | ) 77 | .unwrap(); 78 | 79 | let mut plot = Plot::new(); 80 | for y0 in (1..6).map(f64::from) { 81 | let p = NalgebraVec::from_element(1, y0, problem.context().clone()); 82 | problem.eqn_mut().set_params(&p); 83 | 84 | let mut solver = problem.bdf::().unwrap(); 85 | let (ys, _ts) = solver.solve(40.0).unwrap(); 86 | 87 | let prey: Vec<_> = ys.inner().row(0).into_iter().copied().collect(); 88 | let predator: Vec<_> = ys.inner().row(1).into_iter().copied().collect(); 89 | 90 | let phase = Scatter::new(prey, predator) 91 | .mode(Mode::Lines) 92 | .name(format!("y0 = {}", y0)); 93 | plot.add_trace(phase); 94 | } 95 | 96 | let layout = Layout::new() 97 | .x_axis(Axis::new().title("x")) 98 | .y_axis(Axis::new().title("y")); 99 | plot.set_layout(layout); 100 | let plot_html = plot.to_inline_html(Some("prey-predator2")); 101 | fs::write("book/src/primer/images/prey-predator2.html", plot_html) 102 | .expect("Unable to write file"); 103 | } 104 | -------------------------------------------------------------------------------- /examples/predator-prey-fitting-forward/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "predator-prey-fitting-forward" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [features] 8 | diffsl-llvm15 = ["diffsol/diffsl-llvm15"] 9 | diffsl-llvm16 = ["diffsol/diffsl-llvm16"] 10 | diffsl-llvm17 = ["diffsol/diffsl-llvm17"] 11 | diffsl-llvm18 = ["diffsol/diffsl-llvm18"] 12 | diffsl-llvm = [] 13 | 14 | [dependencies] 15 | diffsol = { path = "../../diffsol" } 16 | nalgebra = { workspace = true } 17 | plotly = { workspace = true } 18 | argmin = { workspace = true } 19 | argmin-math = { workspace = true } 20 | argmin-observer-slog = { workspace = true } -------------------------------------------------------------------------------- /examples/predator-prey-fitting-forward/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "diffsl-llvm")] 2 | mod main_llvm; 3 | 4 | fn main() { 5 | #[cfg(feature = "diffsl-llvm")] 6 | main_llvm::main(); 7 | } 8 | -------------------------------------------------------------------------------- /examples/predator-prey-fitting-forward/src/main_llvm.rs: -------------------------------------------------------------------------------- 1 | use argmin::{ 2 | core::{observers::ObserverMode, CostFunction, Executor, Gradient}, 3 | solver::{linesearch::MoreThuenteLineSearch, quasinewton::LBFGS}, 4 | }; 5 | use argmin_observer_slog::SlogLogger; 6 | use diffsol::{ 7 | DiffSl, OdeBuilder, OdeEquations, OdeSolverMethod, OdeSolverProblem, 8 | SensitivitiesOdeSolverMethod, 9 | }; 10 | use nalgebra::{DMatrix, DVector}; 11 | use std::cell::RefCell; 12 | 13 | type M = DMatrix; 14 | type V = DVector; 15 | type T = f64; 16 | type LS = diffsol::NalgebraLU; 17 | type CG = diffsol::LlvmModule; 18 | type Eqn = DiffSl; 19 | 20 | struct Problem { 21 | ys_data: M, 22 | ts_data: Vec, 23 | problem: RefCell>, 24 | } 25 | 26 | impl CostFunction for Problem { 27 | type Output = T; 28 | type Param = Vec; 29 | 30 | fn cost(&self, param: &Self::Param) -> Result { 31 | let mut problem = self.problem.borrow_mut(); 32 | problem.eqn_mut().set_params(&V::from_vec(param.clone())); 33 | let mut solver = problem.bdf::().unwrap(); 34 | let ys = match solver.solve_dense(&self.ts_data) { 35 | Ok(ys) => ys, 36 | Err(_) => return Ok(f64::MAX / 1000.), 37 | }; 38 | let loss = ys 39 | .column_iter() 40 | .zip(self.ys_data.column_iter()) 41 | .map(|(a, b)| (a - b).norm_squared()) 42 | .sum::(); 43 | Ok(loss) 44 | } 45 | } 46 | 47 | impl Gradient for Problem { 48 | type Gradient = Vec; 49 | type Param = Vec; 50 | 51 | fn gradient(&self, param: &Self::Param) -> Result { 52 | let mut problem = self.problem.borrow_mut(); 53 | problem.eqn_mut().set_params(&V::from_vec(param.clone())); 54 | let mut solver = problem.bdf_sens::().unwrap(); 55 | let (ys, sens) = match solver.solve_dense_sensitivities(&self.ts_data) { 56 | Ok((ys, sens)) => (ys, sens), 57 | Err(_) => return Ok(vec![f64::MAX / 1000.; param.len()]), 58 | }; 59 | let dlossdp = sens 60 | .into_iter() 61 | .map(|s| { 62 | s.column_iter() 63 | .zip(ys.column_iter().zip(self.ys_data.column_iter())) 64 | .map(|(si, (yi, di))| 2.0 * (yi - di).dot(&si)) 65 | .sum::() 66 | }) 67 | .collect::>(); 68 | Ok(dlossdp) 69 | } 70 | } 71 | 72 | pub fn main() { 73 | let eqn = DiffSl::::compile( 74 | " 75 | in = [ b, d ] 76 | a { 2.0/3.0 } b { 4.0/3.0 } c { 1.0 } d { 1.0 } x0 { 1.0 } y0 { 1.0 } 77 | u_i { 78 | y1 = x0, 79 | y2 = y0, 80 | } 81 | F_i { 82 | a * y1 - b * y1 * y2, 83 | c * y1 * y2 - d * y2, 84 | } 85 | ", 86 | ) 87 | .unwrap(); 88 | 89 | let (b_true, d_true) = (4.0 / 3.0, 1.0); 90 | let t_data = (0..101) 91 | .map(|i| f64::from(i) * 40. / 100.) 92 | .collect::>(); 93 | let problem = OdeBuilder::::new() 94 | .p([b_true, d_true]) 95 | .sens_atol([1e-6]) 96 | .sens_rtol(1e-6) 97 | .build_from_eqn(eqn) 98 | .unwrap(); 99 | let mut solver = problem.bdf::().unwrap(); 100 | let ys_data = solver.solve_dense(&t_data).unwrap(); 101 | 102 | let cost = Problem { 103 | ys_data, 104 | ts_data: t_data, 105 | problem: RefCell::new(problem), 106 | }; 107 | 108 | let init_param = vec![b_true - 0.1, d_true - 0.1]; 109 | 110 | let linesearch = MoreThuenteLineSearch::new().with_c(1e-4, 0.9).unwrap(); 111 | let solver = LBFGS::new(linesearch, 7); 112 | let res = Executor::new(cost, solver) 113 | .configure(|state| state.param(init_param)) 114 | .add_observer(SlogLogger::term(), ObserverMode::Always) 115 | .run() 116 | .unwrap(); 117 | 118 | // print result 119 | println!("{}", res); 120 | // Best parameter vector 121 | let best = res.state().best_param.as_ref().unwrap(); 122 | println!("Best parameter vector: {:?}", best); 123 | println!("True parameter vector: {:?}", vec![b_true, d_true]); 124 | } 125 | -------------------------------------------------------------------------------- /examples/spring-mass-system/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spring-mass-system" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | 7 | [dependencies] 8 | diffsol = { path = "../../diffsol", features = ["diffsl-cranelift"] } 9 | nalgebra = { workspace = true } 10 | plotly = { workspace = true } -------------------------------------------------------------------------------- /examples/spring-mass-system/src/main.rs: -------------------------------------------------------------------------------- 1 | use diffsol::{CraneliftJitModule, MatrixCommon, OdeBuilder, OdeSolverMethod}; 2 | use plotly::{common::Mode, layout::Axis, layout::Layout, Plot, Scatter}; 3 | use std::fs; 4 | type M = diffsol::NalgebraMat; 5 | type CG = CraneliftJitModule; 6 | type LS = diffsol::NalgebraLU; 7 | 8 | fn main() { 9 | let problem = OdeBuilder::::new() 10 | .build_from_diffsl::( 11 | " 12 | k { 1.0 } m { 1.0 } c { 0.1 } 13 | u_i { 14 | x = 1, 15 | v = 0, 16 | } 17 | F_i { 18 | v, 19 | -k/m * x - c/m * v, 20 | } 21 | ", 22 | ) 23 | .unwrap(); 24 | let mut solver = problem.bdf::().unwrap(); 25 | let (ys, ts) = solver.solve(40.0).unwrap(); 26 | 27 | let x: Vec<_> = ys.inner().row(0).into_iter().copied().collect(); 28 | let time: Vec<_> = ts.into_iter().collect(); 29 | 30 | let x_line = Scatter::new(time.clone(), x).mode(Mode::Lines); 31 | 32 | let mut plot = Plot::new(); 33 | plot.add_trace(x_line); 34 | 35 | let layout = Layout::new() 36 | .x_axis(Axis::new().title("t")) 37 | .y_axis(Axis::new().title("x")); 38 | plot.set_layout(layout); 39 | let plot_html = plot.to_inline_html(Some("sping-mass-system")); 40 | fs::write("../src/primer/images/spring-mass-system.html", plot_html) 41 | .expect("Unable to write file"); 42 | } 43 | --------------------------------------------------------------------------------