diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index a70ad1a69..68cec9fd0 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -5,6 +5,7 @@ from libcpp.vector cimport vector from libcpp.string cimport string from libcpp.map cimport map as stdmap +from libcpp.unordered_map cimport unordered_map from libcpp.pair cimport pair from libcpp cimport bool as cbool from cpython cimport bool as pybool @@ -43,6 +44,21 @@ cdef extern from "cantera/base/xml.h" namespace "Cantera": XML_Node* findID(string) int nChildren() +cdef extern from "cantera/base/AnyMap.h" namespace "Cantera": + cdef cppclass CxxAnyValue "Cantera::AnyValue" + + cdef cppclass CxxAnyMap "Cantera::AnyMap": + CxxAnyMap() + CxxAnyValue& operator[](string) except +translate_exception + string keys_str() + + cdef cppclass CxxAnyValue "Cantera::AnyValue": + CxxAnyValue() + unordered_map[string, CxxAnyMap*] asMap(string) except +translate_exception + + CxxAnyMap AnyMapFromYamlFile "Cantera::AnyMap::fromYamlFile" (string) except +translate_exception + CxxAnyMap AnyMapFromYamlString "Cantera::AnyMap::fromYamlString" (string) except +translate_exception + cdef extern from "cantera/base/stringUtils.h" namespace "Cantera": cdef Composition parseCompString(string) except +translate_exception @@ -610,10 +626,12 @@ cdef extern from "cantera/zerodim.h" namespace "Cantera": cdef extern from "cantera/thermo/ThermoFactory.h" namespace "Cantera": cdef CxxThermoPhase* newPhase(string, string) except +translate_exception cdef CxxThermoPhase* newPhase(XML_Node&) except +translate_exception + cdef shared_ptr[CxxThermoPhase] newPhase(CxxAnyMap&, CxxAnyMap&) except +translate_exception cdef CxxThermoPhase* newThermoPhase(string) except +translate_exception cdef extern from "cantera/kinetics/KineticsFactory.h" namespace "Cantera": cdef CxxKinetics* newKineticsMgr(XML_Node&, vector[CxxThermoPhase*]) except +translate_exception + cdef shared_ptr[CxxKinetics] newKinetics(vector[CxxThermoPhase*], CxxAnyMap&, CxxAnyMap&) except +translate_exception cdef CxxKinetics* CxxNewKinetics "Cantera::newKineticsMgr" (string) except +translate_exception cdef extern from "cantera/transport/TransportFactory.h" namespace "Cantera": diff --git a/interfaces/cython/cantera/base.pyx b/interfaces/cython/cantera/base.pyx index 5c5b24f66..c91e0c501 100644 --- a/interfaces/cython/cantera/base.pyx +++ b/interfaces/cython/cantera/base.pyx @@ -3,8 +3,8 @@ cdef class _SolutionBase: def __cinit__(self, infile='', phaseid='', phases=(), origin=None, - source=None, thermo=None, species=(), kinetics=None, - reactions=(), **kwargs): + source=None, yaml=None, thermo=None, species=(), + kinetics=None, reactions=(), **kwargs): # Shallow copy of an existing Solution (for slicing support) cdef _SolutionBase other if origin is not None: @@ -21,7 +21,9 @@ cdef class _SolutionBase: self._selected_species = other._selected_species.copy() return - if infile or source: + if infile.endswith('.yml') or infile.endswith('.yaml') or yaml: + self._init_yaml(infile, phaseid, phases, yaml) + elif infile or source: self._init_cti_xml(infile, phaseid, phases, source) elif thermo and species: self._init_parts(thermo, species, kinetics, phases, reactions) @@ -37,6 +39,55 @@ cdef class _SolutionBase: if isinstance(self, Transport): assert self.transport is not NULL + def _init_yaml(self, infile, phaseid, phases, source): + """ + Instantiate a set of new Cantera C++ objects from a YAML + phase definition + """ + cdef CxxAnyMap root + if infile: + root = AnyMapFromYamlFile(stringify(infile)) + elif source: + root = AnyMapFromYamlString(stringify(source)) + + phaseNodes = root[stringify("phases")].asMap(stringify("name")) + phaseNames = [] + for item in phaseNodes: + phaseNames.append(pystr(item.first)) + + if not phaseNames: + raise ValueError("YAML document doesn't contain any phase definitions") + + if phaseid: + if phaseid in phaseNames: + phaseNode = phaseNodes[stringify(phaseid)] + else: + raise ValueError("YAML document doesn't contain" + " a phase named '{}'".format(phaseid)) + else: + phaseNode = phaseNodes[stringify(phaseNames[0])] + + # Thermo + if isinstance(self, ThermoPhase): + self._thermo = newPhase(deref(phaseNode), root) + self.thermo = self._thermo.get() + else: + self.thermo = NULL + + # Kinetics + cdef vector[CxxThermoPhase*] v + cdef _SolutionBase phase + + if isinstance(self, Kinetics): + v.push_back(self.thermo) + for phase in phases: + # adjacent bulk phases for a surface phase + v.push_back(phase.thermo) + self._kinetics = newKinetics(v, deref(phaseNode), root) + self.kinetics = self._kinetics.get() + else: + self.kinetics = NULL + def _init_cti_xml(self, infile, phaseid, phases, source): """ Instantiate a set of new Cantera C++ objects from a CTI or XML diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index 5b4556bef..a44ad7a4b 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -919,6 +919,18 @@ ideal_gas(name='spam', elements='O H', with self.assertRaisesRegex(ct.CanteraError, 'reaction is unbalanced'): ct.Solution('h2o2_unbalancedReaction.xml') + def test_yaml_ideal_gas_simple(self): + gas = ct.ThermoPhase('ideal-gas.yaml', 'simple') + self.check(gas, 'simple', 500, 10 * ct.one_atm, 3, 2) + + def test_yaml_ideal_gas_remote_species(self): + gas = ct.ThermoPhase('ideal-gas.yaml', 'species-remote') + self.check(gas, 'species-remote', 300, ct.one_atm, 4, 2) + + def test_yaml_duplicate(self): + with self.assertRaisesRegex(ct.CanteraError, 'duplicate'): + gas = ct.ThermoPhase('ideal-gas.yaml', 'duplicate-species') + class TestSpecies(utilities.CanteraTest): def setUp(self):