From 69371e5751aa0c1e9b975d9d9d035634dad23c94 Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Wed, 20 Feb 2019 22:49:44 -0500 Subject: [PATCH] [Python] Implement Reaction constructors from YAML files/strings --- include/cantera/kinetics/Reaction.h | 5 +++ interfaces/cython/cantera/_cantera.pxd | 2 + interfaces/cython/cantera/reaction.pyx | 42 +++++++++++++++++-- .../cython/cantera/test/test_kinetics.py | 31 ++++++++++++++ src/kinetics/Reaction.cpp | 10 +++++ 5 files changed, 87 insertions(+), 3 deletions(-) diff --git a/include/cantera/kinetics/Reaction.h b/include/cantera/kinetics/Reaction.h index 3df105a4c..74317d617 100644 --- a/include/cantera/kinetics/Reaction.h +++ b/include/cantera/kinetics/Reaction.h @@ -284,6 +284,11 @@ unique_ptr newReaction(const AnyMap& rxn_node, const Kinetics& kin); //! bulk (e.g. gas) phases std::vector > getReactions(const XML_Node& node); +//! Create Reaction objects for each item (an AnyMap) in `items`. The species +//! involved in these reactions must exist in the phases associated with the +//! Kinetics object `kinetics`. +std::vector> getReactions(const AnyValue& items, + Kinetics& kinetics); } #endif diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index 8672eefc9..049f3c1fc 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -256,7 +256,9 @@ cdef extern from "cantera/thermo/SurfPhase.h": cdef extern from "cantera/kinetics/Reaction.h" namespace "Cantera": cdef shared_ptr[CxxReaction] CxxNewReaction "newReaction" (XML_Node&) except +translate_exception + cdef shared_ptr[CxxReaction] CxxNewReaction "newReaction" (CxxAnyMap&, CxxKinetics&) except +translate_exception cdef vector[shared_ptr[CxxReaction]] CxxGetReactions "getReactions" (XML_Node&) except +translate_exception + cdef vector[shared_ptr[CxxReaction]] CxxGetReactions "getReactions" (CxxAnyValue&, CxxKinetics&) except +translate_exception cdef cppclass CxxArrhenius "Cantera::Arrhenius": CxxArrhenius() diff --git a/interfaces/cython/cantera/reaction.pyx b/interfaces/cython/cantera/reaction.pyx index 257333d1d..5e36736e2 100644 --- a/interfaces/cython/cantera/reaction.pyx +++ b/interfaces/cython/cantera/reaction.pyx @@ -79,10 +79,28 @@ cdef class Reaction: return wrapReaction(cxx_reaction) @staticmethod - def listFromFile(filename): + def fromYaml(text, Kinetics kinetics): + """ + Create a `Reaction` object from its YAML string representation. + + :param text: + The YAML reaction string + :param kinetics: + A `Kinetics` object whose associated phase(s) contain the species + involved in the reaction. + """ + cxx_reaction = CxxNewReaction(AnyMapFromYamlString(stringify(text)), + deref(kinetics.kinetics)) + return wrapReaction(cxx_reaction) + + @staticmethod + def listFromFile(filename, Kinetics kinetics=None, section='reactions'): """ Create a list of Reaction objects from all of the reactions defined in a - CTI or XML file. + YAML, CTI, or XML file. + + For YAML input files, a `Kinetics` object is required as the second + argument, and reactions from the section *section* will be returned. Directories on Cantera's input file path will be searched for the specified file. @@ -91,7 +109,14 @@ cdef class Reaction: children of the ```` node in a document with a ```` root node, as in the XML files produced by conversion from CTI files. """ - cxx_reactions = CxxGetReactions(deref(CxxGetXmlFile(stringify(filename)))) + if filename.lower().split('.')[-1] in ('yml', 'yaml'): + if kinetics is None: + raise ValueError("A Kinetics object is required.") + root = AnyMapFromYamlFile(stringify(filename)) + cxx_reactions = CxxGetReactions(root[stringify(section)], + deref(kinetics.kinetics)) + else: + cxx_reactions = CxxGetReactions(deref(CxxGetXmlFile(stringify(filename)))) return [wrapReaction(r) for r in cxx_reactions] @staticmethod @@ -116,6 +141,17 @@ cdef class Reaction: cxx_reactions = CxxGetReactions(deref(CxxGetXmlFromString(stringify(text)))) return [wrapReaction(r) for r in cxx_reactions] + @staticmethod + def listFromYaml(text, Kinetics kinetics): + """ + Create a list of `Reaction` objects from all the reactions defined in a + YAML string. + """ + root = AnyMapFromYamlString(stringify(text)) + cxx_reactions = CxxGetReactions(root[stringify("items")], + deref(kinetics.kinetics)) + return [wrapReaction(r) for r in cxx_reactions] + property reactant_string: """ A string representing the reactants side of the chemical equation for diff --git a/interfaces/cython/cantera/test/test_kinetics.py b/interfaces/cython/cantera/test/test_kinetics.py index b0e38f419..a948e927b 100644 --- a/interfaces/cython/cantera/test/test_kinetics.py +++ b/interfaces/cython/cantera/test/test_kinetics.py @@ -767,6 +767,23 @@ class TestReaction(utilities.CanteraTest): self.assertEqual(r.efficiencies['H2O'], 15.4) self.assertEqual(r.rate.temperature_exponent, -1.0) + def test_fromYaml(self): + r = ct.Reaction.fromYaml( + "{equation: 2 O + M <=> O2 + M," + " type: three-body," + " rate-constant: {A: 1.2e+11, b: -1.0, Ea: 0.0}," + " efficiencies: {H2: 2.4, H2O: 15.4, AR: 0.83}}", + self.gas) + + self.assertTrue(isinstance(r, ct.ThreeBodyReaction)) + self.assertEqual(r.reactants['O'], 2) + self.assertEqual(r.products['O2'], 1) + self.assertEqual(r.efficiencies['H2O'], 15.4) + self.assertEqual(r.rate.temperature_exponent, -1.0) + self.assertIn('O', r) + self.assertIn('O2', r) + self.assertNotIn('H2O', r) + def test_listFromFile(self): R = ct.Reaction.listFromFile('h2o2.xml') eq1 = [r.equation for r in R] @@ -789,6 +806,20 @@ class TestReaction(utilities.CanteraTest): eq2 = [r.equation for r in self.gas.reactions()] self.assertEqual(eq1, eq2) + def test_listFromYaml(self): + yaml = """ + - equation: O + H2 <=> H + OH # Reaction 3 + rate-constant: {A: 3.87e+04, b: 2.7, Ea: 6260.0} + - equation: O + HO2 <=> OH + O2 # Reaction 4 + rate-constant: {A: 2.0e+13, b: 0.0, Ea: 0.0} + - equation: O + H2O2 <=> OH + HO2 # Reaction 5 + rate-constant: {A: 9.63e+06, b: 2.0, Ea: 4000.0} + """ + R = ct.Reaction.listFromYaml(yaml, self.gas) + self.assertEqual(len(R), 3) + self.assertIn('HO2', R[2].products) + self.assertEqual(R[0].rate.temperature_exponent, 2.7) + def test_elementary(self): r = ct.ElementaryReaction({'O':1, 'H2':1}, {'H':1, 'OH':1}) r.rate = ct.Arrhenius(3.87e1, 2.7, 6260*1000*4.184) diff --git a/src/kinetics/Reaction.cpp b/src/kinetics/Reaction.cpp index 809089cba..63018cacb 100644 --- a/src/kinetics/Reaction.cpp +++ b/src/kinetics/Reaction.cpp @@ -1080,4 +1080,14 @@ std::vector > getReactions(const XML_Node& node) return all_reactions; } +std::vector> getReactions(const AnyValue& items, + Kinetics& kinetics) +{ + std::vector> all_reactions; + for (const auto& node : items.asVector()) { + all_reactions.emplace_back(newReaction(node, kinetics)); + }; + return all_reactions; +} + }