Skip to content

Commit

Permalink
[Kinetics] Introduce Reaction.from_dict in Python
Browse files Browse the repository at this point in the history
This commit squashes several approaches for the creation of a Reaction
from a dictionary that corresponds to a YAML definition.
  • Loading branch information
ischoegl authored and speth committed Jun 12, 2021
1 parent e1256ea commit 4945645
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 13 deletions.
56 changes: 50 additions & 6 deletions interfaces/cython/cantera/reaction.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -440,12 +440,53 @@ cdef class Reaction:
return Reaction.wrap(cxx_reaction)

@classmethod
def fromYaml(cls, text, Kinetics kinetics):
def from_dict(cls, data, Kinetics kinetics=None):
"""
Create a `Reaction` object from a dictionary corresponding to its YAML
representation.
An example for the creation of a Reaction from a dictionary is::
rxn = Reaction.from_dict(
{"equation": "O + H2 <=> H + OH",
"rate-constant": {"A": 38.7, "b": 2.7, "Ea": 26191840.0}},
kinetics=gas)
In the example, *gas* is a Kinetics (or Solution) object.
:param data:
A dictionary corresponding to the YAML representation.
:param kinetics:
A `Kinetics` object whose associated phase(s) contain the species
involved in the reaction.
"""
if cls._reaction_type != "":
raise TypeError(
"Class method 'from_dict' was invoked from '{}' but should "
"be called from base class 'Reaction'".format(cls.__name__))
if kinetics is None:
raise ValueError("A Kinetics object is required.")

cdef CxxAnyMap any_map = dict_to_anymap(data)
cxx_reaction = CxxNewReaction(any_map, deref(kinetics.kinetics))
return Reaction.wrap(cxx_reaction)

@classmethod
def fromYaml(cls, text, Kinetics kinetics=None):
"""
Create a `Reaction` object from its YAML string representation.
An example for the creation of a Reaction from a YAML string is::
rxn = Reaction.fromYaml('''
equation: O + H2 <=> H + OH
rate-constant: {A: 38.7, b: 2.7, Ea: 6260.0 cal/mol}
''', kinetics=gas)
In the example, *gas* is a Kinetics (or Solution) object.
:param text:
The YAML reaction string
The YAML reaction string.
:param kinetics:
A `Kinetics` object whose associated phase(s) contain the species
involved in the reaction.
Expand All @@ -454,9 +495,12 @@ cdef class Reaction:
raise TypeError(
"Class method 'fromYaml' was invoked from '{}' but should "
"be called from base class 'Reaction'".format(cls.__name__))
if kinetics is None:
raise ValueError("A Kinetics object is required.")

cxx_reaction = CxxNewReaction(AnyMapFromYamlString(stringify(text)),
deref(kinetics.kinetics))
cdef CxxAnyMap any_map
any_map = AnyMapFromYamlString(stringify(text))
cxx_reaction = CxxNewReaction(any_map, deref(kinetics.kinetics))
return Reaction.wrap(cxx_reaction)

@staticmethod
Expand Down Expand Up @@ -1160,7 +1204,7 @@ cdef class PlogReaction(Reaction):
A pressure-dependent reaction parameterized by logarithmically interpolating
between Arrhenius rate expressions at various pressures.
An example for the definition of an `PlogReaction` object is given as::
An example for the definition of a `PlogReaction` object is given as::
rxn = PlogReaction(
equation="H2 + O2 <=> 2 OH",
Expand Down Expand Up @@ -1315,7 +1359,7 @@ cdef class ChebyshevReaction(Reaction):
A pressure-dependent reaction parameterized by a bivariate Chebyshev
polynomial in temperature and pressure.
An example for the definition of an `PlogReaction` object is given as::
An example for the definition of a `ChebyshevReaction` object is given as::
rxn = ChebyshevReaction(
equation="HO2 <=> OH + O",
Expand Down
27 changes: 20 additions & 7 deletions interfaces/cython/cantera/test/test_reaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,23 +316,26 @@ def setUpClass(cls):
cls.gas.TP = 900, 2*ct.one_atm
cls.species = cls.gas.species()

def check_rxn(self, rxn):
def check_rxn(self, rxn, check_legacy=True):
# helper function that checks reaction configuration
ix = self._index
self.assertEqual(rxn.reaction_type, self._type)
self.assertEqual(rxn.reactants, self.gas.reaction(ix).reactants)
self.assertEqual(rxn.products, self.gas.reaction(ix).products)
self.assertEqual(rxn.uses_legacy, self._type.endswith("-legacy"))
if check_legacy:
self.assertEqual(rxn.reaction_type, self._type)
self.assertEqual(rxn.uses_legacy, self._type.endswith("-legacy"))
self.assertEqual(rxn.uses_legacy, self._legacy)

gas2 = ct.Solution(thermo="IdealGas", kinetics="GasKinetics",
species=self.species, reactions=[rxn])
gas2.TPX = self.gas.TPX
self.check_solution(gas2)
self.check_solution(gas2, check_legacy)

def check_solution(self, gas2):
def check_solution(self, gas2, check_legacy=True):
# helper function that checks evaluation of reaction rates
ix = self._index
self.assertEqual(gas2.reaction_type_str(0), self._type)
if check_legacy:
self.assertEqual(gas2.reaction_type_str(0), self._type)
self.assertNear(gas2.forward_rate_constants[0],
self.gas.forward_rate_constants[ix])
self.assertNear(gas2.net_rates_of_progress[0],
Expand All @@ -357,7 +360,7 @@ def test_from_parts(self):
rxn.rate = self._rate_obj
self.check_rxn(rxn)

def test_from_dict(self):
def test_from_dict1(self):
# check instantiation from keywords / rate defined by dictionary
rxn = self._cls(equation=self._equation, rate=self._rate, kinetics=self.gas,
legacy=self._legacy, **self._kwargs)
Expand All @@ -370,6 +373,16 @@ def test_from_yaml(self):
rxn = ct.Reaction.fromYaml(self._yaml, kinetics=self.gas)
self.check_rxn(rxn)

def test_from_dict2(self):
# check instantiation from a yaml dictionary
if self._yaml is None:
return
rxn1 = ct.Reaction.fromYaml(self._yaml, kinetics=self.gas)
input_data = rxn1.input_data
rxn2 = ct.Reaction.from_dict(input_data, kinetics=self.gas)
# cannot compare types as input_data does not recreate legacy objects
self.check_rxn(rxn2, check_legacy=False)

def test_from_rate(self):
# check instantiation from keywords / rate provided as object
if self._rate_obj is None:
Expand Down

0 comments on commit 4945645

Please sign in to comment.