(1) adaptive variable time steps
(2) fixed time steps
(3) exact solution
A <-> B,¶taken to equilibrium.
This is a continuation of the experiments react_2_a (fixed time steps) and react_2_b (adaptive variable time steps)
Background: please see experiments react_2_a and react_2_b
LAST_REVISED = "Jan. 3, 2026"
LIFE123_VERSION = "1.0.0rc7" # Library version this experiment is based on
#import set_path # Using MyBinder? Uncomment this before running the next cell!
#import sys
#sys.path.append("C:/some_path/my_env_or_install") # CHANGE to the folder containing your venv or libraries installation!
# NOTE: If any of the imports below can't find a module, uncomment the lines above, or try: import set_path
from life123 import check_version, ChemData, UniformCompartment
import numpy as np
import plotly.graph_objects as go
from life123 import ReactionKinetics, ReactionRegistry, PlotlyHelper
check_version(LIFE123_VERSION)
OK
# Set up the reactions and their chemicals (common for all the simulations below)
rxns = ReactionRegistry()
rxns.add_reaction(reactants="A", products="B", kF=3., kR=2.) # ELementary reaction A <-> B
rxns.describe_reactions()
add_reaction(): detected reaction type `ReactionUnimolecular` Number of reactions: 1 0: A <-> B Elementary Unimolecular reaction (kF = 3 / kR = 2 / K = 1.5) Chemicals involved in the above reactions: ['A', 'B']
We'll do this part first, because the number of steps taken is unpredictable;
we'll note that number, and in Part 2 we'll do exactly that same number of fixed steps
dynamics_variable = UniformCompartment(reactions=rxns, preset="mid")
# Initial concentrations of all the chemicals
dynamics_variable.set_conc({"A": 10., "B": 50.})
dynamics_variable.describe_state()
SYSTEM STATE at Time t = 0: 2 species: Species 0 (A). Conc: 10.0 Species 1 (B). Conc: 50.0 Chemicals involved in reactions: ['B', 'A']
dynamics_variable.single_compartment_react(initial_step=0.1, target_end_time=1.2,
variable_steps=True)
# Specifying variable_steps=True for emphasis; it's the default
19 total variable step(s) taken in 0.042 sec
Number of step re-do's because of elective soft aborts: 2
Norm usage: {'norm_A': 17, 'norm_B': 15, 'norm_C': 15, 'norm_D': 15}
System Time is now: 1.2268
In part 2, we'll remember that it took 19 steps
dynamics_variable.get_history() # The system's history, saved during the run of single_compartment_react()
| SYSTEM TIME | A | B | step | caption | |
|---|---|---|---|---|---|
| 0 | 0.000000 | 10.000000 | 50.000000 | Set concentration | |
| 1 | 0.016000 | 11.120000 | 48.880000 | 1 | 1st reaction step |
| 2 | 0.032000 | 12.150400 | 47.849600 | 2 | |
| 3 | 0.048000 | 13.098368 | 46.901632 | 3 | |
| 4 | 0.067200 | 14.144925 | 45.855075 | 4 | |
| 5 | 0.086400 | 15.091012 | 44.908988 | 5 | |
| 6 | 0.109440 | 16.117327 | 43.882673 | 6 | |
| 7 | 0.132480 | 17.025411 | 42.974589 | 7 | |
| 8 | 0.160128 | 17.989578 | 42.010422 | 8 | |
| 9 | 0.193306 | 18.986635 | 41.013365 | 9 | |
| 10 | 0.233119 | 19.984624 | 40.015376 | 10 | |
| 11 | 0.280894 | 20.943812 | 39.056188 | 11 | |
| 12 | 0.338225 | 21.819882 | 38.180118 | 12 | |
| 13 | 0.407022 | 22.569810 | 37.430190 | 13 | |
| 14 | 0.489579 | 23.160168 | 36.839832 | 14 | |
| 15 | 0.588647 | 23.576169 | 36.423831 | 15 | |
| 16 | 0.707528 | 23.828097 | 36.171903 | 16 | |
| 17 | 0.850186 | 23.950713 | 36.049287 | 17 | |
| 18 | 1.021375 | 23.992900 | 36.007100 | 18 | |
| 19 | 1.226802 | 24.000193 | 35.999807 | 19 | last reaction step |
dynamics_variable.plot_history(colors=['darkturquoise', 'green'], show_intervals=True)
That resulted from passing the flag variable_steps=True to single_compartment_react()
The fixed time step is chosen to attain the same total number of data points (i.e. 19) as obtained with the variable time steps of part 1
dynamics_fixed = UniformCompartment(reactions=rxns) # Re-use the chemicals and reactions of part 1
# Initial concentrations of all the chemicals
dynamics_fixed.set_conc({"A": 10., "B": 50.})
dynamics_fixed.describe_state()
SYSTEM STATE at Time t = 0: 2 species: Species 0 (A). Conc: 10.0 Species 1 (B). Conc: 50.0 Chemicals involved in reactions: ['B', 'A']
# Matching the total number of steps to the earlier, variable-step simulation
dynamics_fixed.single_compartment_react(n_steps=19, target_end_time=1.2,
variable_steps=False)
19 total fixed step(s) taken in 0.037 sec
dynamics_fixed.get_history() # The system's history, saved during the run of single_compartment_react()
| SYSTEM TIME | A | B | step | caption | |
|---|---|---|---|---|---|
| 0 | 0.000000 | 10.000000 | 50.000000 | Set concentration | |
| 1 | 0.063158 | 14.421053 | 45.578947 | 1 | 1st reaction step |
| 2 | 0.126316 | 17.445983 | 42.554017 | 2 | |
| 3 | 0.189474 | 19.515673 | 40.484327 | 3 | |
| 4 | 0.252632 | 20.931776 | 39.068224 | 4 | |
| 5 | 0.315789 | 21.900689 | 38.099311 | 5 | |
| 6 | 0.378947 | 22.563629 | 37.436371 | 6 | |
| 7 | 0.442105 | 23.017220 | 36.982780 | 7 | |
| 8 | 0.505263 | 23.327572 | 36.672428 | 8 | |
| 9 | 0.568421 | 23.539917 | 36.460083 | 9 | |
| 10 | 0.631579 | 23.685207 | 36.314793 | 10 | |
| 11 | 0.694737 | 23.784615 | 36.215385 | 11 | |
| 12 | 0.757895 | 23.852631 | 36.147369 | 12 | |
| 13 | 0.821053 | 23.899169 | 36.100831 | 13 | |
| 14 | 0.884211 | 23.931010 | 36.068990 | 14 | |
| 15 | 0.947368 | 23.952796 | 36.047204 | 15 | |
| 16 | 1.010526 | 23.967703 | 36.032297 | 16 | |
| 17 | 1.073684 | 23.977902 | 36.022098 | 17 | |
| 18 | 1.136842 | 23.984880 | 36.015120 | 18 | |
| 19 | 1.200000 | 23.989655 | 36.010345 | 19 | last reaction step |
dynamics_fixed.plot_history(colors=['darkturquoise', 'green'], show_intervals=True)
Notice how grid points are being "wasted" on the tail part of the simulation, where little is happening - grid points that would be best used in the early part, as was done by the variable-step simulation of Part 1
dynamics_exact = UniformCompartment(reactions=rxns, exact=True)
# Re-use the chemicals and reactions of part 1 . Note the `exact` flag
# Initial concentrations of all the chemicals
dynamics_exact.set_conc({"A": 10., "B": 50.})
dynamics_exact.describe_state()
SYSTEM STATE at Time t = 0: 2 species: Species 0 (A). Conc: 10.0 Species 1 (B). Conc: 50.0 Chemicals involved in reactions: ['B', 'A']
# Matching the total number of steps to those of Part 1 and Part 2
dynamics_exact.single_compartment_react(n_steps=19, target_end_time=1.2,
variable_steps=False)
19 total fixed step(s) taken in 0.036 sec
dynamics_exact.get_history() # The system's history, saved during the run of single_compartment_react()
| SYSTEM TIME | A | B | step | caption | |
|---|---|---|---|---|---|
| 0 | 0.000000 | 10.000000 | 50.000000 | Set concentration | |
| 1 | 0.063158 | 13.791019 | 46.208981 | 1 | 1st reaction step |
| 2 | 0.126316 | 16.555479 | 43.444521 | 2 | |
| 3 | 0.189474 | 18.571359 | 41.428641 | 3 | |
| 4 | 0.252632 | 20.041364 | 39.958636 | 4 | |
| 5 | 0.315789 | 21.113312 | 38.886688 | 5 | |
| 6 | 0.378947 | 21.894989 | 38.105011 | 6 | |
| 7 | 0.442105 | 22.464999 | 37.535001 | 7 | |
| 8 | 0.505263 | 22.880657 | 37.119343 | 8 | |
| 9 | 0.568421 | 23.183761 | 36.816239 | 9 | |
| 10 | 0.631579 | 23.404788 | 36.595212 | 10 | |
| 11 | 0.694737 | 23.565964 | 36.434036 | 11 | |
| 12 | 0.757895 | 23.683495 | 36.316505 | 12 | |
| 13 | 0.821053 | 23.769200 | 36.230800 | 13 | |
| 14 | 0.884211 | 23.831698 | 36.168302 | 14 | |
| 15 | 0.947368 | 23.877272 | 36.122728 | 15 | |
| 16 | 1.010526 | 23.910505 | 36.089495 | 16 | |
| 17 | 1.073684 | 23.934739 | 36.065261 | 17 | |
| 18 | 1.136842 | 23.952411 | 36.047589 | 18 | |
| 19 | 1.200000 | 23.965297 | 36.034703 | 19 | last reaction step |
dynamics_exact.plot_history(colors=['darkturquoise', 'green'], show_intervals=True, title_prefix="EXACT solution")
# A streamlined version of the diagram seen in Part 1
fig_variable = dynamics_variable.plot_history(chemicals='A', colors='darkturquoise', title="VARIABLE time steps", show=True)
# A streamlined version of the diagram seen in Part 2
fig_fixed = dynamics_fixed.plot_history(chemicals='A', colors='blue', title="FIXED time steps", show=True)
# A streamlined version of the diagram seen in Part 3
fig_exact = dynamics_exact.plot_history(chemicals='A', colors='red', title="EXACT solution (at fixed time steps)", show=True)
PlotlyHelper.combine_plots(fig_list=[fig_fixed, fig_variable, fig_exact],
xrange=[0, 0.8], y_label="concentration [A]",
title="Variable time steps vs. Fixed vs. Exact soln, for [A] in reaction `A<->B`",
legend_title="Simulation run")
# All the 3 plots put together: show only the initial part (but it's all there; you can zoom out!)
All 3 curves essentially converge as the reaction approaches equilibrium.