E + S <-> ES, and ES -> E + P , using real-life kinetic parameters.¶In the follow-up experiments enzyme_1_b and enzyme_1_c, the enzyme concentration gets progressively increased.
Two other reactions being assumed negligible, and not included, are :
the enzyme Adenosine deaminase with the substrate 2,6-Diamino-9-β-D-deoxyribofuranosyl-9-H-purine,
and the initial concentration values choosen below, all satisfy the customary Michaelis-Menten assumptions that [E] << [S] and that the reaction rate constants satisfy k1_reverse >> k2_forward
For this reaction: k1_forward = 18, k1_reverse = 100, k2_forward = 49
Source of kinetic parameters: page 16 of "Analysis of Enzyme Reaction Kinetics, Vol. 1", by F. Xavier Malcata, Wiley, 2023
LAST_REVISED = "Nov. 17, 2024"
LIFE123_VERSION = "1.0.0.rc.0" # 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 ipynbname
import pandas as pd
from life123 import check_version, ChemData, UniformCompartment, ReactionEnz, GraphicLog, PlotlyHelper
check_version(LIFE123_VERSION)
OK
# Initialize the HTML logging (for the graphics)
log_file = ipynbname.name() + ".log.htm" # Use the notebook base filename for the log file
# IN CASE OF PROBLEMS, set manually to any desired name
# 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 'enzyme_1_a.log.htm'
chem_data = ChemData(names=["P", "ES"])
# Our Enzyme
chem_data.add_chemical(name="Adenosine deaminase", label="E")
# Our Substrate
chem_data.add_chemical(name="2,6-Diamino-9-β-D-deoxyribofuranosyl-9-H-purine", label="S");
chem_data.all_chemicals()
| name | label | |
|---|---|---|
| 0 | P | P |
| 1 | ES | ES |
| 2 | Adenosine deaminase | E |
| 3 | 2,6-Diamino-9-β-D-deoxyribofuranosyl-9-H-purine | S |
Source: page 16 of "Analysis of Enzyme Reaction Kinetics, Vol. 1", by F. Xavier Malcata, Wiley, 2023
# Reaction E + S <-> ES , with 1st-order kinetics,
chem_data.add_reaction(reactants=["E", "S"], products=["ES"],
forward_rate=18., reverse_rate=100.)
# Reaction ES <-> E + P , with 1st-order kinetics, ignoring the reverse reaction
chem_data.add_reaction(reactants=["ES"], products=["E", "P"],
forward_rate=49., reverse_rate=0)
chem_data.describe_reactions()
Number of reactions: 2 (at temp. 25 C)
0: E + S <-> ES (kF = 18 / kR = 100 / delta_G = 4,250.9 / K = 0.18) | 1st order in all reactants & products
1: ES <-> E + P (kF = 49 / kR = 0) | 1st order in all reactants & products
Set of chemicals involved in the above reactions: {'E', 'ES', 'P', 'S'}
# Send a plot of the network of reactions to the HTML log file
chem_data.plot_reaction_network("vue_cytoscape_2")
[GRAPHIC ELEMENT SENT TO LOG FILE `enzyme_1_a.log.htm`]
S0 = 20.
E0 = 1.
# Here we use the "slower" preset for the variable steps, a very conservative option prioritizing accuracy over speed
uc = UniformCompartment(chem_data=chem_data, preset="slower")
uc.set_conc(conc={"S": S0, "E": E0}) # Small ampount of enzyme `E`, relative to substrate `S`
uc.describe_state()
SYSTEM STATE at Time t = 0:
4 species:
Species 0 (P). Conc: 0.0
Species 1 (ES). Conc: 0.0
Species 2 (E). Conc: 1.0
Species 3 (S). Conc: 20.0
Set of chemicals involved in reactions: {'E', 'ES', 'P', 'S'}
# Perform the reactions
uc.single_compartment_react(duration=1.5, initial_step=0.05)
827 total step(s) taken in 0.03 min
Number of step re-do's because of negative concentrations: 2
Number of step re-do's because of elective soft aborts: 1
Norm usage: {'norm_A': 706, 'norm_B': 710, 'norm_C': 706, 'norm_D': 706}
System Time is now: 1.5039
uc.plot_history(colors=['green', 'red', 'violet', 'darkturquoise'], show_intervals=True,
title_prefix="Small amout of E relative to S(0)")
plot_pandas() NOTICE: Excessive number of vertical lines (828) - only showing 1 every 6 lines
# Highlight a detail about the initial buildup of ES
uc.plot_history(colors=['green', 'red', 'violet', 'darkturquoise'],
title_prefix="DETAIL at early times",
range_x=[0, 0.5], range_y=[0, 2.])
ES (red) quickly builds up at the very beginning, from time 0 to roughly 0.01 ... and that, in the longer term, the enzyme returns to its unbound state E¶
P?¶One could take the numerical derivative (gradient) of the time values of [P] - but no need to! Reaction rates are computed in the course of the simulation, and stored in a rate-history dataframe
rates = uc.get_rate_history() # We'll be interested in rxn1_rate (the reaction that leads to `P`)
rates
| SYSTEM TIME | rxn0_rate | rxn1_rate | |
|---|---|---|---|
| 0 | 0.000000 | 360.000000 | 0.000000 |
| 1 | 0.000500 | 274.543200 | 8.820000 |
| 2 | 0.000750 | 243.269275 | 12.075109 |
| 3 | 0.000763 | 241.911753 | 12.216716 |
| 4 | 0.000769 | 241.237493 | 12.287060 |
| ... | ... | ... | ... |
| 822 | 1.468805 | 0.253941 | 0.285397 |
| 823 | 1.475832 | 0.244291 | 0.274566 |
| 824 | 1.482859 | 0.235005 | 0.264142 |
| 825 | 1.489886 | 0.226068 | 0.254109 |
| 826 | 1.496913 | 0.217468 | 0.244454 |
827 rows × 3 columns
# Let's take a look at how the reaction rate varies with time
PlotlyHelper.plot_pandas(df=rates,
title="Reaction rate, dP/dt, over time",
x_var="SYSTEM TIME", fields="rxn1_rate",
x_label="time", y_label="dP/dt")
# A closer peek at its maximum value
PlotlyHelper.plot_pandas(df=rates,
title="Reaction rate, dP/dt, over time (DETAIL at early times)",
x_var="SYSTEM TIME", fields="rxn1_rate",
range_x=[0,0.05], range_y=[33., 34.5])
As we saw earlier, the time it took for ES to build up was about 0.01
Ignoring the brief initial transient phase, the reaction rate starts at about 34.1
for background reference, see: https://vallance.chem.ox.ac.uk/pdfs/KineticsLectureNotes.pdf (p. 20)
rxn = ReactionEnz(enzyme="E", substrate="S", product="P",
k1_F=chem_data.get_forward_rate(0), k1_R=chem_data.get_reverse_rate(0),
k2_F=chem_data.get_forward_rate(1))
rxn.kM # For the data in this experiment, it comes out to (49. + 100.) / 18.
8.277777777777779
rxn.kcat
49.0
vmax = rxn.compute_vmax(E_tot=E0) # kcat * E0
vmax
49.0
initial_rxn_rate = rxn.compute_rate(S_conc=S0) # (vmax * S0) / (kM + S0)
initial_rxn_rate
34.656188605108056
Not too far from the value of about 34.1 we saw earlier...
To keep in mind that the initial part of the reaction is affected by transients
P as a function of [S]; we'll compare what we computed earlier vs. what is given by the approximation of the Michaelis-Menten model¶First, we'll merge the concentration history and and the rate history into a single dataframe df
df = uc.add_rate_to_conc_history(rate_name="rxn1_rate", new_rate_name="P_rate")
df
| SYSTEM TIME | P | ES | E | S | caption | P_rate | |
|---|---|---|---|---|---|---|---|
| 0 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 20.000000 | Set concentration | 0.000000 |
| 1 | 0.000500 | 0.000000 | 0.180000 | 0.820000 | 19.820000 | 1st reaction step | 8.820000 |
| 2 | 0.000750 | 0.002205 | 0.246431 | 0.753569 | 19.751364 | 12.075109 | |
| 3 | 0.000763 | 0.002356 | 0.249321 | 0.750679 | 19.748323 | 12.216716 | |
| 4 | 0.000769 | 0.002432 | 0.250756 | 0.749244 | 19.746811 | 12.287060 | |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 822 | 1.468805 | 19.947437 | 0.005824 | 0.994176 | 0.046738 | 0.285397 | |
| 823 | 1.475832 | 19.949443 | 0.005603 | 0.994397 | 0.044954 | 0.274566 | |
| 824 | 1.482859 | 19.951372 | 0.005391 | 0.994609 | 0.043237 | 0.264142 | |
| 825 | 1.489886 | 19.953228 | 0.005186 | 0.994814 | 0.041586 | 0.254109 | |
| 826 | 1.496913 | 19.955014 | 0.004989 | 0.995011 | 0.039997 | 0.244454 |
827 rows × 7 columns
# Let's add a column with the rate estimated by the Michaelis-Menten model
df["Michaelis_rate"] = rxn.compute_rate(S_conc=df["S"])
df
| SYSTEM TIME | P | ES | E | S | caption | P_rate | Michaelis_rate | |
|---|---|---|---|---|---|---|---|---|
| 0 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 20.000000 | Set concentration | 0.000000 | 34.656189 |
| 1 | 0.000500 | 0.000000 | 0.180000 | 0.820000 | 19.820000 | 1st reaction step | 8.820000 | 34.564299 |
| 2 | 0.000750 | 0.002205 | 0.246431 | 0.753569 | 19.751364 | 12.075109 | 34.528950 | |
| 3 | 0.000763 | 0.002356 | 0.249321 | 0.750679 | 19.748323 | 12.216716 | 34.527380 | |
| 4 | 0.000769 | 0.002432 | 0.250756 | 0.749244 | 19.746811 | 12.287060 | 34.526599 | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 822 | 1.468805 | 19.947437 | 0.005824 | 0.994176 | 0.046738 | 0.285397 | 0.275111 | |
| 823 | 1.475832 | 19.949443 | 0.005603 | 0.994397 | 0.044954 | 0.274566 | 0.264664 | |
| 824 | 1.482859 | 19.951372 | 0.005391 | 0.994609 | 0.043237 | 0.264142 | 0.254610 | |
| 825 | 1.489886 | 19.953228 | 0.005186 | 0.994814 | 0.041586 | 0.254109 | 0.244934 | |
| 826 | 1.496913 | 19.955014 | 0.004989 | 0.995011 | 0.039997 | 0.244454 | 0.235622 |
827 rows × 8 columns
# Let's see how our computed rate compares with the approximations from the Michaelis-Menten model
PlotlyHelper.plot_pandas(df=df, x_var="S", fields=["P_rate", "Michaelis_rate"],
title="Reaction rate, dP/dt, as a function of Substrate concentration",
y_label="dP/dt", legend_header="Rates",
vertical_lines_to_add=18.9, colors=["blue", "yellow"])
Let's recall that our reactions started with [S]=20
Virtually overlapped plots, except at the very right! (very early times, when [S] is greater than about 19)
S), which is the brief initial transient phase when ES builds up from zero¶[E] << [S] and that the rates satisfy k1_reverse >> k2_forward¶
df["Morrison_rate"] = rxn.compute_rate_morrison(E_tot=E0,
S_tot=df["S"] + df["ES"])
df
| SYSTEM TIME | P | ES | E | S | caption | P_rate | Michaelis_rate | Morrison_rate | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 20.000000 | Set concentration | 0.000000 | 34.656189 | 34.292188 |
| 1 | 0.000500 | 0.000000 | 0.180000 | 0.820000 | 19.820000 | 1st reaction step | 8.820000 | 34.564299 | 34.292188 |
| 2 | 0.000750 | 0.002205 | 0.246431 | 0.753569 | 19.751364 | 12.075109 | 34.528950 | 34.291025 | |
| 3 | 0.000763 | 0.002356 | 0.249321 | 0.750679 | 19.748323 | 12.216716 | 34.527380 | 34.290945 | |
| 4 | 0.000769 | 0.002432 | 0.250756 | 0.749244 | 19.746811 | 12.287060 | 34.526599 | 34.290905 | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 822 | 1.468805 | 19.947437 | 0.005824 | 0.994176 | 0.046738 | 0.285397 | 0.275111 | 0.276209 | |
| 823 | 1.475832 | 19.949443 | 0.005603 | 0.994397 | 0.044954 | 0.274566 | 0.264664 | 0.265721 | |
| 824 | 1.482859 | 19.951372 | 0.005391 | 0.994609 | 0.043237 | 0.264142 | 0.254610 | 0.255628 | |
| 825 | 1.489886 | 19.953228 | 0.005186 | 0.994814 | 0.041586 | 0.254109 | 0.244934 | 0.245914 | |
| 826 | 1.496913 | 19.955014 | 0.004989 | 0.995011 | 0.039997 | 0.244454 | 0.235622 | 0.236566 |
827 rows × 9 columns
PlotlyHelper.plot_pandas(df=df, x_var="S", fields=["P_rate", "Michaelis_rate", "Morrison_rate"],
title="Reaction rate, dP/dt, as a function of Substrate concentration",
y_label="dP/dt", legend_header="Rates",
vertical_lines_to_add=18.9,
colors=["blue", "yellow", "orange"])
# Let's take a closer look at the rightmost portion
PlotlyHelper.plot_pandas(df=df, x_var="S", fields=["P_rate", "Michaelis_rate", "Morrison_rate"],
title="Reaction rate, dP/dt, as a function of Substrate concentration",
y_label="dP/dt", legend_header="Rates",
vertical_lines_to_add=18.9,
colors=["blue", "yellow", "orange"],
range_x=[17, 20], range_y=[32,35])
Then why even bother with it? In the next two experiments, enzyme_2 and enzyme_3, we'll see how the Morrison model becomes progressively better than Michaelis-Menten at increasingly higher amounts of enzyme.