Reaction#

class Reaction(reaction, reactant=None, X=1.0, chemicals=None, basis='mol', *, phases=None, check_mass_balance=False, check_atomic_balance=False, correct_atomic_balance=False, correct_mass_balance=False)[source]#

Create a Reaction object which defines a stoichiometric reaction and conversion. A Reaction object is capable of reacting the material flow rates of a thermosteam.Stream object.

Parameters:
  • reaction (dict or str) – A dictionary of stoichiometric coefficients or a stoichiometric equation written as: i1 R1 + … + in Rn -> j1 P1 + … + jm Pm

  • reactant (str, optional) – ID of reactant chemical. Defaults to reactant if only one available.

  • X (float, optional) – Reactant conversion (fraction). Defaults to 1.

  • chemicals (Chemicals, optional) – Chemicals corresponding to each entry in the stoichiometry array. Defaults to settings.chemicals.

  • basis ({'mol', 'wt'}, optional) – Basis of reaction. Defaults to ‘mol’.

  • check_mass_balance=False (bool) – Whether to check if mass is not created or destroyed.

  • correct_mass_balance=False (bool) – Whether to make sure mass is not created or destroyed by varying the reactant stoichiometric coefficient.

  • check_atomic_balance=False (bool) – Whether to check if stoichiometric balance by atoms cancel out.

  • correct_atomic_balance=False (bool) – Whether to correct the stoichiometry according to the atomic balance.

Notes

A reaction object can react either a stream or an array. When a stream is passed, it reacts either the mol or mass flow rate according to the basis of the reaction object. When an array is passed, the array elements are reacted regardless of what basis they are associated with.

Warning

Negative conversions and conversions above 1.0 are fair game (allowed), but may lead to odd/infeasible values when reacting a stream.

Examples

