#!/usr/bin/env python # coding: utf-8 # ## Enzyme Kinetics in a NON Michaelis-Menten modality # #### 3 Coupled Reactions: `S <-> P` , `E + S <-> ES*`, and `ES* <-> E + P` # A direct reaction and the same reaction, catalyzed by an enzyme `E` and showing the intermediate state. # Re-run from same initial concentrations of S ("Substrate") and P ("Product"), for various concentations of the enzyme `E`: from zero to hugely abundant # ### We'll REJECT the customary Michaelis-Menten assumptions that `[E] << [S]` and that the rate constants satisfy `k1_reverse >> k2_forward` ! # #### We'll explore exotic scenarios with lavish amount of enzyme, leading to diminishing (though fast-produced!) products, and a buildup of the (not-so-transient!) `ES*` intermediate # In[1]: LAST_REVISED = "Oct. 11, 2024" LIFE123_VERSION = "1.0.0.beta.39" # Library version this experiment is based on # In[2]: #import set_path # Using MyBinder? Uncomment this before running the next cell! # In[3]: #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 ChemData, UniformCompartment, MovieTabular, GraphicLog import pandas as pd # In[4]: # 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") # In[5]: # Initialize the system chem_data = ChemData() # 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 E + S <-> ES* , with 1st-order kinetics, and a forward rate that is much faster than it was without the enzyme # Thermodynamically, the forward direction is at a disadvantage (higher energy state) because of the activation barrier in forming the transient state ES* chem_data.add_reaction(reactants=["E", "S"], products=["ES*"], forward_rate=100., delta_G=2000) # Reaction ES* <-> E + P , with 1st-order kinetics, and a forward rate that is much faster than it was without the enzyme # Thermodynamically, the total energy change of this reaction and the previous one adds up to the same value as the reaction without the enzyme (-3989.73) chem_data.add_reaction(reactants=["ES*"], products=["E", "P"], forward_rate=200., delta_G=-5989.73) # NOTE: the forward_rate's of the last 2 reactions (the catalyzed ones) were tweaked, # to lead to a crossover point [S] = [P] at about the same time as in experiment `enzyme3`, in step 2 (when [E] = 0.2) chem_data.describe_reactions() # Send the plot of the reaction network to the HTML log file chem_data.plot_reaction_network("vue_cytoscape_2") # Note that `E` is not labeled as an "enzyme" because it doesn't appear as a catalyst in any of the registered reactions; it only becomes an enzyme in the context of the _compound_ reaction from (2) and (3) # In[ ]: # # 1. Set the initial concentrations of all the chemicals - starting with no enzyme # In[6]: dynamics = UniformCompartment(chem_data=chem_data, preset="mid") dynamics.set_conc(conc={"S": 20.}, snapshot=True) # Initially, no enzyme `E` dynamics.describe_state() # ### Advance the reactions (for now without enzyme) to equilibrium # In[7]: #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, explain_variable_steps=False) # In[8]: dynamics.plot_history(colors=['cyan', 'green', 'violet', 'red'], show_intervals=True, title_prefix="With ZERO enzyme") # ### The reactions, lacking enzyme, are proceeding slowly towards equilibrium, just like the reaction that was discussed in part 1 of the experiment "enzyme_1" # In[9]: # Verify that the reactions have reached equilibrium dynamics.is_in_equilibrium(tolerance=2) # In[10]: # Locate the intersection of the curves for [S] and [P]: dynamics.curve_intersect("S", "P", t_start=0, t_end=1.0) # In[11]: dynamics.get_history(columns=['SYSTEM TIME', 'E', 'ES*', 'P'], tail=1) # Last point in the simulation # In[12]: P_equil = dynamics.get_chem_conc("P") P_equil # In[13]: P_70_threshold = P_equil * 0.70 P_70_threshold # In[14]: dynamics.reach_threshold(chem="P", threshold=P_70_threshold) # In[ ]: # In[ ]: # # 2. Re-start all the reactions from the same initial concentrations - except for now having a tiny amount of enzyme (two orders of magnitude less than the starting [S]) # In[15]: E_init = 0.2 # A tiny bit of enzyme `E`: 1/100 of the initial [S] # In[16]: dynamics = UniformCompartment(chem_data=chem_data, preset="slower") # A brand-new simulation, with the same chemicals and reactions as before dynamics.set_conc(conc={"S": 20., "E": E_init}, snapshot=True) dynamics.describe_state() # In[17]: # Perform the reactions (The duration of the run was manually adjusted for optimal visibility) dynamics.single_compartment_react(duration=1.3, initial_step=0.00005, variable_steps=True, explain_variable_steps=False) # In[18]: # Verify that the reactions have reached equilibrium dynamics.is_in_equilibrium(verbose=False) # In[19]: # Locate the intersection of the curves for [S] and [P]: dynamics.curve_intersect("S", "P", t_start=0, t_end=1.0) # In[20]: dynamics.plot_history(colors=['cyan', 'green', 'violet', 'red'], show_intervals=True, title_prefix=f"[E] init = {E_init}", range_x=[0, 0.4]) # In[21]: # Locate the intersection of the curves for [S] and [P]: dynamics.curve_intersect("S", "P", t_start=0, t_end=0.4) # ### Now, let's investigate E and ES* in the very early times # ## Notice how even a tiny amount of enzyme (1/100 of the initial [S]) makes a very pronounced difference! # In[22]: #The very early part of the reaction dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"Detail when [E] init = {E_init}", range_x=[0, 0.002]) # ### Notice how, with this small initial concentration of [E], the timescale of [E] and [ES*] is vastly faster than that of [P] and [S] # In[23]: # The full reaction of E and ES* dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"E and ES when [E] init = {E_init}") # Notice how at every onset of instability in [E] or [ES*], the adaptive time steps shrink down # In[24]: dynamics.get_history(columns=['SYSTEM TIME', 'E', 'ES*', 'P'], tail=1) # Last point in the simulation # Interestingly, most of the inital [E] of 0.2 is now, at equilibrium, stored as [ES*]=0.119; the energy of the "activation barrier" from E + S to ES* might be unrealistically low (2000 Joules). Zooming in on the very earl part of the plot: # In[25]: P_equil = dynamics.get_chem_conc("P") P_equil # In[26]: P_70_threshold = P_equil * 0.70 P_70_threshold # In[27]: dynamics.reach_threshold(chem="P", threshold=P_70_threshold) # In[ ]: # In[ ]: # # 3. Repeat last step with increasingly-larger initial [E] # In[28]: E_init = 0.4 # In[29]: dynamics = UniformCompartment(chem_data=chem_data, preset="slower") # A brand-new simulation, with the same chemicals and reactions as before dynamics.set_conc(conc={"S": 20., "E": E_init}, snapshot=True) dynamics.describe_state() # In[30]: # Perform the reactions (The duration of the run was manually adjusted for optimal visibility) dynamics.single_compartment_react(duration=1.0, initial_step=0.00005, variable_steps=True, explain_variable_steps=False) # In[31]: # Verify that the reactions have reached equilibrium dynamics.is_in_equilibrium(verbose=False) # In[32]: dynamics.plot_history(colors=['cyan', 'green', 'violet', 'red'], show_intervals=True, title_prefix=f"[E] init = {E_init}") # In[33]: # Locate the intersection of the curves for [S] and [P]: dynamics.curve_intersect("S", "P", t_start=0, t_end=0.4) # Notice how the timescale of [S] and [P] is becoming faster with a higher initial [E] # In[34]: #The very early part of the reaction dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"Detail when [E] init = {E_init}", range_x=[0, 0.002]) # In[35]: # The full reaction of E and ES* dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"E and ES when [E] init = {E_init}") # In[36]: dynamics.get_history(columns=['SYSTEM TIME', 'E', 'ES*', 'P'], tail=1) # Last point in the simulation # In[37]: P_equil = dynamics.get_chem_conc("P") P_equil # #### P_equil continues to decrease with increasing initial [E] # In[38]: P_70_threshold = P_equil * 0.70 P_70_threshold # In[39]: dynamics.reach_threshold(chem="P", threshold=P_70_threshold) # #### The time at which the 70% threshold of the equilibrium value of P continues to decrease with increasing initial [E] # In[ ]: # In[ ]: # # 4. Keep increasing the initial [E] # In[40]: E_init = 1.0 # In[41]: dynamics = UniformCompartment(chem_data=chem_data, preset="slower") # A brand-new simulation, with the same chemicals and reactions as before dynamics.set_conc(conc={"S": 20., "E": E_init}, snapshot=True) dynamics.describe_state() # In[42]: # Perform the reactions (The duration of the run was manually adjusted for optimal visibility) dynamics.single_compartment_react(duration=0.4, initial_step=0.00005, variable_steps=True, explain_variable_steps=False) # In[43]: # Verify that the reactions have reached equilibrium dynamics.is_in_equilibrium(verbose=False) # In[44]: dynamics.plot_history(colors=['cyan', 'green', 'violet', 'red'], show_intervals=True, title_prefix=f"[E] init = {E_init}") # In[45]: # Locate the intersection of the curves for [S] and [P]: dynamics.curve_intersect("S", "P", t_start=0, t_end=0.4) # The timescale of [S] and [P] continues to become faster with a higher initial [E] # In[46]: #The very early part of the reaction dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"Detail when [E] init = {E_init}", range_x=[0, 0.002]) # In[47]: # The full reaction of E and ES* dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"E and ES when [E] init = {E_init}") # In[48]: dynamics.get_history(columns=['SYSTEM TIME', 'E', 'ES*', 'P'], tail=1) # Last point in the simulation # In[49]: P_equil = dynamics.get_chem_conc("P") P_equil # #### P_equil continues to decrease with increasing initial [E] # In[50]: P_70_threshold = P_equil * 0.70 P_70_threshold # In[51]: dynamics.reach_threshold(chem="P", threshold=P_70_threshold) # #### The time at which we reach the 70% threshold of the equilibrium value of P continues to decrease with increasing initial [E] # In[ ]: # In[ ]: # # 5. Keep increasing the initial [E] # In[52]: E_init = 2.0 # 1/10 of the initial [S] # In[53]: dynamics = UniformCompartment(chem_data=chem_data, preset="slower") # A brand-new simulation, with the same chemicals and reactions as before dynamics.set_conc(conc={"S": 20., "E": E_init}, snapshot=True) dynamics.describe_state() # In[54]: # Perform the reactions (The duration of the run was manually adjusted for optimal visibility) dynamics.single_compartment_react(duration=0.2, initial_step=0.00005, variable_steps=True, explain_variable_steps=False) # In[55]: # Verify that the reactions have reached equilibrium dynamics.is_in_equilibrium(verbose=False) # In[56]: dynamics.plot_history(colors=['cyan', 'green', 'violet', 'red'], show_intervals=True, title_prefix=f"[E] init = {E_init}") # In[57]: # Locate the intersection of the curves for [S] and [P]: dynamics.curve_intersect("S", "P", t_start=0, t_end=0.4) # The timescale of [S] and [P] continues to become faster with a higher initial [E] # In[58]: #The very early part of the reaction dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"Detail when [E] init = {E_init}", range_x=[0, 0.002]) # In[59]: # The full reaction of E and ES* dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"E and ES when [E] init = {E_init}") # In[60]: dynamics.get_history(columns=['SYSTEM TIME', 'E', 'ES*', 'P'], tail=1) # Last point in the simulation # In[61]: P_equil = dynamics.get_chem_conc("P") P_equil # #### P_equil continues to decrease with increasing initial [E] # In[62]: P_70_threshold = P_equil * 0.70 P_70_threshold # In[63]: dynamics.reach_threshold(chem="P", threshold=P_70_threshold) # #### The time at which we reach the 70% threshold of the equilibrium value of P continues to decrease with increasing initial [E] # In[ ]: # In[ ]: # # 6. Keep increasing the initial [E] # In[64]: E_init = 10.0 # 1/2 of the initial [S] # In[65]: dynamics = UniformCompartment(chem_data=chem_data, preset="slower") # A brand-new simulation, with the same chemicals and reactions as before dynamics.set_conc(conc={"S": 20., "E": E_init}, snapshot=True) dynamics.describe_state() # In[66]: # Perform the reactions (The duration of the run was manually adjusted for optimal visibility) dynamics.single_compartment_react(duration=0.05, initial_step=0.00005, variable_steps=True, explain_variable_steps=False) # In[67]: # Verify that the reactions have reached equilibrium dynamics.is_in_equilibrium(verbose=False) # In[68]: dynamics.plot_history(colors=['cyan', 'green', 'violet', 'red'], show_intervals=True, title_prefix=f"[E] init = {E_init}") # In[69]: # Locate the intersection of the curves for [S] and [P]: dynamics.curve_intersect("S", "P", t_start=0, t_end=0.05) # #### The timescale of [S] and [P] continues to become faster with a higher initial [E] -- **AND THEY'RE NOW APPROACHING THE TIMESCALES OF E AND ES** # In[70]: #The very early part of the reaction dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"Detail when [E] init = {E_init}", range_x=[0, 0.002]) # In[71]: # The full reaction of E and ES* dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"E and ES when [E] init = {E_init}") # #### Notice that at these higher initial concentrations of [E], we're now beginning to see overshoots in [E] and [ES*] # In[72]: dynamics.get_history(columns=['SYSTEM TIME', 'E', 'ES*', 'P'], tail=1) # Last point in the simulation # In[73]: P_equil = dynamics.get_chem_conc("P") P_equil # #### P_equil continues to decrease with increasing initial [E] # In[74]: P_70_threshold = P_equil * 0.70 P_70_threshold # In[75]: dynamics.reach_threshold(chem="P", threshold=P_70_threshold) # #### The time at which we reach the 70% threshold of the equilibrium value of P continues to decrease with increasing initial [E] # In[ ]: # In[ ]: # # 7. Keep increasing the initial [E] # In[76]: E_init = 20.0 # Same as the initial [S] # In[77]: dynamics = UniformCompartment(chem_data=chem_data, preset="slower") # A brand-new simulation, with the same chemicals and reactions as before dynamics.set_conc(conc={"S": 20., "E": E_init}, snapshot=True) dynamics.describe_state() # In[78]: # Perform the reactions (The duration of the run was manually adjusted for optimal visibility) dynamics.single_compartment_react(duration=0.02, initial_step=0.00005, variable_steps=True, explain_variable_steps=False) # In[79]: # Verify that the reactions have reached equilibrium dynamics.is_in_equilibrium(verbose=False) # In[80]: dynamics.plot_history(colors=['cyan', 'green', 'violet', 'red'], show_intervals=True, title_prefix=f"[E] init = {E_init}") # In[81]: # Locate the intersection of the curves for [S] and [P]: dynamics.curve_intersect("S", "P", t_start=0, t_end=0.02) # #### The timescale of [S] and [P] continues to become faster with a higher initial [E] -- **AND THEY'RE NOW GETTING CLOSE THE TIMESCALES OF E AND ES** # In[82]: #The very early part of the reaction dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"Detail when [E] init = {E_init}", range_x=[0, 0.002]) # In[83]: # The full reaction of E and ES* dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"E and ES when [E] init = {E_init}") # #### At these higher initial concentrations of [E], we're now beginning to see overshoots in [E] and [ES*] that overlap less # In[84]: dynamics.get_history(columns=['SYSTEM TIME', 'E', 'ES*', 'P'], tail=1) # Last point in the simulation # In[85]: P_equil = dynamics.get_chem_conc("P") P_equil # #### P_equil continues to decrease with increasing initial [E] # In[86]: P_70_threshold = P_equil * 0.70 P_70_threshold # In[87]: dynamics.reach_threshold(chem="P", threshold=P_70_threshold) # #### The time at which we reach the 70% threshold of the equilibrium value of P continues to decrease with increasing initial [E] # In[ ]: # In[ ]: # # 8. Keep increasing the initial [E] # In[88]: E_init = 30.0 # 50% higher than the initial [S] # In[89]: dynamics = UniformCompartment(chem_data=chem_data, preset="slower") # A brand-new simulation, with the same chemicals and reactions as before dynamics.set_conc(conc={"S": 20., "E": E_init}, snapshot=True) dynamics.describe_state() # In[90]: # Perform the reactions (The duration of the run was manually adjusted for optimal visibility) dynamics.single_compartment_react(duration=0.01, initial_step=0.00005, variable_steps=True, explain_variable_steps=False) # In[91]: # Verify that the reactions have reached equilibrium dynamics.is_in_equilibrium(verbose=False) # In[92]: dynamics.plot_history(colors=['cyan', 'green', 'violet', 'red'], show_intervals=True, title_prefix=f"[E] init = {E_init}") # In[93]: # Locate the intersection of the curves for [S] and [P]: dynamics.curve_intersect("S", "P", t_start=0, t_end=0.01) # #### The timescale of [S] and [P] continues to become faster with a higher initial [E] -- **AND THEY'RE NOW COMPARABLE THE TIMESCALES OF E AND ES** # In[94]: #The very early part of the reaction dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"Detail when [E] init = {E_init}", range_x=[0, 0.005]) # In[95]: # The full reaction of E and ES* dynamics.plot_history(chemicals=['E', 'ES*'], colors=['violet', 'red'], show_intervals=True, title_prefix=f"E and ES when [E] init = {E_init}") # #### At these high initial concentrations of [E], we're now beginning to see [E] and [ES*] no longer overlapping; the overshoot is still present in both # In[96]: dynamics.get_history(columns=['SYSTEM TIME', 'E', 'ES*', 'P'], tail=1) # Last point in the simulation # In[97]: P_equil = dynamics.get_chem_conc("P") P_equil # #### P_equil continues to decrease with increasing initial [E] # In[98]: P_70_threshold = P_equil * 0.70 P_70_threshold # In[99]: dynamics.reach_threshold(chem="P", threshold=P_70_threshold) # #### The time at which we reach the 70% threshold of the equilibrium value of P continues to decrease with increasing initial [E] # In[ ]: # In[ ]: # # 9. Keep increasing the initial [E] # In[100]: E_init = 60.0 # Triple the initial [S] # In[101]: dynamics = UniformCompartment(chem_data=chem_data, preset="slower") # A brand-new simulation, with the same chemicals and reactions as before dynamics.set_conc(conc={"S": 20., "E": E_init}, snapshot=True) dynamics.describe_state() # In[102]: # Perform the reactions (The duration of the run was manually adjusted for optimal visibility) dynamics.single_compartment_react(duration=0.005, initial_step=0.00005, variable_steps=True, explain_variable_steps=False) # In[103]: # Verify that the reactions have reached equilibrium dynamics.is_in_equilibrium(verbose=False) # In[104]: dynamics.plot_history(colors=['cyan', 'green', 'violet', 'red'], show_intervals=True, title_prefix=f"[E] init = {E_init}") # In[105]: # Locate the intersection of the curves for [S] and [P]: dynamics.curve_intersect("S", "P", t_start=0, t_end=0.005) # #### The timescales of [S] and [P] continues to become faster with a higher initial [E] -- **AND NOW THEY REMAIN COMPARABLE THE TIMESCALES OF E AND ES** # #### At these high initial concentrations of [E], [E] and [ES*] no longer overlapping, and are now getting further apart # In[106]: dynamics.get_history(columns=['SYSTEM TIME', 'E', 'ES*', 'P'], tail=1) # Last point in the simulation # In[107]: P_equil = dynamics.get_chem_conc("P") P_equil # #### P_equil continues to decrease with increasing initial [E] # In[108]: P_70_threshold = P_equil * 0.70 P_70_threshold # In[109]: dynamics.reach_threshold(chem="P", threshold=P_70_threshold) # #### The time at which we reach the 70% threshold of the equilibrium value of P continues to decrease with increasing initial [E] # In[ ]: # In[ ]: # # 10. Keep increasing the initial [E] # In[110]: E_init = 100.0 # Quintuple the initial [S] # In[111]: dynamics = UniformCompartment(chem_data=chem_data, preset="slower") # A brand-new simulation, with the same chemicals and reactions as before dynamics.set_conc(conc={"S": 20., "E": E_init}, snapshot=True) dynamics.describe_state() # In[112]: # Perform the reactions (The duration of the run was manually adjusted for optimal visibility) dynamics.single_compartment_react(duration=0.003, initial_step=0.00005, variable_steps=True, explain_variable_steps=False) # In[113]: # Verify that the reactions have reached equilibrium dynamics.is_in_equilibrium(verbose=False) # In[114]: dynamics.plot_history(colors=['cyan', 'green', 'violet', 'red'], show_intervals=True, title_prefix=f"[E] init = {E_init}") # In[115]: # Locate the intersection of the curves for [S] and [P]: dynamics.curve_intersect("S", "P", t_start=0, t_end=0.005) # #### The timescales of [S] and [P] continues to become faster with a higher initial [E] -- **AND REMAIN COMPARABLE TO THE TIMESCALES OF E AND ES** # #### At these high initial concentrations of [E], [E] and [ES*] no longer overlapping, and are now getting further apart # In[116]: dynamics.get_history(columns=['SYSTEM TIME', 'E', 'ES*', 'P'], tail=1) # Last point in the simulation # In[117]: P_equil = dynamics.get_chem_conc("P") P_equil # #### P_equil continues to decrease with increasing initial [E] # In[118]: P_70_threshold = P_equil * 0.70 P_70_threshold # In[119]: dynamics.reach_threshold(chem="P", threshold=P_70_threshold) # In[ ]: # # CONCLUSION: # ### as the initial concentration of the Enzyme E increases from zero to lavish values much larger that the concentration of the Substrate S, the reaction keeps reaching an equilibrium faster and faster... # #### BUT the equilibrium concentration of the Product P steadily _reduces_ - and ES*, far from being transient, actually builds up. # #### The **strange world of departing from the customary Michaelis-Menten assumptions** that [E] << [S] and that the rates satisfy k1_reverse >> k2_forward ! # In[120]: dynamics.get_history(head=1) # First point in the simulation # In[121]: dynamics.get_history(tail=1) # Last point in the simulation # ### When the initial [E] is quite high, relatively little of the reactant S goes into making the product P ; most of S binds with the abundant E, to produce a lasting ES* # The following manual stoichiometry check illustrates it. # #### Manual overall STOICHIOMETRY CHECK: # In[122]: delta_S = 0.46627 - 20.0 delta_S # In[123]: delta_E = 82.779099 - 100.0 delta_E # In[124]: delta_E = 82.779099 - 100.0 delta_E # In[125]: delta_ES = 17.220901 - 0 delta_ES # In[126]: chem_data.describe_reactions() # In[ ]: # 19.53373 units of `S` are consumed : a meager 2.312829 of that goes into the production of `P` (rxn 0) ; the remainder of Delta S is: # In[127]: 19.53373 - 2.312829 # That extra Delta S of 17.2209 combines with an equal amount of `E` to produce the same amount, 17.2209, of ES* (rxn 1) # # `E`, now depleted by that amount of 17.2209, reaches the value: # In[128]: 100 - 17.2209 # In[129]: # Also review that the chemical equilibrium holds, with the final simulation values dynamics.is_in_equilibrium() # In[ ]: