Skip to content

Reaction API

Reaction

Chemical reaction with stoichiometric coefficients and thermodynamic evaluation.

The Reaction class supports: - Explicit coefficients for all species - Automatic coefficient calculation using atom conservation - Mixed explicit/auto coefficients for partially constrained systems

Coefficient Types

  • Numeric (int, float): Explicit coefficient value
  • Auto or None: Coefficient to be auto-calculated during balance()

Parameters

reactants : list[ReactionSpecies] List of reactant species with coefficients products : list[ReactionSpecies] List of product species with coefficients

Examples

All explicit coefficients

rxn = Reaction.from_smiles( ... reactants=[("CH4", 1), ("O2", 2)], ... products=[("CO2", 1), ("H2O", 2)] ... )

Full auto-balance

rxn = Reaction.from_smiles( ... reactants=["CH4", "O2"], # No coefficients -> all Auto ... products=["CO2", "H2O"] ... ) rxn.balance()

Mixed explicit/auto

rxn = Reaction.from_smiles( ... reactants=[("OCC(O)CO", 1), ("[H][]", Auto)], ... products=[("CC(O)CO", 1), ("O", Auto)] ... ) rxn.balance()

reactants property

reactants: list[ReactionSpecies]

List of reactant species.

products property

products: list[ReactionSpecies]

List of product species.

all_species property

all_species: list[ReactionSpecies]

All species in order: reactants then products.

is_balanced property

is_balanced: bool

Whether the reaction has been balanced.

elements property

elements: set[str]

Set of all elements in the reaction.

coefficients property

coefficients: dict[str, float | None]

Dictionary mapping formula to coefficient.

Returns coefficients in order: reactants (positive), products (positive). Note: For thermodynamic calculations, reactant coefficients are negated.

stoichiometry_vector property

stoichiometry_vector: ndarray | None

Stoichiometry vector nu where nu_i < 0 for reactants, > 0 for products.

Returns None if any coefficient is undetermined.

enthalpy property

enthalpy: float

Standard enthalpy of reaction (Delta H_r) in kJ/mol.

Auto-balances if needed.

delta_h property

delta_h: float

Alias for enthalpy.

entropy property

entropy: float

Standard entropy of reaction (Delta S_r) in J/(mol*K).

Auto-balances if needed.

delta_s property

delta_s: float

Alias for entropy.

gibbs_free_energy property

gibbs_free_energy: float

Standard Gibbs free energy of reaction (Delta G_r) in kJ/mol at 298.15 K.

Calculated as: Delta G = Delta H - T * Delta S

delta_g property

delta_g: float

Alias for gibbs_free_energy.

from_smiles classmethod

from_smiles(reactants: Sequence[str], products: Sequence[str]) -> Reaction
from_smiles(reactants: Sequence[tuple[str, CoeffSpec]], products: Sequence[tuple[str, CoeffSpec]]) -> Reaction
from_smiles(*, reactant_smiles: Sequence[str] | Sequence[tuple[str, CoeffSpec]], product_smiles: Sequence[str] | Sequence[tuple[str, CoeffSpec]]) -> Reaction
from_smiles(reactants: Sequence[str] | Sequence[tuple[str, CoeffSpec]] | None = None, products: Sequence[str] | Sequence[tuple[str, CoeffSpec]] | None = None, *, reactant_smiles: Sequence[str] | Sequence[tuple[str, CoeffSpec]] | None = None, product_smiles: Sequence[str] | Sequence[tuple[str, CoeffSpec]] | None = None) -> Reaction

Create a Reaction from SMILES strings with optional coefficients.

Supports multiple input formats for flexibility:

  1. Simple SMILES lists (all coefficients Auto):

    Reaction.from_smiles(["CH4", "O2"], ["CO2", "H2O"])

  2. Tuples with explicit coefficients:

    Reaction.from_smiles( ... reactants=[("CH4", 1), ("O2", 2)], ... products=[("CO2", 1), ("H2O", 2)] ... )

  3. Mixed explicit/Auto coefficients:

    Reaction.from_smiles( ... reactants=[("OCC(O)CO", 1), ("[H][]", Auto)], ... products=[("CC(O)CO", 1), ("O", Auto)] ... )

  4. Legacy keyword argument style:

    Reaction.from_smiles( ... reactant_smiles=[("CH4", 1), ("O2", 2)], ... product_smiles=[("CO2", 1), ("H2O", 2)] ... )

Parameters

reactants : Sequence[str] | Sequence[tuple[str, CoeffSpec]] Reactant SMILES, optionally with coefficients products : Sequence[str] | Sequence[tuple[str, CoeffSpec]] Product SMILES, optionally with coefficients reactant_smiles : Sequence (keyword, legacy) Alternative way to specify reactants product_smiles : Sequence (keyword, legacy) Alternative way to specify products

Returns

Reaction New Reaction instance

Raises

ValueError If neither positional nor keyword arguments provided InvalidSmilesError If any SMILES string is invalid

from_reaction_smiles classmethod

from_reaction_smiles(reaction_smiles: str, auto_balance: bool = True) -> Reaction

Create a Reaction from a reaction SMILES string.

Format: 'coeff SMILES + coeff SMILES >> coeff SMILES + coeff SMILES' Coefficients are optional; if omitted, marked as Auto.

Parameters

reaction_smiles : str Reaction SMILES with '>>' separator auto_balance : bool If True, automatically balance after parsing (default: True)

Returns

Reaction Parsed reaction

Examples

rxn = Reaction.from_reaction_smiles("CH4 + 2 O2 >> CO2 + 2 H2O") rxn = Reaction.from_reaction_smiles("OCC(O)CO + [H][] >> CC(O)CO + O")

balance

balance(*, normalize: bool = True, prefer_integers: bool = True) -> Reaction

Balance the reaction by solving for unknown coefficients.

Uses the null-space algorithm for atom conservation: - For fully unconstrained: finds basis of null(A) and selects simplest solution - For partially constrained: solves the linear system with known coefficients

Parameters

normalize : bool If True, normalize so smallest coefficient is 1 (default: True) prefer_integers : bool If True, scale to get integer coefficients when possible (default: True)

Returns

Reaction Self (for method chaining)

Raises

OverconstrainedError If the constraints are inconsistent (no solution exists) UnderconstrainedError If multiple solutions exist (need more constraints)

Notes

The balancing problem is formulated as:

For m elements and n species with composition matrix A (m x n): A @ nu = 0 (atom conservation)

where nu is the stoichiometry vector (negative for reactants).

If some coefficients are known, we partition: A @ [nu_known; nu_unknown] = 0 which gives: A_unknown @ nu_unknown = -A_known @ nu_known

Examples

rxn = Reaction.from_smiles(["CH4", "O2"], ["CO2", "H2O"]) rxn.balance() print(rxn) CH4 + 2 O2 -> CO2 + 2 H2O

rxn = Reaction.from_smiles( ... reactants=[("OCC(O)CO", 1), ("[H][]", Auto)], ... products=[("CC(O)CO", 1), ("O", Auto)] ... ) rxn.balance() print(rxn.coefficients)

__str__

__str__() -> str

Human-readable reaction equation.

to_equation

to_equation(*, use_names: bool = False) -> str

Format as a chemical equation string.

Parameters

use_names : bool If True, use compound names instead of formulas (not yet implemented)

Returns

str Formatted equation like "CH4 + 2 O2 -> CO2 + 2 H2O"


Class Overview

from phoenix import Reaction, Auto

rxn = Reaction.from_smiles(
    reactants=[("CH4", 1), ("O=O", Auto)],
    products=[("O=C=O", Auto), ("O", Auto)]
)
rxn.balance()

Auto Sentinel

from phoenix import Auto

Auto is a singleton sentinel value for auto-calculated coefficients.

# These are equivalent
rxn1 = Reaction.from_smiles(
    reactants=[("CH4", Auto), ("O2", Auto)],
    products=[("CO2", Auto), ("H2O", Auto)]
)

rxn2 = Reaction.from_smiles(
    reactants=["CH4", "O2"],
    products=["CO2", "H2O"]
)

Factory Methods

from_smiles

@classmethod
def from_smiles(
    cls,
    reactants: Sequence[str] | Sequence[tuple[str, CoeffSpec]] | None = None,
    products: Sequence[str] | Sequence[tuple[str, CoeffSpec]] | None = None,
    *,
    reactant_smiles: Sequence[...] | None = None,
    product_smiles: Sequence[...] | None = None,
) -> Reaction

Create a Reaction from SMILES strings.

Parameters:

Name Type Description
reactants Sequence Reactant SMILES or (SMILES, coeff) tuples
products Sequence Product SMILES or (SMILES, coeff) tuples
reactant_smiles Sequence Legacy keyword argument
product_smiles Sequence Legacy keyword argument

Coefficient Spec:

  • int or float: Explicit coefficient
  • Auto or None: Auto-calculate

Examples:

# All auto
rxn = Reaction.from_smiles(["CH4", "O2"], ["CO2", "H2O"])

# Mixed
rxn = Reaction.from_smiles(
    reactants=[("CH4", 1), ("O2", Auto)],
    products=[("CO2", Auto), ("H2O", Auto)]
)

from_reaction_smiles

@classmethod
def from_reaction_smiles(cls, reaction_smiles: str, auto_balance: bool = True) -> Reaction

Create from reaction SMILES format.

Format: "coeff SMILES + coeff SMILES >> coeff SMILES + coeff SMILES"

Examples:

rxn = Reaction.from_reaction_smiles("CH4 + 2 O2 >> CO2 + 2 H2O")
rxn = Reaction.from_reaction_smiles("CH4 + O2 >> CO2 + H2O", auto_balance=True)

Properties

Species Access

Property Type Description
reactants list[ReactionSpecies] Reactant species
products list[ReactionSpecies] Product species
all_species list[ReactionSpecies] All species
elements set[str] Elements in reaction
is_balanced bool Whether balanced
coefficients dict[str, float None]
stoichiometry_vector np.ndarray None

Thermodynamic Properties

Property Type Description
enthalpy / delta_h float ΔH_rxn in kJ/mol
entropy / delta_s float ΔS_rxn in J/(mol·K)
gibbs_free_energy / delta_g float ΔG_rxn in kJ/mol

Methods

balance

def balance(
    self,
    *,
    normalize: bool = True,
    prefer_integers: bool = True
) -> Reaction

Balance the reaction.

Parameters:

Name Type Default Description
normalize bool True Normalize smallest coefficient to 1
prefer_integers bool True Convert to integers if possible

Returns: Self (for chaining)

Raises:

  • OverconstrainedError - Inconsistent constraints
  • UnderconstrainedError - Multiple solutions

to_equation

def to_equation(self, *, use_names: bool = False) -> str

Format as equation string.

Returns: "CH4 + 2 O2 -> CO2 + 2 H2O"


ReactionSpecies

ReactionSpecies dataclass

A species (compound) in a reaction with its stoichiometric coefficient.

Attributes

compound : Compound The chemical compound coefficient : float | None Stoichiometric coefficient (None if not yet determined) is_auto : bool Whether coefficient was marked for auto-calculation

formula property

formula: str

Molecular formula of the compound.

composition property

composition: dict[str, int]

Elemental composition of the compound.

with_coefficient

with_coefficient(coeff: float) -> ReactionSpecies

Return a new ReactionSpecies with the specified coefficient.

Attributes

Attribute Type Description
compound Compound The compound
coefficient float None
is_auto bool Was coefficient auto-calculated

Properties

Property Type Description
formula str Molecular formula
composition dict[str, int] Element counts

Methods

def with_coefficient(self, coeff: float) -> ReactionSpecies

Create copy with new coefficient.


Balancing Algorithm

Atom Conservation

For each element j:

\[ \sum_i \nu_i \cdot a_{ij} = 0 \]

Null-Space Method

When all coefficients unknown, find null(A) where A is the composition matrix.

Constrained System

When some coefficients known:

\[ A_{unknown} \cdot \nu_{unknown} = -A_{known} \cdot \nu_{known} \]

Examples

Full Auto-Balance

rxn = Reaction.from_smiles(["CH4", "O2"], ["CO2", "H2O"])
rxn.balance()
print(rxn)  # CH4 + 2 O2 -> CO2 + 2 H2O

Partial Constraints

rxn = Reaction.from_smiles(
    reactants=[("OCC(O)CO", 1), ("[H][H]", Auto)],
    products=[("CC(O)CO", 1), ("O", Auto)]
)
rxn.balance()
print(rxn.coefficients)

Thermodynamics

rxn = Reaction.from_smiles(["CH4", "O2"], ["CO2", "H2O"])
rxn.balance()
print(f"ΔH = {rxn.delta_h:.1f} kJ/mol")
print(f"ΔS = {rxn.delta_s:.1f} J/(mol·K)")
print(f"ΔG = {rxn.delta_g:.1f} kJ/mol")