A <-> C derived from 2 coupled elementary reactions:¶A <-> B (fast) and B <-> C (slow)¶A repeat of experiment cascade_2_b, but with the fast/slow elementary reactions in reverse order.
In PART 1, a time evolution of [A], [B] and [C] is obtained by simulation
In PART 2, the time functions generated in Part 1 are taken as a starting point, to explore how to model the composite reaction A <-> C
Background: please see experiment cascade_2_b
LAST_REVISED = "Nov. 12, 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
from experiments.get_notebook_info import get_notebook_basename
from life123 import UniformCompartment, ReactionKinetics, PlotlyHelper
# Instantiate the simulator and specify the chemicals
dynamics = UniformCompartment(names=["A", "B", "C"], preset="mid")
# Reaction A <-> B (much faster, and with a much larger K)
dynamics.add_reaction(reactants="A", products="B",
forward_rate=80., reverse_rate=0.1) # <===== SPEEDS of 2 reactions ARE REVERSED, relative to experiment `cascade_2_b`
# Reaction B <-> C (much slower, and with a much smaller K)
dynamics.add_reaction(reactants="B", products="C",
forward_rate=8., reverse_rate=2.) # <===== SPEEDS of 2 reactions ARE REVERSED, relative to experiment `cascade_2_b`
dynamics.describe_reactions()
Number of reactions: 2 (at temp. 25 C)
0: A <-> B (kF = 80 / kR = 0.1 / delta_G = -16,571 / K = 800) | 1st order in all reactants & products
1: B <-> C (kF = 8 / kR = 2 / delta_G = -3,436.6 / K = 4) | 1st order in all reactants & products
Set of chemicals involved in the above reactions: {'B', 'C', 'A'}
dynamics.set_conc({"A": 50.}, snapshot=True)
dynamics.describe_state()
SYSTEM STATE at Time t = 0:
3 species:
Species 0 (A). Conc: 50.0
Species 1 (B). Conc: 0.0
Species 2 (C). Conc: 0.0
Set of chemicals involved in reactions: {'B', 'C', 'A'}
dynamics.single_compartment_react(initial_step=0.01, duration=0.8,
variable_steps=True)
152 total step(s) taken in 0.287 sec
Number of step re-do's because of elective soft aborts: 4
Norm usage: {'norm_A': 46, 'norm_B': 44, 'norm_C': 42, 'norm_D': 42}
System Time is now: 0.80143
dynamics.plot_history(colors=['darkturquoise', 'orange', 'green'], show_intervals=True)
plot_pandas() NOTICE: Excessive number of vertical lines (153) - only showing 1 every 2 lines
dynamics.is_in_equilibrium(tolerance=3)
0: A <-> B
Final concentrations: [A] = 0.01218 ; [B] = 9.998
1. Ratio of reactant/product concentrations, adjusted for reaction orders: 821.034
Formula used: [B] / [A]
2. Ratio of forward/reverse reaction rates: 800
Discrepancy between the two values: 2.629 %
Reaction IS in equilibrium (within 3% tolerance)
1: B <-> C
Final concentrations: [B] = 9.998 ; [C] = 39.99
1. Ratio of reactant/product concentrations, adjusted for reaction orders: 3.99992
Formula used: [C] / [B]
2. Ratio of forward/reverse reaction rates: 4
Discrepancy between the two values: 0.001889 %
Reaction IS in equilibrium (within 3% tolerance)
True
cascade_2_b¶
Let's start by taking stock of the actual data (saved during the simulation of part 1):
df = dynamics.get_history(columns=["SYSTEM TIME", "A", "C", "caption"]) # We're NOT given the intermediary B
df
| SYSTEM TIME | A | C | caption | |
|---|---|---|---|---|
| 0 | 0.000000 | 50.000000 | 0.000000 | Set concentration |
| 1 | 0.000256 | 48.976000 | 0.000000 | 1st reaction step |
| 2 | 0.000563 | 47.772397 | 0.002517 | |
| 3 | 0.000717 | 47.185404 | 0.005250 | |
| 4 | 0.000794 | 46.895519 | 0.006975 | |
| ... | ... | ... | ... | ... |
| 148 | 0.450771 | 0.012844 | 39.746776 | |
| 149 | 0.516094 | 0.012619 | 39.905478 | |
| 150 | 0.594482 | 0.012517 | 39.971659 | |
| 151 | 0.688547 | 0.012538 | 39.988899 | |
| 152 | 0.801426 | 0.012177 | 39.990107 | last reaction step |
153 rows × 4 columns
t_arr = df["SYSTEM TIME"].to_numpy() # The independent variable : Time
A_conc = df["A"].to_numpy()
C_conc = df["C"].to_numpy()
A <-> C could be modeled as an elementary reaction, we'd expect the rate of change of [C] to be proportional to [A]¶Let's see what happens if we try to do such a linear fit!
ReactionKinetics.estimate_rate_constants_simple(t=t_arr, A_conc=A_conc, B_conc=C_conc, reactant_name="A", product_name="C")
Reaction A <-> C
Total REACTANT + PRODUCT has a median of 24.85,
with standard deviation 12.08 (ideally should be zero)
The sum of the time derivatives of the reactant and the product
has a median of -234.3 (ideally should be zero)
Least square fit to model as elementary reaction: C'(t) = kF * A(t) - kR * C(t)
-> ESTIMATED RATE CONSTANTS: kF = 2.729 , kR = -6.08
This is a larger numer of splits than we did in experiments cascade_2_a and cascade_2_b
Let's visually locate at what times those [A] values occur:
dynamics.plot_history(chemicals='A', colors='darkturquoise', range_x=[0, 0.15],
vertical_lines_to_add=[0.028, 0.1], title="[A] as a function of time")
dynamics.get_history(columns=["SYSTEM TIME", "A"], t_start=0.02, t_end=0.03)
| SYSTEM TIME | A | |
|---|---|---|
| 72 | 0.020462 | 9.428805 |
| 73 | 0.021645 | 8.540614 |
| 74 | 0.022828 | 7.736565 |
| 75 | 0.024012 | 7.008682 |
| 76 | 0.025195 | 6.349747 |
| 77 | 0.026378 | 5.753223 |
| 78 | 0.027561 | 5.213195 |
| 79 | 0.028745 | 4.724310 |
| 80 | 0.029928 | 4.281718 |
[A] assumes the value 5 around t=0.028
dynamics.get_history(columns=["SYSTEM TIME", "A"], t_start=0.09, t_end=0.11)
| SYSTEM TIME | A | |
|---|---|---|
| 126 | 0.090918 | 0.062461 |
| 127 | 0.093371 | 0.057137 |
| 128 | 0.095825 | 0.052750 |
| 129 | 0.098769 | 0.048391 |
| 130 | 0.101714 | 0.044909 |
| 131 | 0.105247 | 0.041540 |
| 132 | 0.109487 | 0.038396 |
[A] assumes the value 0.05 around t=0.1
A_conc and C_conc arrays we extracted earlier (with the entire time evolution of, respectively, [A] and [C]) into 3 parts:¶A_conc_early = A_conc[:79]
A_conc_mid = A_conc[79:129]
A_conc_late = A_conc[129:]
C_conc_early = C_conc[:79]
C_conc_mid = C_conc[79:129]
C_conc_late = C_conc[129:]
t_arr_early = t_arr[:79]
t_arr_mid = t_arr[79:129]
t_arr_late = t_arr[129:]
ReactionKinetics.estimate_rate_constants_simple(t=t_arr_early, A_conc=A_conc_early, B_conc=C_conc_early,
reactant_name="A", product_name="C")
Reaction A <-> C
Total REACTANT + PRODUCT has a median of 35.2,
with standard deviation 11.66 (ideally should be zero)
The sum of the time derivatives of the reactant and the product
has a median of -2,699 (ideally should be zero)
Least square fit to model as elementary reaction: C'(t) = kF * A(t) - kR * C(t)
-> ESTIMATED RATE CONSTANTS: kF = 1.799 , kR = -71.02
Just as we saw in experiment cascade_2_b, trying to fit an elementary reaction to that region leads to a negative reverse rate constant!
We won't discuss this part any further.
ReactionKinetics.estimate_rate_constants_simple(t=t_arr_mid, A_conc=A_conc_mid, B_conc=C_conc_mid,
reactant_name="A", product_name="C")
Reaction A <-> C
Total REACTANT + PRODUCT has a median of 14.9,
with standard deviation 3.612 (ideally should be zero)
The sum of the time derivatives of the reactant and the product
has a median of 199 (ideally should be zero)
Least square fit to model as elementary reaction: C'(t) = kF * A(t) - kR * C(t)
-> ESTIMATED RATE CONSTANTS: kF = 68.94 , kR = -12.04
For this region, too, trying to fit an elementary reaction to that region leads to a negative reverse rate!
We won't discuss this part any further.
ReactionKinetics.estimate_rate_constants_simple(t=t_arr_late, A_conc=A_conc_late, B_conc=C_conc_late,
reactant_name="A", product_name="C")
Reaction A <-> C
Total REACTANT + PRODUCT has a median of 34.37,
with standard deviation 6.092 (ideally should be zero)
The sum of the time derivatives of the reactant and the product
has a median of 62.73 (ideally should be zero)
Least square fit to model as elementary reaction: C'(t) = kF * A(t) - kR * C(t)
-> ESTIMATED RATE CONSTANTS: kF = 4,790 , kR = 1.255
This time we have an adequate linear fit AND positive rate constants, but a huge value of kF relative to that of any of the elementary reactions!
Let's see the time evolution again, but just for A and C:
dynamics.plot_history(chemicals=['A', 'C'], colors=['darkturquoise', 'green'], range_x=[0, 0.25],
vertical_lines_to_add=[0.028, 0.1], title='A <-> C compound reaction')
For t > 0.1, the product [C] appears to have a lot of response to nearly non-existing changes in [A] !
That doesn't seem like a good fit to an elementary reaction...
A <-> C as an elementary reaction is not a good fit at any time¶We observed this in a scenario where the slower reaction is the later one.