S <-> P , and S + E <-> P + E¶A direct reaction and the same reaction, catalyzed
E: from zero to hugely abundant¶E¶ELAST REVISED: June 14, 2024 (using v. 1.0 beta33)
import set_path # Importing this module will add the project's home directory to sys.path
Added 'D:\Docs\- MY CODE\BioSimulations\life123-Win7' to sys.path
from src.modules.chemicals.chem_data import ChemData
from src.modules.reactions.uniform_compartment import UniformCompartment
from src.modules.movies.movies import MovieTabular
import pandas as pd
import plotly.express as px
# Initialize the system
chem_data = ChemData(names=["S", "P", "E"])
# Reaction S <-> P , with 1st-order kinetics, favorable thermodynamics in the forward direction,
# and a forward rate that is much slower than it would be with the enzyme - as seen in the next reaction, below
chem_data.add_reaction(reactants="S", products="P",
forward_rate=1., delta_G=-3989.73)
# Reaction S + E <-> P + E , with 1st-order kinetics, and a forward rate that is much faster than it was without the enzyme
# Thermodynamically, there's no change from the reaction without the enzyme
chem_data.add_reaction(reactants=["S", "E"], products=["P", "E"],
forward_rate=10., delta_G=-3989.73)
chem_data.describe_reactions() # Notice how the enzyme `E` is noted in the printout below
Number of reactions: 2 (at temp. 25 C)
0: S <-> P (kF = 1 / kR = 0.2 / delta_G = -3,989.7 / K = 5) | 1st order in all reactants & products
1: S + E <-> P + E (kF = 10 / kR = 2 / delta_G = -3,989.7 / K = 5) | Enzyme: E | 1st order in all reactants & products
Set of chemicals involved in the above reactions (not counting enzymes): {'P', 'S'}
Set of enzymes involved in the above reactions: {'E'}
dynamics = UniformCompartment(chem_data=chem_data, preset="mid")
dynamics.set_conc(conc={"S": 20.},
snapshot=True) # Initially, no enzyme `E`
dynamics.describe_state()
SYSTEM STATE at Time t = 0:
3 species:
Species 0 (S). Conc: 20.0
Species 1 (P). Conc: 0.0
Species 2 (E). Conc: 0.0
Set of chemicals involved in reactions (not counting enzymes): {'P', 'S'}
Set of enzymes involved in reactions: {'E'}
dynamics.set_diagnostics() # To save diagnostic information about the call to single_compartment_react()
# Perform the reactions
dynamics.single_compartment_react(duration=4.0,
initial_step=0.1, variable_steps=True)
35 total step(s) taken
Number of step re-do's because of negative concentrations: 0
Number of step re-do's because of elective soft aborts: 1
Norm usage: {'norm_A': 20, 'norm_B': 20, 'norm_C': 19, 'norm_D': 19}
#dynamics.explain_time_advance()
dynamics.plot_history(colors=['cyan', 'green', 'violet'], show_intervals=True, title_prefix="With ZERO enzyme")
# To save up snapshots of crossover times at various Enzyme concentrations
crossover_points = MovieTabular(parameter_name="Enzyme concentration")
new_crossover = dynamics.curve_intersect("S", "P", t_start=0, t_end=1.0)
crossover_points.store(par=dynamics.get_chem_conc("E"),
data_snapshot = {"crossover time": new_crossover[0]})
new_crossover
(0.7435259497128707, 9.999999999999996)
# Verify that the reaction has reached equilibrium
dynamics.is_in_equilibrium(tolerance=2)
0: S <-> P
Final concentrations: [S] = 3.372 ; [P] = 16.63
1. Ratio of reactant/product concentrations, adjusted for reaction orders: 4.93068
Formula used: [P] / [S]
2. Ratio of forward/reverse reaction rates: 5.00001
Discrepancy between the two values: 1.387 %
Reaction IS in equilibrium (within 2% tolerance)
1: S + E <-> P + E
Final concentrations: [S] = 3.372 ; [E] = 0 ; [P] = 16.63
Reaction IS in equilibrium because it can't proceed in either direction due to zero concentrations in both some reactants and products!
True
dynamics = UniformCompartment(chem_data=chem_data, preset="mid") # A brand-new simulation
dynamics.set_conc(conc={"S": 20., "E": 0.2},
snapshot=True) # A tiny bit of enzyme `E`
dynamics.describe_state()
SYSTEM STATE at Time t = 0:
3 species:
Species 0 (S). Conc: 20.0
Species 1 (P). Conc: 0.0
Species 2 (E). Conc: 0.2
Set of chemicals involved in reactions (not counting enzymes): {'P', 'S'}
Set of enzymes involved in reactions: {'E'}
dynamics.single_compartment_react(duration=1.5,
initial_step=0.05, variable_steps=True)
Some steps were backtracked and re-done, to prevent negative concentrations or excessively large concentration changes
33 total step(s) taken
Number of step re-do's because of negative concentrations: 0
Number of step re-do's because of elective soft aborts: 1
Norm usage: {'norm_A': 19, 'norm_B': 19, 'norm_C': 18, 'norm_D': 18}
#dynamics.explain_time_advance()
dynamics.plot_history(colors=['cyan', 'green', 'violet'], show_intervals=True, title_prefix="With a tiny amount of enzyme")
new_crossover = dynamics.curve_intersect("S", "P", t_start=0, t_end=0.5)
crossover_points.store(par=dynamics.get_chem_conc("E"),
data_snapshot = {"crossover time": new_crossover[0]})
new_crossover
(0.24710707675961718, 10.0)
# Verify that the reaction has reached equilibrium
dynamics.is_in_equilibrium()
0: S <-> P
Final concentrations: [S] = 3.34 ; [P] = 16.66
1. Ratio of reactant/product concentrations, adjusted for reaction orders: 4.98745
Formula used: [P] / [S]
2. Ratio of forward/reverse reaction rates: 5.00001
Discrepancy between the two values: 0.2511 %
Reaction IS in equilibrium (within 1% tolerance)
1: S + E <-> P + E
Final concentrations: [S] = 3.34 ; [E] = 0.2 ; [P] = 16.66
1. Ratio of reactant/product concentrations, adjusted for reaction orders: 4.98745
Formula used: ([P][E]) / ([S][E])
2. Ratio of forward/reverse reaction rates: 5.00001
Discrepancy between the two values: 0.2511 %
Reaction IS in equilibrium (within 1% tolerance)
True
dynamics = UniformCompartment(chem_data=chem_data, preset="mid") # A brand-new simulation
dynamics.set_conc(conc={"S": 20., "E": 1.},
snapshot=True) # A more substantial amount of enzyme `E`
dynamics.describe_state()
SYSTEM STATE at Time t = 0:
3 species:
Species 0 (S). Conc: 20.0
Species 1 (P). Conc: 0.0
Species 2 (E). Conc: 1.0
Set of chemicals involved in reactions (not counting enzymes): {'P', 'S'}
Set of enzymes involved in reactions: {'E'}
dynamics.single_compartment_react(duration=0.5,
initial_step=0.02, variable_steps=True)
Some steps were backtracked and re-done, to prevent negative concentrations or excessively large concentration changes
39 total step(s) taken
Number of step re-do's because of negative concentrations: 0
Number of step re-do's because of elective soft aborts: 2
Norm usage: {'norm_A': 24, 'norm_B': 23, 'norm_C': 22, 'norm_D': 22}
#dynamics.explain_time_advance()
dynamics.plot_history(colors=['cyan', 'green', 'violet'], show_intervals=True, title_prefix="With a more substantial amount of enzyme")
new_crossover = dynamics.curve_intersect("S", "P", t_start=0, t_end=0.5)
crossover_points.store(par=dynamics.get_chem_conc("E"),
data_snapshot = {"crossover time": new_crossover[0]})
new_crossover
(0.06769827987210066, 10.0)
# Verify that the reaction has reached equilibrium
dynamics.is_in_equilibrium(explain=False)
True
dynamics = UniformCompartment(chem_data=chem_data, preset="mid") # A brand-new simulation
dynamics.set_conc(conc={"S": 20., "E": 5.},
snapshot=True) # A good amount of enzyme `E`
dynamics.describe_state()
SYSTEM STATE at Time t = 0:
3 species:
Species 0 (S). Conc: 20.0
Species 1 (P). Conc: 0.0
Species 2 (E). Conc: 5.0
Set of chemicals involved in reactions (not counting enzymes): {'P', 'S'}
Set of enzymes involved in reactions: {'E'}
dynamics.single_compartment_react(duration=0.2,
initial_step=0.01, variable_steps=True)
Some steps were backtracked and re-done, to prevent negative concentrations or excessively large concentration changes
36 total step(s) taken
Number of step re-do's because of negative concentrations: 0
Number of step re-do's because of elective soft aborts: 2
Norm usage: {'norm_A': 24, 'norm_B': 21, 'norm_C': 21, 'norm_D': 21}
#dynamics.explain_time_advance()
dynamics.plot_history(colors=['cyan', 'green', 'violet'], show_intervals=True, title_prefix="With a good amount of enzyme")
new_crossover = dynamics.curve_intersect("S", "P", t_start=0, t_end=0.5)
crossover_points.store(par=dynamics.get_chem_conc("E"),
data_snapshot = {"crossover time": new_crossover[0]})
new_crossover
(0.014494267066104415, 10.000000000000002)
# Verify that the reaction has reached equilibrium
dynamics.is_in_equilibrium(explain=False)
True
dynamics = UniformCompartment(chem_data=chem_data, preset="mid") # A brand-new simulation
dynamics.set_conc(conc={"S": 20., "E": 20.},
snapshot=True) # A lot of enzyme `E`
dynamics.describe_state()
SYSTEM STATE at Time t = 0:
3 species:
Species 0 (S). Conc: 20.0
Species 1 (P). Conc: 0.0
Species 2 (E). Conc: 20.0
Set of chemicals involved in reactions (not counting enzymes): {'P', 'S'}
Set of enzymes involved in reactions: {'E'}
dynamics.single_compartment_react(duration=0.05,
initial_step=0.005, variable_steps=True)
Some steps were backtracked and re-done, to prevent negative concentrations or excessively large concentration changes
39 total step(s) taken
Number of step re-do's because of negative concentrations: 0
Number of step re-do's because of elective soft aborts: 3
Norm usage: {'norm_A': 27, 'norm_B': 23, 'norm_C': 23, 'norm_D': 23}
#dynamics.explain_time_advance()
dynamics.plot_history(colors=['cyan', 'green', 'violet'], show_intervals=True, title_prefix="With a lot of enzyme")
new_crossover = dynamics.curve_intersect("S", "P", t_start=0, t_end=0.5)
crossover_points.store(par=dynamics.get_chem_conc("E"),
data_snapshot = {"crossover time": new_crossover[0]})
new_crossover
(0.003687634618431487, 9.999999999999996)
# Verify that the reaction has reached equilibrium
dynamics.is_in_equilibrium(explain=False)
True
dynamics = UniformCompartment(chem_data=chem_data, preset="mid") # A brand-new simulation
dynamics.set_conc(conc={"S": 20., "E": 30.},
snapshot=True) # A very large amount of enzyme `E`
dynamics.describe_state()
SYSTEM STATE at Time t = 0:
3 species:
Species 0 (S). Conc: 20.0
Species 1 (P). Conc: 0.0
Species 2 (E). Conc: 30.0
Set of chemicals involved in reactions (not counting enzymes): {'P', 'S'}
Set of enzymes involved in reactions: {'E'}
dynamics.single_compartment_react(duration=0.02,
initial_step=0.001, variable_steps=True)
Some steps were backtracked and re-done, to prevent negative concentrations or excessively large concentration changes
36 total step(s) taken
Number of step re-do's because of negative concentrations: 0
Number of step re-do's because of elective soft aborts: 2
Norm usage: {'norm_A': 23, 'norm_B': 22, 'norm_C': 21, 'norm_D': 21}
#dynamics.explain_time_advance()
dynamics.plot_history(colors=['cyan', 'green', 'violet'], show_intervals=True, title_prefix="With a very large amount of enzyme")
new_crossover = dynamics.curve_intersect("S", "P", t_start=0, t_end=0.5)
crossover_points.store(par=dynamics.get_chem_conc("E"),
data_snapshot = {"crossover time": new_crossover[0]})
new_crossover
(0.002465868124424963, 10.0)
# Verify that the reaction has reached equilibrium
dynamics.is_in_equilibrium(explain=False)
True
dynamics = UniformCompartment(chem_data=chem_data, preset="mid") # A brand-new simulation
dynamics.set_conc(conc={"S": 20., "E": 100.},
snapshot=True) # A lavish amount of enzyme `E`
dynamics.describe_state()
SYSTEM STATE at Time t = 0:
3 species:
Species 0 (S). Conc: 20.0
Species 1 (P). Conc: 0.0
Species 2 (E). Conc: 100.0
Set of chemicals involved in reactions (not counting enzymes): {'P', 'S'}
Set of enzymes involved in reactions: {'E'}
dynamics.single_compartment_react(duration=0.02,
initial_step=0.0005, variable_steps=True)
40 total step(s) taken
Number of step re-do's because of negative concentrations: 0
Number of step re-do's because of elective soft aborts: 2
Norm usage: {'norm_A': 28, 'norm_B': 25, 'norm_C': 25, 'norm_D': 25}
#dynamics.explain_time_advance()
dynamics.plot_history(colors=['cyan', 'green', 'violet'], show_intervals=True, title_prefix="With a huge amount of enzyme")
new_crossover = dynamics.curve_intersect("S", "P", t_start=0, t_end=0.5)
crossover_points.store(par=dynamics.get_chem_conc("E"),
data_snapshot = {"crossover time": new_crossover[0]})
new_crossover
(0.0007383445224719487, 10.0)
# Verify that the reaction has reached equilibrium
dynamics.is_in_equilibrium(explain=False)
True
dynamics = UniformCompartment(chem_data=chem_data, preset="mid") # A brand-new simulation
dynamics.set_conc(conc={"S": 20., "E": 2000.},
snapshot=True) # A lavish amount of enzyme `E`
dynamics.describe_state()
SYSTEM STATE at Time t = 0:
3 species:
Species 0 (S). Conc: 20.0
Species 1 (P). Conc: 0.0
Species 2 (E). Conc: 2000.0
Set of chemicals involved in reactions (not counting enzymes): {'P', 'S'}
Set of enzymes involved in reactions: {'E'}
dynamics.single_compartment_react(duration=0.0015,
initial_step=0.000005, variable_steps=True)
48 total step(s) taken
Number of step re-do's because of negative concentrations: 0
Number of step re-do's because of elective soft aborts: 1
Norm usage: {'norm_A': 33, 'norm_B': 33, 'norm_C': 32, 'norm_D': 32}
#dynamics.explain_time_advance()
dynamics.plot_history(chemicals=['S', 'P'],
colors=['cyan', 'green', 'violet'], show_intervals=True, title_prefix="With a LAVISH amount of enzyme (NOT shown)")
Note: NOT showing the enzyme (concentration 2,000) in the graph, to avoid squishing down the other curves!
new_crossover = dynamics.curve_intersect("S", "P", t_start=0, t_end=0.5)
crossover_points.store(par=dynamics.get_chem_conc("E"),
data_snapshot = {"crossover time": new_crossover[0]})
new_crossover
(3.7174487975074336e-05, 10.0)
# Verify that the reaction has reached equilibrium
dynamics.is_in_equilibrium()
0: S <-> P
Final concentrations: [S] = 3.313 ; [P] = 16.69
1. Ratio of reactant/product concentrations, adjusted for reaction orders: 5.03748
Formula used: [P] / [S]
2. Ratio of forward/reverse reaction rates: 5.00001
Discrepancy between the two values: 0.7496 %
Reaction IS in equilibrium (within 1% tolerance)
1: S + E <-> P + E
Final concentrations: [S] = 3.313 ; [E] = 2,000 ; [P] = 16.69
1. Ratio of reactant/product concentrations, adjusted for reaction orders: 5.03748
Formula used: ([P][E]) / ([S][E])
2. Ratio of forward/reverse reaction rates: 5.00001
Discrepancy between the two values: 0.7496 %
Reaction IS in equilibrium (within 1% tolerance)
True
df = crossover_points.get_dataframe()
df
| Enzyme concentration | crossover time | caption | |
|---|---|---|---|
| 0 | 0.0 | 0.743526 | |
| 1 | 0.2 | 0.247107 | |
| 2 | 1.0 | 0.067698 | |
| 3 | 5.0 | 0.014494 | |
| 4 | 20.0 | 0.003688 | |
| 5 | 30.0 | 0.002466 | |
| 6 | 100.0 | 0.000738 | |
| 7 | 2000.0 | 0.000037 |
# Let's plot just the first 4 data points, to avoid squishing the graph
px.line(data_frame=df.loc[0:3],
x="Enzyme concentration", y=["crossover time"],
title="Time of crossover of [S] and [P] , as a function of Enzyme concentration (first 4 points)",
labels={"value":"Crossover time when [S]=[P]"})
# Same plot, but with log scales on both axes (all data points used this time,
# but the value E = 0 is automatically dropped by the graphic function because of the log scale)
px.line(data_frame=df,
x="Enzyme concentration", y=["crossover time"],
log_x=True, log_y=True,
title="Time of crossover of [S] and [P] , as a function of Enzyme conc - log scales on both axes<br>([E]=0 value skipped)",
labels={"value":"Crossover time when [S]=[P]"})