Electrolysis of water to molecular hydrogen and oxygen:

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['H2O', 'H2', 'O2'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> reaction = tmo.Reaction('2H2O,l -> 2H2,g + O2,g', reactant='H2O', X=0.7)
>>> reaction.show() # Note that the default basis is by 'mol'
Reaction (by mol):
stoichiometry             reactant    X[%]
H2O,l -> H2,g + 0.5 O2,g  H2O,l      70.00
>>> reaction.reactant # The reactant is a tuple of phase and chemical ID
('l', 'H2O')
>>> feed = tmo.Stream('feed', H2O=100)
>>> feed.phases = ('g', 'l') # Gas and liquid phases must be available
>>> reaction(feed) # Call to run reaction on molar flow
>>> feed.show() # Notice how 70% of water was converted to product
MultiStream: feed
phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
flow (kmol/hr): (g) H2   70
                    O2   35
                (l) H2O  30

Let’s change to a per ‘wt’ basis:

>>> reaction.basis = 'wt'
>>> reaction.show()
Reaction (by wt):
stoichiometry                     reactant    X[%]
H2O,l -> 0.112 H2,g + 0.888 O2,g  H2O,l      70.00

Although we changed the basis, the end result is the same if we pass a stream:

>>> feed = tmo.Stream('feed', H2O=100)
>>> feed.phases = ('g', 'l')
>>> reaction(feed) # Call to run reaction on mass flow
>>> feed.show() # Notice how 70% of water was converted to product
MultiStream: feed
phases: ('g', 'l'), T: 298.15 K, P: 101325 Pa
flow (kmol/hr): (g) H2   70
                    O2   35
                (l) H2O  30

If chemicals phases are not specified, Reaction objects can react a any single phase Stream object (regardless of phase):

>>> reaction = tmo.Reaction('2H2O -> 2H2 + O2', reactant='H2O', X=0.7)
>>> feed = tmo.Stream('feed', H2O=100, phase='g')
>>> reaction(feed)
>>> feed.show()
Stream: feed
phase: 'g', T: 298.15 K, P: 101325 Pa
flow (kmol/hr): H2O  30
                H2   70
                O2   35

Alternatively, it’s also possible to react an array (instead of a stream):

>>> import numpy as np
>>> array = np.array([100., 0. , 0.])
>>> reaction(array)
>>> array
array([30., 70., 35.])

Reaction objects with the same reactant can be added together:

>>> tmo.settings.set_thermo(['Glucose', 'Ethanol', 'H2O', 'O2', 'CO2'])
>>> fermentation = tmo.Reaction('Glucose + O2 -> Ethanol + CO2', reactant='Glucose', X=0.7)
>>> combustion = tmo.Reaction('Glucose + O2 -> H2O + CO2', reactant='Glucose', X=0.2)
>>> mixed_reaction = fermentation + combustion
>>> mixed_reaction.show()
Reaction (by mol):
stoichiometry                                    reactant    X[%]
Glucose + O2 -> 0.778 Ethanol + 0.222 H2O + CO2  Glucose    90.00

Note how conversions are added and the stoichiometry rescales to a per reactant basis. Conversely, reaction objects may be subtracted as well:

>>> combustion = mixed_reaction - fermentation
>>> combustion.show()
Reaction (by mol):
stoichiometry              reactant    X[%]
Glucose + O2 -> H2O + CO2  Glucose    20.00

When a Reaction object is multiplied (or divided), a new Reaction object with the conversion multiplied (or divided) is returned:

>>> combustion_multiplied = 2 * combustion
>>> combustion_multiplied.show()
Reaction (by mol):
stoichiometry              reactant    X[%]
Glucose + O2 -> H2O + CO2  Glucose    40.00
>>> fermentation_divided = fermentation / 2
>>> fermentation_divided.show()
Reaction (by mol):
stoichiometry                  reactant    X[%]
Glucose + O2 -> Ethanol + CO2  Glucose    35.00
property reaction_chemicals#

Return all chemicals involved in the reaction.

copy(basis=None)[source]#

Return copy of Reaction object.

has_reaction()[source]#

Return whether any reaction takes place.

force_reaction(material)[source]#

React material ignoring feasibility checks.

reactant_demand(reactant, basis=None, reactant_demand=None)[source]#

Return or set the demand of any reactant (i.e., the reactant’s stoichiometric coefficient multiplied by the conversion).

If this function is used to set the reactant yield, the conversion is updated and the stoichiometric coefficient is kept constant.

Parameters:
  • reactant (str, optional) – ID of the reactant chemical.

  • basis (str, optional) – Can be ‘mol’ or ‘wt’. Defaults to the Reaction object’s basis.

  • reactant_demand (float, optional) – New reactant demand as a number between 0 and 1. If none given, the reactant demand is returned.

product_yield(product, basis=None, product_yield=None)[source]#

Return or set the yield of a product per reactant (i.e., the product’s stoichiometric coefficient multiplied by the conversion).

If this function is used to set the product yield, the conversion is updated and the stoichiometric coefficient is kept constant.

Parameters:
  • product (str, optional) – ID of the product chemical.

  • basis (str, optional) – Can be ‘mol’ or ‘wt’. Defaults to the Reaction object’s basis.

  • product_yield (float, optional) – New product yield as a number between 0 and 1. If none given, the product yield is returned.

adiabatic_reaction(stream, Q=0)[source]#

React stream material adiabatically, accounting for the change in enthalpy due to the heat of reaction.

Examples

Note how the stream temperature changes after each reaction due to the heat of reaction. Adiabatic combustion of hydrogen:

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['H2', 'O2', 'H2O'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> reaction = tmo.Reaction('2H2 + O2 -> 2H2O', reactant='H2', X=0.7)
>>> s1 = tmo.Stream('s1', H2=10, O2=20, H2O=1000, T=373.15, phase='g')
>>> s2 = tmo.Stream('s2')
>>> s2.copy_like(s1) # s1 and s2 are the same
>>> s1.show() # Before reaction
Stream: s1
phase: 'g', T: 373.15 K, P: 101325 Pa
flow (kmol/hr): H2   10
                O2   20
                H2O  1e+03
>>> reaction.show()
Reaction (by mol):
stoichiometry       reactant    X[%]
H2 + 0.5 O2 -> H2O  H2         70.00
>>> reaction(s1)
>>> s1.show() # After isothermal reaction
Stream: s1
phase: 'g', T: 373.15 K, P: 101325 Pa
flow (kmol/hr): H2   3
                O2   16.5
                H2O  1.01e+03
>>> reaction.adiabatic_reaction(s2)
>>> s2.show() # After adiabatic reaction
Stream: s2
phase: 'g', T: 421.6 K, P: 101325 Pa
flow (kmol/hr): H2   3
                O2   16.5
                H2O  1.01e+03

Adiabatic combustion of H2 and CH4:

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['H2', 'CH4', 'O2', 'CO2', 'H2O'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> reaction = tmo.ParallelReaction([
...    #            Reaction definition          Reactant    Conversion
...    tmo.Reaction('2H2 + O2 -> 2H2O',        reactant='H2',  X=0.7),
...    tmo.Reaction('CH4 + O2 -> CO2 + 2H2O',  reactant='CH4', X=0.1)
... ])
>>> s1 = tmo.Stream('s1', H2=10, CH4=5, O2=100, H2O=100, T=373.15, phase='g')
>>> s2 = tmo.Stream('s2')
>>> s1.show() # Before reaction
Stream: s1
phase: 'g', T: 373.15 K, P: 101325 Pa
flow (kmol/hr): H2   10
                CH4  5
                O2   100
                H2O  100
>>> reaction.show()
ParallelReaction (by mol):
index  stoichiometry            reactant    X[%]
[0]    H2 + 0.5 O2 -> H2O       H2         70.00
[1]    CH4 + O2 -> CO2 + 2 H2O  CH4        10.00
>>> reaction.adiabatic_reaction(s1)
>>> s1.show() # After adiabatic reaction
Stream: s1
phase: 'g', T: 666.38 K, P: 101325 Pa
flow (kmol/hr): H2   3
                CH4  4.5
                O2   96
                CO2  0.5
                H2O  108

Sequential combustion of CH4 and CO:

>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['CH4', 'CO','O2', 'CO2', 'H2O'], cache=True)
>>> tmo.settings.set_thermo(chemicals)
>>> reaction = tmo.SeriesReaction([
...     #            Reaction definition                 Reactant       Conversion
...     tmo.Reaction('2CH4 + 3O2 -> 2CO + 4H2O',       reactant='CH4',    X=0.7),
...     tmo.Reaction('2CO + O2 -> 2CO2',               reactant='CO',     X=0.1)
...     ])
>>> s1 = tmo.Stream('s1', CH4=5, O2=100, H2O=100, T=373.15, phase='g')
>>> s1.show() # Before reaction
Stream: s1
phase: 'g', T: 373.15 K, P: 101325 Pa
flow (kmol/hr): CH4  5
                O2   100
                H2O  100
>>> reaction.show()
SeriesReaction (by mol):
index  stoichiometry               reactant    X[%]
[0]    CH4 + 1.5 O2 -> CO + 2 H2O  CH4        70.00
[1]    CO + 0.5 O2 -> CO2          CO         10.00
>>> reaction.adiabatic_reaction(s1)
>>> s1.show() # After adiabatic reaction
Stream: s1
phase: 'g', T: 650.01 K, P: 101325 Pa
flow (kmol/hr): CH4  1.5
                CO   3.15
                O2   94.6
                CO2  0.35
                H2O  107
X_net(indexer=False)[source]#

Return net reaction conversion of reactants as a dictionary or a ChemicalIndexer if indexer is True.

property dH#

Heat of reaction at given conversion. Units are in either J/mol-reactant or J/g-reactant; depending on basis.

Warning

This property also accounts for: * Latent heats (at 298.15 K) when phases are specified. * The extent of reaction, X. * The basis of the reaction (by mol or by wt)

property X#

[float] Reaction conversion as a fraction.

property stoichiometry#

[sparse array] Stoichiometry coefficients.

property istoichiometry#

[ChemicalIndexer] Stoichiometry coefficients.

property reactant#

[str] Reactant associated to conversion.

property MWs#

[1d array] Molecular weights of all chemicals [g/mol].

property basis#

{‘mol’, ‘wt’} Basis of reaction

mass_balance_error()[source]#

Return error in stoichiometric mass balance. If positive, mass is being created. If negative, mass is being destroyed.

atomic_balance_error()[source]#

Return a dictionary of errors in stoichiometric atomic balances. If value is positive, the atom is being created. If negative, the atom is being destroyed.

check_mass_balance(tol=0.001)[source]#

Check that stoichiometric mass balance is correct.

check_atomic_balance(tol=0.001)[source]#

Check that stoichiometric atomic balance is correct.

correct_mass_balance(variable=None)[source]#

Make sure mass is not created or destroyed by varying the reactant stoichiometric coefficient.

correct_atomic_balance(constants=None)[source]#

Correct stoichiometry coefficients to satisfy atomic balance.

Parameters:

constants (str, optional) – IDs of chemicals for which stoichiometric coefficients are held constant.

Examples

Balance glucose fermentation to ethanol:

>>> import thermosteam as tmo
>>> tmo.settings.set_thermo(['Glucose', 'O2', 'CO2', 'Ethanol', 'CH4', 'Water'])
>>> fermentation = tmo.Reaction('Glucose + O2 -> Ethanol + CO2',
...                             reactant='Glucose',  X=0.9)
>>> fermentation.correct_atomic_balance()
>>> fermentation.show()
Reaction (by mol):
stoichiometry                 reactant    X[%]
Glucose -> 2 CO2 + 2 Ethanol  Glucose    90.00

Balance methane combustion:

>>> combustion = tmo.Reaction('CH4 + O2 -> Water + CO2',
...                           reactant='CH4', X=1)
>>> combustion.correct_atomic_balance()
>>> combustion.show()
Reaction (by mol):
stoichiometry                reactant    X[%]
2 O2 + CH4 -> CO2 + 2 Water  CH4       100.00

Balance electrolysis of water (with chemical phases specified):

>>> electrolysis = tmo.Reaction('H2O,l -> H2,g + O2,g',
...                             chemicals=tmo.Chemicals(['H2O', 'H2', 'O2']),
...                             reactant='H2O', X=1)
>>> electrolysis.correct_atomic_balance()
>>> electrolysis.show()
Reaction (by mol):
stoichiometry             reactant    X[%]
H2O,l -> H2,g + 0.5 O2,g  H2O,l     100.00

Note that if the reaction is underspecified, there are infinite ways to balance the reaction and a runtime error is raised:

>>> rxn_underspecified = tmo.Reaction('CH4 + Glucose + O2 -> Water + CO2',
...                                   reactant='CH4', X=1)
>>> rxn_underspecified.correct_atomic_balance()
Traceback (most recent call last):
RuntimeError: reaction stoichiometry is underspecified; pass the
`constants` argument to the `<Reaction>.correct_atomic_balance` method
to specify which stoichiometric coefficients to hold constant

Chemical coefficients can be held constant to prevent this error:

>>> rxn_underspecified = tmo.Reaction('CH4 + Glucose + O2 -> Water + CO2',
...                                   reactant='CH4', X=1)
>>> rxn_underspecified.correct_atomic_balance(['Glucose', 'CH4'])
>>> rxn_underspecified.show()
Reaction (by mol):
stoichiometry                            reactant    X[%]
Glucose + 8 O2 + CH4 -> 7 CO2 + 8 Water  CH4       100.00