diff --git a/include/cantera/base/YamlWriter.h b/include/cantera/base/YamlWriter.h index 45b8b354ad4..0bb023b0ecb 100644 --- a/include/cantera/base/YamlWriter.h +++ b/include/cantera/base/YamlWriter.h @@ -31,7 +31,12 @@ class YamlWriter void addPhase(shared_ptr thermo, shared_ptr kin={}, shared_ptr tran={}); + //! Return a YAML string that contains the definitions for the added phases, + //! species, and reactions std::string toYamlString() const; + + //! Write the definitions for the added phases, species and reactions to + //! the specified file. void toYamlFile(const std::string& filename) const; //! For output floating point values, set the maximum number of digits to @@ -47,10 +52,12 @@ class YamlWriter m_skip_user_defined = skip; } - //! Set the units to be used in the output file - void setUnits(const UnitSystem& units) { - m_output_units = units; - } + //! Set the units to be used in the output file. Dimensions not specified + //! will use Cantera's defaults. + //! @param units A map where keys are dimensions (mass, length, time, + //! quantity, pressure, energy, activation-energy) and the values are + //! corresponding units supported by the UnitSystem class. + void setUnits(const std::map& units={}); protected: std::vector> m_phases; diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index 944e1cd53fd..1c37c795d6f 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -513,6 +513,15 @@ cdef extern from "cantera/transport/TransportData.h" namespace "Cantera": double dispersion_coefficient double quadrupole_polarizability +cdef extern from "cantera/base/YamlWriter.h" namespace "Cantera": + cdef cppclass CxxYamlWriter "Cantera::YamlWriter": + CxxYamlWriter() + void addPhase(shared_ptr[CxxSolution]) except +translate_exception + string toYamlString() except +translate_exception + void toYamlFile(string&) except +translate_exception + void setPrecision(int) + void skipUserDefined(cbool) + void setUnits(stdmap[string, string]&) except +translate_exception cdef extern from "cantera/equil/MultiPhase.h" namespace "Cantera": cdef cppclass CxxMultiPhase "Cantera::MultiPhase": @@ -1056,6 +1065,10 @@ cdef class Transport(_SolutionBase): cdef class DustyGasTransport(Transport): pass +cdef class YamlWriter: + cdef shared_ptr[CxxYamlWriter] _writer + cdef CxxYamlWriter* writer + cdef class Mixture: cdef CxxMultiPhase* mix cdef list _phases diff --git a/interfaces/cython/cantera/_cantera.pyx b/interfaces/cython/cantera/_cantera.pyx index 98ec9fd7e83..40548fb28e6 100644 --- a/interfaces/cython/cantera/_cantera.pyx +++ b/interfaces/cython/cantera/_cantera.pyx @@ -20,6 +20,7 @@ include "reaction.pyx" include "kinetics.pyx" include "transport.pyx" +include "yamlwriter.pyx" include "mixture.pyx" include "reactor.pyx" include "onedim.pyx" diff --git a/interfaces/cython/cantera/base.pyx b/interfaces/cython/cantera/base.pyx index cc099053544..d80a6d50be1 100644 --- a/interfaces/cython/cantera/base.pyx +++ b/interfaces/cython/cantera/base.pyx @@ -239,6 +239,40 @@ cdef class _SolutionBase: self.transport.getParameters(params) return mergeAnyMap(params, self.thermo.input()) + def write_yaml(self, filename, phases=None, units=None, precision=None, + skip_user_defined=None): + """ + Write the definition for this phase, any additional phases specified, + and their species and reactions to the specified file. + + :param filename: + The name of the output file + :param phases: + Additional ThermoPhase / Solution objects to be included in the + output file + :param units: + A dictionary of the units to be used for each dimension. See + `YamlWriter.output_units`. + :param precision: + For output floating point values, the maximum number of digits to + the right of the decimal point. The default is 15 digits. + :param skip_user_defined: + If `True`, user-defined fields which are not used by Cantera will + be stripped from the output. + """ + Y = YamlWriter() + Y.add_solution(self) + if phases is not None: + for phase in phases: + Y.add_solution(phase) + if units is not None: + Y.output_units = units + if precision is not None: + Y.precision = precision + if skip_user_defined is not None: + Y.skip_user_defined = skip_user_defined + Y.to_file(filename) + def __getitem__(self, selection): copy = self.__class__(origin=self) if isinstance(selection, slice): diff --git a/interfaces/cython/cantera/kinetics.pyx b/interfaces/cython/cantera/kinetics.pyx index d8a3f53e07a..3b3719fbe8f 100644 --- a/interfaces/cython/cantera/kinetics.pyx +++ b/interfaces/cython/cantera/kinetics.pyx @@ -428,7 +428,7 @@ cdef class InterfaceKinetics(Kinetics): def _phase_slice(self, phase): p = self.phase_index(phase) - k1 = self.kinetics_species_index(0, p) + k1 = self.kinetics_speci(0, p) if p == self.n_phases - 1: k2 = self.n_total_species @@ -460,3 +460,19 @@ cdef class InterfaceKinetics(Kinetics): species in all phases. """ return self.net_production_rates[self._phase_slice(phase)] + + def write_yaml(self, filename, phases=None, units=None, precision=None, + skip_user_defined=None): + """ + See `_SolutionBase.write_yaml`. + """ + if phases is not None: + phases = list(phases) + else: + phases = [] + + for phase in self._phase_indices: + if isinstance(phase, _SolutionBase) and phase is not self: + phases.append(phase) + + super().write_yaml(filename, phases, units, precision, skip_user_defined) diff --git a/interfaces/cython/cantera/test/test_composite.py b/interfaces/cython/cantera/test/test_composite.py index e3ac050bd57..fbc34e90926 100644 --- a/interfaces/cython/cantera/test/test_composite.py +++ b/interfaces/cython/cantera/test/test_composite.py @@ -460,6 +460,47 @@ def test_input_data_debye_huckel(self): self.assertNotIn('kinetics', data) self.assertNotIn('transport', data) + def test_yaml_simple(self): + gas = ct.Solution('h2o2.yaml') + gas.write_yaml('h2o2-generated.yaml') + with open('h2o2-generated.yaml', 'r') as infile: + generated = yaml.safe_load(infile) + for key in ('generator', 'date', 'phases', 'species', 'reactions'): + self.assertIn(key, generated) + self.assertEqual(generated['phases'][0]['transport'], 'mixture-averaged') + for i, species in enumerate(generated['species']): + self.assertEqual(species['composition'], gas.species(i).composition) + for i, reaction in enumerate(generated['reactions']): + self.assertEqual(reaction['equation'], gas.reaction_equation(i)) + + def test_yaml_outunits(self): + gas = ct.Solution('h2o2.yaml') + units = {'length': 'cm', 'quantity': 'mol', 'energy': 'cal'} + gas.write_yaml('h2o2-generated.yaml', units=units) + with open('h2o2-generated.yaml') as infile: + generated = yaml.safe_load(infile) + with open(pjoin(self.cantera_data, "h2o2.yaml")) as infile: + original = yaml.safe_load(infile) + self.assertEqual(generated['units'], units) + + for r1, r2 in zip(original['reactions'], generated['reactions']): + if 'rate-constant' in r1: + self.assertNear(r1['rate-constant']['A'], r2['rate-constant']['A']) + self.assertNear(r1['rate-constant']['Ea'], r2['rate-constant']['Ea']) + + def test_yaml_surface(self): + gas = ct.Solution('ptcombust.yaml', 'gas') + surf = ct.Interface('ptcombust.yaml', 'Pt_surf', [gas]) + surf.write_yaml('ptcombust-generated.yaml') + + with open('ptcombust-generated.yaml') as infile: + generated = yaml.safe_load(infile) + for key in ('phases', 'species', 'gas-reactions', 'Pt_surf-reactions'): + self.assertIn(key, generated) + self.assertEqual(len(generated['gas-reactions']), gas.n_reactions) + self.assertEqual(len(generated['Pt_surf-reactions']), surf.n_reactions) + self.assertEqual(len(generated['species']), surf.n_total_species) + class TestSpeciesSerialization(utilities.CanteraTest): def test_species_simple(self): diff --git a/src/base/YamlWriter.cpp b/src/base/YamlWriter.cpp index 53ae8b1e09d..77e5f181123 100644 --- a/src/base/YamlWriter.cpp +++ b/src/base/YamlWriter.cpp @@ -208,4 +208,10 @@ void YamlWriter::toYamlFile(const std::string& filename) const out << toYamlString(); } +void YamlWriter::setUnits(const std::map& units) +{ + m_output_units = UnitSystem(); + m_output_units.setDefaults(units); +} + } diff --git a/test/general/test_serialization.cpp b/test/general/test_serialization.cpp index fc370710b96..0703b967bfb 100644 --- a/test/general/test_serialization.cpp +++ b/test/general/test_serialization.cpp @@ -114,14 +114,11 @@ TEST(YamlWriter, reaction_units_from_Yaml) YamlWriter writer; writer.addPhase(original); writer.setPrecision(14); - UnitSystem outUnits; - std::map defaults = { + writer.setUnits({ {"activation-energy", "K"}, {"quantity", "mol"}, {"length", "cm"} - }; - outUnits.setDefaults(defaults); - writer.setUnits(outUnits); + }); writer.toYamlFile("generated-h2o2-outunits.yaml"); auto duplicate = newSolution("generated-h2o2-outunits.yaml"); @@ -143,19 +140,16 @@ TEST(YamlWriter, reaction_units_from_Xml) YamlWriter writer; writer.addPhase(original); writer.setPrecision(14); - UnitSystem outUnits; - std::map defaults = { + writer.setUnits({ {"activation-energy", "K"}, {"quantity", "mol"}, {"length", "cm"} - }; - outUnits.setDefaults(defaults); - writer.setUnits(outUnits); + }); // Should fail because pre-exponential factors from XML can't be converted EXPECT_THROW(writer.toYamlFile("generated-h2o2-fail.yaml"), CanteraError); // Outputting with the default MKS+kmol system still works - writer.setUnits(UnitSystem()); + writer.setUnits(); writer.toYamlFile("generated-h2o2-from-xml.yaml"); auto duplicate = newSolution("generated-h2o2-from-xml.yaml"); @@ -177,15 +171,12 @@ TEST(YamlWriter, chebyshev_units_from_Yaml) YamlWriter writer; writer.addPhase(original); writer.setPrecision(14); - UnitSystem outUnits; - std::map defaults = { + writer.setUnits({ {"activation-energy", "K"}, {"quantity", "mol"}, {"length", "cm"}, {"pressure", "atm"} - }; - outUnits.setDefaults(defaults); - writer.setUnits(outUnits); + }); writer.toYamlFile("generated-pdep-test.yaml"); auto duplicate = newSolution("generated-pdep-test.yaml"); @@ -246,9 +237,11 @@ TEST(YamlWriter, Interface) YamlWriter writer; writer.addPhase(gas1); writer.addPhase(surf1, kin1); - UnitSystem U{"mm", "molec"}; - U.setDefaultActivationEnergy("K"); - writer.setUnits(U); + writer.setUnits({ + {"length", "mm"}, + {"quantity", "molec"}, + {"activation-energy", "K"} + }); writer.toYamlFile("generated-ptcombust.yaml"); shared_ptr gas2(newPhase("generated-ptcombust.yaml", "gas")); @@ -301,11 +294,11 @@ TEST(YamlWriter, sofc) writer.addPhase(metal1); writer.addPhase(gas1); writer.addPhase(ox_bulk1); - - UnitSystem U; - U.setDefaults({"cm", "atm"}); - U.setDefaultActivationEnergy("eV"); - writer.setUnits(U); + writer.setUnits({ + {"length", "cm"}, + {"pressure", "atm"}, + {"activation-energy", "eV"} + }); writer.toYamlFile("generated-sofc.yaml"); shared_ptr gas2(newPhase("generated-sofc.yaml", "gas"));