#!/usr/bin/env python # coding: utf-8 # ## Association/Dissociation reaction `A + B <-> C` # #### with 1st-order kinetics for each species, taken to equilibrium. # #### Exploration of debugging and diagnostics options # (Adaptive variable time substeps are used) # # _See also the experiment "1D/reactions/reaction_4"_ # # # For the 0-th reaction (the only reaction in our case) # In[1]: # Extend the sys.path variable, to contain the project's root directory import set_path set_path.add_ancestor_dir_to_syspath(2) # The number of levels to go up # to reach the project's home, from the folder containing this notebook # In[2]: from experiments.get_notebook_info import get_notebook_basename from src.modules.reactions.reaction_data import ReactionData as chem from src.modules.reactions.reaction_dynamics import ReactionDynamics import numpy as np import plotly.express as px from src.modules.visualization.graphic_log import GraphicLog # In[3]: # Initialize the HTML logging (for the graphics) 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_1"], extra_js="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.21.2/cytoscape.umd.js") # # Initialize the System # Specify the chemicals and the reactions # In[4]: # Specify the chemicals chem_data = chem(names=["A", "B", "C"]) # Reaction A + B <-> C , with 1st-order kinetics for each species chem_data.add_reaction(reactants=["A" , "B"], products=["C"], forward_rate=5., reverse_rate=2.) print("Number of reactions: ", chem_data.number_of_reactions()) # In[5]: chem_data.describe_reactions() # In[6]: # Send a plot of the network of reactions to the HTML log file graph_data = chem_data.prepare_graph_network() GraphicLog.export_plot(graph_data, "vue_cytoscape_1") # # Start the simulation # In[7]: dynamics = ReactionDynamics(reaction_data=chem_data) # In[8]: # Initial concentrations of all the chemicals, in index order dynamics.set_conc([10., 50., 20.], snapshot=True) # In[9]: dynamics.describe_state() # In[10]: dynamics.get_history() # ## Run the reaction # In[11]: dynamics.set_diagnostics() # To save diagnostic information about the call to single_compartment_react() #dynamics.verbose_list = [1] # Uncomment for detailed run information (meant for debugging the adaptive variable time step) dynamics.single_compartment_react(time_step=0.004, reaction_duration=0.06, snapshots={"initial_caption": "1st reaction step", "final_caption": "last reaction step"}, dynamic_substeps=2, rel_fast_threshold=1) # ### Note: the argument _dynamic_step=2_ splits the time steps in 2 whenever the reaction is "fast" (as determined using the given value of _fast_threshold_ ) # In[12]: df = dynamics.get_history() df # In[13]: dynamics.explain_time_advance() # ### Notice how the reaction proceeds in smaller steps in the early times, when the concentrations are changing much more rapidly # ## Note: "A" (now largely depleted) is the limiting reagent # ### Check the final equilibrium # In[14]: dynamics.get_system_conc() # NOTE: Consistent with the 3/2 ratio of forward/reverse rates (and the 1st order reactions), # the systems settles in the following equilibrium: # # [A] = 0.29487741 , [B] = 40.29487741 , [C] = 29.70512259 # # In[15]: # Verify that the reaction has reached equilibrium dynamics.is_in_equilibrium() # ## Plots of changes of concentration with time # In[16]: dynamics.plot_curves(colors=['red', 'violet', 'green']) # In[ ]: # # Everthing below is just for diagnostic insight # ## into the adaptive variable time steps # ## WARNING: The explanations below are based on an older system of using RELATIVE concentration changes, and no longer applicable to the newer approach; it'll be corrected in future versions (COMMENTED OUT FOR NOW) # In[17]: # This approach, from the run data, is only usable with single-reaction runs #dynamics.examine_run(df=df, time_step=0.004, rel_fast_threshold=1) # The arguments step MUST match the value used in call to single_compartment_react() # # Take a peek at internal diagnostic data from the reactions # In[18]: # This approach, from internal diagnostic data, # is more generally applicable also to runs with multiple reactions #dynamics.diagnose_variable_time_steps() # ### The above diagnostics are based on the following diagnostic data: # In[19]: dynamics.get_diagnostic_data(rxn_index=0) # In[ ]: