A -> B,¶with 1st-order kinetics.
Adaptive variable time steps compared with exact analytical solution
LAST_REVISED = "Aug. 29, 2025"
LIFE123_VERSION = "1.0.0rc5" # 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
import numpy as np
from life123 import check_version, UniformCompartment, ReactionKinetics, GraphicLog, PlotlyHelper
check_version(LIFE123_VERSION) # To check compatibility
OK
# Instantiate the simulator and specify the chemicals
# Here we use the "fast" preset for the variable steps, trying to push the envelope on speed
uc = UniformCompartment(preset="fast")
# Reaction A <-> B , with 1st-order kinetics in both directions
uc.add_reaction(reactants="A", products="B",
kF=3., reversible=False)
uc.describe_reactions()
Number of reactions: 1 0: A <-> B (Elementary Unimolecular Irreversible reaction) (kF = 3 / Temp = 25 C) Chemicals involved in the above reactions: ['A', 'B']
# Set the initial concentrations of all the chemicals
uc.set_conc({"A": 50., "B": 10.})
uc.describe_state()
SYSTEM STATE at Time t = 0: 2 species: Species 0 (A). Conc: 50.0 Species 1 (B). Conc: 10.0 Chemicals involved in reactions: ['B', 'A']
uc.get_history()
| SYSTEM TIME | A | B | step | caption | |
|---|---|---|---|---|---|
| 0 | 0.0 | 50.0 | 10.0 | Set concentration |
uc.enable_diagnostics() # To save diagnostic information about the call to single_compartment_react()
# Useful for insight into the inner workings of the simulation
uc.single_compartment_react(initial_step=0.1, target_end_time=1.5,
variable_steps=True)
46 total variable step(s) taken in 0.243 sec
Number of step re-do's because of elective soft aborts: 5
Norm usage: {'norm_A': 24, 'norm_B': 10, 'norm_C': 10, 'norm_D': 10}
System Time is now: 1.5586
history = uc.get_history() # The system's history, saved during the run of single_compartment_react()
history
| SYSTEM TIME | A | B | step | caption | |
|---|---|---|---|---|---|
| 0 | 0.000000 | 50.000000 | 10.000000 | Set concentration | |
| 1 | 0.007776 | 48.833600 | 11.166400 | 1 | 1st reaction step |
| 2 | 0.019440 | 47.124815 | 12.875185 | 2 | |
| 3 | 0.028771 | 45.805621 | 14.194379 | 3 | |
| 4 | 0.038102 | 44.523357 | 15.476643 | 4 | |
| 5 | 0.047434 | 43.276988 | 16.723012 | 5 | |
| 6 | 0.061430 | 41.459770 | 18.540230 | 6 | |
| 7 | 0.072628 | 40.067040 | 19.932960 | 7 | |
| 8 | 0.083825 | 38.721095 | 21.278905 | 8 | |
| 9 | 0.095023 | 37.420364 | 22.579636 | 9 | |
| 10 | 0.106220 | 36.163327 | 23.836673 | 10 | |
| 11 | 0.123016 | 34.341112 | 25.658888 | 11 | |
| 12 | 0.136453 | 32.956795 | 27.043205 | 12 | |
| 13 | 0.149890 | 31.628281 | 28.371719 | 13 | |
| 14 | 0.163327 | 30.353320 | 29.646680 | 14 | |
| 15 | 0.176764 | 29.129754 | 30.870246 | 15 | |
| 16 | 0.196919 | 27.368389 | 32.631611 | 16 | |
| 17 | 0.213044 | 26.044499 | 33.955501 | 17 | |
| 18 | 0.229168 | 24.784650 | 35.215350 | 18 | |
| 19 | 0.253355 | 22.986291 | 37.013709 | 19 | |
| 20 | 0.272704 | 21.651993 | 38.348007 | 20 | |
| 21 | 0.292053 | 20.395149 | 39.604851 | 21 | |
| 22 | 0.321077 | 18.619317 | 41.380683 | 22 | |
| 23 | 0.344296 | 17.322350 | 42.677650 | 23 | |
| 24 | 0.367515 | 16.115727 | 43.884273 | 24 | |
| 25 | 0.402343 | 14.431866 | 45.568134 | 25 | |
| 26 | 0.430206 | 13.225529 | 46.774471 | 26 | |
| 27 | 0.472000 | 11.567277 | 48.432723 | 27 | |
| 28 | 0.505436 | 10.407008 | 49.592992 | 28 | |
| 29 | 0.555589 | 8.841178 | 51.158822 | 29 | |
| 30 | 0.595711 | 7.776989 | 52.223011 | 30 | |
| 31 | 0.655895 | 6.372845 | 53.627155 | 31 | |
| 32 | 0.716078 | 5.222222 | 54.777778 | 32 | |
| 33 | 0.776262 | 4.279344 | 55.720656 | 33 | |
| 34 | 0.836446 | 3.506704 | 56.493296 | 34 | |
| 35 | 0.896629 | 2.873565 | 57.126435 | 35 | |
| 36 | 0.956813 | 2.354740 | 57.645260 | 36 | |
| 37 | 1.016997 | 1.929589 | 58.070411 | 37 | |
| 38 | 1.077181 | 1.581200 | 58.418800 | 38 | |
| 39 | 1.137364 | 1.295713 | 58.704287 | 39 | |
| 40 | 1.197548 | 1.061770 | 58.938230 | 40 | |
| 41 | 1.257732 | 0.870067 | 59.129933 | 41 | |
| 42 | 1.317915 | 0.712975 | 59.287025 | 42 | |
| 43 | 1.378099 | 0.584247 | 59.415753 | 43 | |
| 44 | 1.438283 | 0.478760 | 59.521240 | 44 | |
| 45 | 1.498466 | 0.392320 | 59.607680 | 45 | |
| 46 | 1.558650 | 0.321486 | 59.678514 | 46 | last reaction step |
uc.plot_history(colors=['darkturquoise', 'green'], show_intervals=True) # Plots of concentration with time
That resulted from passing the flag variable_steps=True to single_compartment_react()
uc.plot_step_sizes(show_intervals=True) # To see the sizes of the steps taken
Why the zigzag? It's because of the "fast" preset picked for the variable steps, in the instantiation of the class UniformCompartment: it's like a "high-strung driver" that tries to get away with excessive speeed - and periodically overdoes on acceleration, and then slams on the brakes!
Other presets (such as "mid") are more "mild-mannered" and more conservative about going too fast too soon.
t_arr = np.linspace(0., 1.5, 50) # A relatively dense uniform grid across our time range
t_arr
array([0. , 0.03061224, 0.06122449, 0.09183673, 0.12244898,
0.15306122, 0.18367347, 0.21428571, 0.24489796, 0.2755102 ,
0.30612245, 0.33673469, 0.36734694, 0.39795918, 0.42857143,
0.45918367, 0.48979592, 0.52040816, 0.55102041, 0.58163265,
0.6122449 , 0.64285714, 0.67346939, 0.70408163, 0.73469388,
0.76530612, 0.79591837, 0.82653061, 0.85714286, 0.8877551 ,
0.91836735, 0.94897959, 0.97959184, 1.01020408, 1.04081633,
1.07142857, 1.10204082, 1.13265306, 1.16326531, 1.19387755,
1.2244898 , 1.25510204, 1.28571429, 1.31632653, 1.34693878,
1.37755102, 1.40816327, 1.43877551, 1.46938776, 1.5 ])
# The exact solution is available for a simple scenario like the one we're simulating here
A_exact, B_exact = ReactionKinetics.exact_solution_unimolecular_irreversible(kF=3., A0=50., B0=10., t_arr=t_arr)
fig_exact = PlotlyHelper.plot_curves(x=t_arr, y=[A_exact, B_exact], title="EXACT solution", x_label="SYSTEM TIME", y_label="concentration",
legend_title="Chemical", curve_labels=["A (EXACT)", "B (EXACT)"],
colors=["darkturquoise", "green"], show=True)
fig_exact = PlotlyHelper.plot_curves(x=t_arr, y=A_exact, title="EXACT solution", x_label="SYSTEM TIME", y_label="concentration",
curve_labels="A (EXACT)", legend_title="Chemical",
colors="red", show=True) # Repeat a portion of the diagram seen just before
fig_variable = uc.plot_history(chemicals=['A'], colors='darkturquoise', title="VARIABLE time steps", show=True) # Repeat a portion of the diagram seen in Part 1
PlotlyHelper.combine_plots(fig_list=[fig_variable, fig_exact],
xrange=[0, 1.5], y_label="concentration [A]",
title="Variable time steps vs. Exact soln, for [A] in irreversible reaction `A->B`",
legend_title="Simulation run") # Both plots put together