Working with Reactions
Overview
Reactions describe how chemical species transform from reactants to products. Each reaction has an associated rate expression that determines how fast the transformation occurs.
from jaff import Network
# Load a network
net = Network("networks/react_COthin")
# Access reactions
for rxn in net.reactions:
print(f"{rxn.verbatim}")
print(f" Type: {rxn.guess_type()}")
print(f" Rate: {rxn.rate}")
Reaction Attributes
Each reaction object has the following attributes:
| Attribute | Type | Description |
|---|---|---|
reactants |
list | List of Species objects consumed |
products |
list | List of Species objects produced |
rate |
sympy.Expr | Symbolic expression for the reaction rate |
tmin |
float or None | Minimum temperature (K) |
tmax |
float or None | Maximum temperature (K) |
dE |
float | Energy change or activation energy |
verbatim |
str | Human-readable equation (e.g., "H + H -> H2") |
serialized |
str | Serialized form using species names |
serialized_exploded |
str | Serialized form using atomic composition |
xsecs |
dict or None | Cross-section data for photochemical reactions |
Accessing Reactions
By Index
# Get first reaction
rxn = net.reactions[0]
print(f"First reaction: {rxn.verbatim}")
# Iterate over all reactions
for i, rxn in enumerate(net.reactions):
print(f"{i}: {rxn.verbatim}")
Count Reactions
Filtering Reactions
By Reaction Type
# Find photochemical reactions
photo_rxns = [r for r in net.reactions if r.guess_type() == "photo"]
print(f"Found {len(photo_rxns)} photochemical reactions")
# Find cosmic ray reactions
cr_rxns = [r for r in net.reactions if r.guess_type() == "cosmic_ray"]
# Find three-body reactions
tb_rxns = [r for r in net.reactions if r.guess_type() == "3_body"]
# Find reactions with visual extinction dependence
av_rxns = [r for r in net.reactions if r.guess_type() == "photo_av"]
By Species Involvement
# Find all reactions involving H2
h2_reactions = [r for r in net.reactions if r.has_any_species("H2")]
# Find reactions that consume H2
h2_destruction = [r for r in net.reactions if r.has_reactant("H2")]
# Find reactions that produce H2
h2_formation = [r for r in net.reactions if r.has_product("H2")]
# Multiple species
water_rxns = [r for r in net.reactions if r.has_any_species(["H2O", "OH"])]
By Number of Reactants/Products
# Unimolecular reactions (1 reactant)
unimolecular = [r for r in net.reactions if len(r.reactants) == 1]
# Bimolecular reactions (2 reactants)
bimolecular = [r for r in net.reactions if len(r.reactants) == 2]
# Reactions with single product
simple_products = [r for r in net.reactions if len(r.products) == 1]
Reaction Analysis
Conservation Checks
Check mass and charge conservation:
# Check individual reaction
rxn = net.reactions[0]
if not rxn.check_mass():
print(f"Mass not conserved: {rxn.verbatim}")
if not rxn.check_charge():
print(f"Charge not conserved: {rxn.verbatim}")
# Check all reactions
errors = []
for i, rxn in enumerate(net.reactions):
if not rxn.check_mass():
errors.append(f"Reaction {i}: mass error - {rxn.verbatim}")
if not rxn.check_charge():
errors.append(f"Reaction {i}: charge error - {rxn.verbatim}")
if errors:
print(f"Found {len(errors)} conservation errors")
else:
print("All reactions pass conservation checks")
Reaction Statistics
from collections import Counter
# Count reaction types
types = Counter(r.guess_type() for r in net.reactions)
print("Reaction type distribution:")
for rtype, count in types.items():
print(f" {rtype}: {count}")
# Count by number of reactants
n_reactants = Counter(len(r.reactants) for r in net.reactions)
print("\nReactant count:")
for n, count in sorted(n_reactants.items()):
print(f" {n} reactants: {count} reactions")
# Count by number of products
n_products = Counter(len(r.products) for r in net.reactions)
print("\nProduct count:")
for n, count in sorted(n_products.items()):
print(f" {n} products: {count} reactions")
Find Duplicate Reactions
# Find identical reactions
duplicates = []
for i, r1 in enumerate(net.reactions):
for r2 in net.reactions[i+1:]:
if r1.is_same(r2):
duplicates.append((i, r1.verbatim))
if duplicates:
print(f"Found {len(duplicates)} duplicate reactions")
for idx, rxn_str in duplicates[:5]:
print(f" {idx}: {rxn_str}")
Find Isomer Versions
# Find reactions with same composition but different species names
isomers = []
for i, r1 in enumerate(net.reactions):
for j, r2 in enumerate(net.reactions[i+1:], start=i+1):
if r1.is_isomer_version(r2):
isomers.append((i, j, r1.verbatim, r2.verbatim))
if isomers:
print(f"Found {len(isomers)} isomer pairs")
for i, j, v1, v2 in isomers[:3]:
print(f" {i}: {v1}")
print(f" {j}: {v2}")
String Representations
Human-Readable Format
# Get verbatim string
equation = rxn.get_verbatim()
print(equation) # "H + H -> H2"
# Print directly (uses __str__)
print(rxn) # Same as above
# Representation (uses __repr__)
print(repr(rxn)) # "Reaction(H + H -> H2, tmin=None, tmax=None, dE=0.0)"
LaTeX Format
# Get LaTeX equation
latex = rxn.get_latex()
print(latex) # "${\rm H} + {\rm H} \to {\rm H_{2}}$"
# Use in Jupyter notebooks
from IPython.display import display, Latex
for rxn in net.reactions[:5]:
display(Latex(rxn.get_latex()))
Serialized Forms
# Serialize using species names
ser = rxn.serialize()
print(ser) # "H_H__H2" (reactants__products)
# Serialize using atomic composition
ser_exp = rxn.serialize_exploded()
print(ser_exp) # "H/H__H/H" (sorted elements)
Code Generation
Generate Rate Expressions
# Generate Python code
for rxn in net.reactions[:3]:
py_code = rxn.get_code(lang="python")
print(f"{rxn.verbatim}: {py_code}")
# Generate C++ code
for rxn in net.reactions[:3]:
cpp_code = rxn.get_code(lang="cxx")
print(f"{rxn.verbatim}: {cpp_code}")
# Generate Fortran code
for rxn in net.reactions[:3]:
f_code = rxn.get_code(lang="fortran")
print(f"{rxn.verbatim}: {f_code}")
Supported languages:
"python"- Python"c"- C"cxx"- C++"fortran"- Fortran"rust"- Rust"julia"- Julia"r"- R
Generate Flux Expressions
# Generate flux expression for ODE system
for i, rxn in enumerate(net.reactions[:3]):
flux = rxn.get_flux_expression(
idx=i,
rate_variable="k",
species_variable="y",
brackets="[]"
)
print(f"flux[{i}] = {flux}")
# Output: "flux[0] = k[0] * y[idx_h] * y[idx_h]"
# Custom bracket style
flux = rxn.get_flux_expression(
idx=0,
rate_variable="rates",
species_variable="conc",
brackets="()",
idx_prefix="n_"
)
Symbolic Manipulation
import sympy
# Get sympy expression
rate_expr = rxn.get_sympy()
# Symbolic differentiation
tgas = sympy.Symbol('tgas')
derivative = sympy.diff(rate_expr, tgas)
print(f"drate/dT = {derivative}")
# Evaluate numerically
rate_func = sympy.lambdify(tgas, rate_expr, "numpy")
import numpy as np
temps = np.logspace(1, 4, 100)
rates = rate_func(temps)
Visualization
Plot Rate vs Temperature
import matplotlib.pyplot as plt
# Plot single reaction
rxn = net.reactions[0]
rxn.plot()
# Plot multiple reactions on same axis
fig, ax = plt.subplots(figsize=(10, 6))
for rxn in net.reactions[:5]:
rxn.plot(ax=ax)
plt.legend([r.verbatim for r in net.reactions[:5]])
plt.title("Reaction Rates vs Temperature")
plt.show()
Plot Cross Sections
For photochemical reactions with cross-section data:
# Find photochemical reactions with cross sections
photo_rxns = [r for r in net.reactions if r.xsecs is not None]
if photo_rxns:
# Plot in eV (default)
photo_rxns[0].plot_xsecs()
# Plot as wavelength in nanometers
photo_rxns[0].plot_xsecs(energy_unit="nm")
# Plot in micrometers with linear scales
photo_rxns[0].plot_xsecs(
energy_unit="um",
energy_log=False,
xsecs_log=False
)
# Compare multiple reactions
fig, ax = plt.subplots()
for rxn in photo_rxns[:3]:
rxn.plot_xsecs(ax=ax, energy_unit="nm")
plt.legend([r.verbatim for r in photo_rxns[:3]])
plt.show()
Energy unit options: "eV", "erg", "nm", "um" (or "micron")
Common Patterns
Formation/Destruction Analysis
def analyze_species(net, species_name):
"""Analyze formation and destruction of a species."""
formation = [r for r in net.reactions if r.has_product(species_name)]
destruction = [r for r in net.reactions if r.has_reactant(species_name)]
print(f"\n{species_name} Chemistry:")
print(f" Formation reactions: {len(formation)}")
print(f" Destruction reactions: {len(destruction)}")
print(f" Ratio: {len(formation)/len(destruction):.2f}")
print("\nMain formation pathways:")
for rxn in formation[:5]:
print(f" {rxn.verbatim}")
print("\nMain destruction pathways:")
for rxn in destruction[:5]:
print(f" {rxn.verbatim}")
# Example usage
analyze_species(net, "H2O")
analyze_species(net, "CO")
Export Reaction List
# Export to text file
with open("reactions.txt", "w") as f:
for i, rxn in enumerate(net.reactions):
f.write(f"{i}: {rxn.verbatim}\n")
f.write(f" Type: {rxn.guess_type()}\n")
f.write(f" Rate: {rxn.rate}\n")
f.write(f" T range: {rxn.tmin} - {rxn.tmax} K\n")
f.write("\n")
# Export to CSV
import csv
with open("reactions.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["Index", "Reaction", "Type", "N_Reactants", "N_Products"])
for i, rxn in enumerate(net.reactions):
writer.writerow([
i,
rxn.verbatim,
rxn.guess_type(),
len(rxn.reactants),
len(rxn.products)
])
Filter by Multiple Criteria
# Complex filtering
selected = []
for rxn in net.reactions:
# Must involve H2
if not rxn.has_any_species("H2"):
continue
# Must be thermal (not photo)
if rxn.guess_type() != "unknown":
continue
# Must have temperature range defined
if rxn.tmin is None or rxn.tmax is None:
continue
# Must conserve mass and charge
if not (rxn.check_mass() and rxn.check_charge()):
continue
selected.append(rxn)
print(f"Selected {len(selected)} reactions matching all criteria")
See Also
- Working with Species - Chemical species
- Loading Networks - Network file formats
- Code Generation - Building simulation code
- API Reference: Reaction - Complete API documentation
Notes
- Reactions are created by loading network files, not by direct instantiation
- Mass conservation allows for electron mass tolerance (9.1093837e-28)
- Charge conservation must be exact
- Rate expressions are stored as sympy expressions
- Cross-section data is only available for photochemical reactions
- Temperature ranges are optional and used primarily for plotting and codegen
Next: Learn about Code Generation.