Based on experiment 1D/reaction/reaction_2
Diffusion not applicable (just 1 bin).
This is the 1D version of the single-compartment reaction by the same name.
LAST_REVISED = "May 5, 2025"
LIFE123_VERSION = "1.0.0rc3" # 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 experiments.get_notebook_info import get_notebook_basename
from life123 import check_version, UniformCompartment, BioSim1D, PlotlyHelper, GraphicLog
import plotly.express as px
# Initialize the HTML logging
log_file = get_notebook_basename() + ".log.htm" # Use the notebook base filename for the log file
# Set up the use of some specified graphic (Vue) components
GraphicLog.config(filename=log_file,
components=["vue_cytoscape_2"],
extra_js="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.21.2/cytoscape.umd.js")
-> Output will be LOGGED into the file 'state_space_1.log.htm'
# Initialize the system. NOTE: Diffusion not applicable (just 1 bin)
uc = UniformCompartment(names=["A", "B"])
# Reaction A <-> 3B , with 1st-order kinetics in both directions
uc.add_reaction(reactants="A", products=[(3,"B",1)], forward_rate=5., reverse_rate=2.)
uc.describe_reactions()
Number of reactions: 1 (at temp. 25 C)
0: A <-> 3 B (kF = 5 / kR = 2 / delta_G = -2,271.4 / K = 2.5) | 1st order in all reactants & products
Set of chemicals involved in the above reactions: {'A', 'B'}
bio = BioSim1D(n_bins=1, reaction_handler=uc)
bio.set_uniform_concentration(chem_label="A", conc=10.)
bio.set_uniform_concentration(chem_label="B", conc=50.)
bio.describe_state()
SYSTEM STATE at Time t = 0: 1 bins and 2 chemical species:
| Species | Diff rate | Bin 0 | |
|---|---|---|---|
| 0 | A | None | 10.0 |
| 1 | B | None | 50.0 |
# Let's enable history - by default for all chemicals and all bins (we're only using one)
bio.enable_history(take_snapshot=True, caption="Initial setup")
History enabled for bins None and chemicals None (None means 'all')
bio.get_bin_history(bin_address=0)
| SYSTEM TIME | A | B | caption | |
|---|---|---|---|---|
| 0 | 0.0 | 10.0 | 50.0 | Initial setup |
# Send the plot to the HTML log file
uc.plot_reaction_network("vue_cytoscape_2")
[GRAPHIC ELEMENT SENT TO LOG FILE `state_space_1.log.htm`]
# Using smaller steps that in experiment reaction_2, to avoid the initial overshooting
bio.react(time_step=0.05, n_steps=10)
System Time is now: 0.5
bio.describe_state()
SYSTEM STATE at Time t = 0.5: 1 bins and 2 chemical species:
| Species | Diff rate | Bin 0 | |
|---|---|---|---|
| 0 | A | None | 14.543907 |
| 1 | B | None | 36.368280 |
bio.get_bin_history(bin_address=0)
| SYSTEM TIME | A | B | caption | |
|---|---|---|---|---|
| 0 | 0.00 | 10.000000 | 50.000000 | Initial setup |
| 1 | 0.05 | 12.500000 | 42.500000 | |
| 2 | 0.10 | 13.625000 | 39.125000 | |
| 3 | 0.15 | 14.131250 | 37.606250 | |
| 4 | 0.20 | 14.359063 | 36.922812 | |
| 5 | 0.25 | 14.461578 | 36.615266 | |
| 6 | 0.30 | 14.507710 | 36.476870 | |
| 7 | 0.35 | 14.528470 | 36.414591 | |
| 8 | 0.40 | 14.537811 | 36.386566 | |
| 9 | 0.45 | 14.542015 | 36.373955 | |
| 10 | 0.50 | 14.543907 | 36.368280 |
# Verify that the reaction has reached equilibrium
bio.reaction_dynamics.is_in_equilibrium(rxn_index=0, conc=bio.bin_snapshot(bin_address = 0))
A <-> 3 B
Current concentrations: [A] = 14.54 ; [B] = 36.37
1. Ratio of reactant/product concentrations, adjusted for reaction orders: 2.50059
Formula used: [B] / [A]
2. Ratio of forward/reverse reaction rates: 2.5
Discrepancy between the two values: 0.02341 %
Reaction IS in equilibrium (within 1% tolerance)
True
bio.plot_history_single_bin(bin_address=0,
title_prefix="Reaction A <-> 3B")
df = bio.get_bin_history(bin_address=0)
fig0 = px.line(data_frame=df, x="A", y="B",
title="State space of reaction A <-> 3B : [A] vs. [B]",
color_discrete_sequence = ['#C83778'],
labels={"value":"concentration", "variable":"Chemical"})
fig0.show()
# Now show the individual data points
df['SYSTEM TIME'] = round(df['SYSTEM TIME'], 2) # To avoid clutter from too many digits, in the column
fig1 = px.scatter(data_frame=df, x="A", y="B",
title="Trajectory in State space: [A] vs. [B]",
hover_data=['SYSTEM TIME'])
fig1.update_traces(marker={"size": 6, "color": "#2FAC74"}) # Modify the style of the dots
# Add annotations (showing the System Time value) to SOME of the points, to avoid clutter
for ind in df.index:
label = df["SYSTEM TIME"][ind]
if ind == 0:
label = f"t={label}"
label_x = ind*16
label_y = 20 + ind*8 # A greater y value here means further DOWN!!
if (ind <= 3) or (ind%2 == 0):
fig1.add_annotation(x=df["A"][ind], y=df["B"][ind],
text=label,
font=dict(
size=10,
color="grey"
),
showarrow=True, arrowhead=0, ax=label_x, ay=label_y, arrowcolor="#b0b0b0",
bordercolor="#c7c7c7")
fig1.show()
# Combine the two above plots,
# while using the layout of fig1 (which includes the title and annotations)
PlotlyHelper.combine_plots(fig_list = [fig0, fig1], layout_index=1)