From 941b5dc765d569a5217a74f956fb3cc3850d948d Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Sat, 8 May 2021 22:18:28 -0400 Subject: [PATCH] [Python] Enable modification of user input data fields Data specified this way is stored when writing to YAML files. --- interfaces/cython/cantera/_cantera.pxd | 7 ++++ interfaces/cython/cantera/base.pyx | 19 ++++++++++- interfaces/cython/cantera/reaction.pyx | 16 +++++++++ interfaces/cython/cantera/speciesthermo.pyx | 16 +++++++++ .../cython/cantera/test/test_composite.py | 34 +++++++++++++++++++ interfaces/cython/cantera/thermo.pyx | 16 +++++++++ interfaces/cython/cantera/transport.pyx | 16 +++++++++ 7 files changed, 123 insertions(+), 1 deletion(-) diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index 0999a4039c..028dd7318a 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -74,6 +74,8 @@ cdef extern from "cantera/base/AnyMap.h" namespace "Cantera": CxxAnyValue& operator[](string) except +translate_exception cbool empty() cbool hasKey(string) + void clear() + void update(CxxAnyMap& other, cbool) string keys_str() void applyUnits() @@ -135,6 +137,7 @@ cdef extern from "cantera/thermo/SpeciesThermoInterpType.h": void reportParameters(size_t&, int&, double&, double&, double&, double* const) except +translate_exception int nCoeffs() except +translate_exception CxxAnyMap parameters(cbool) except +translate_exception + CxxAnyMap& input() cdef extern from "cantera/thermo/SpeciesThermoFactory.h": cdef CxxSpeciesThermo* CxxNewSpeciesThermo "Cantera::newSpeciesThermoInterpType"\ @@ -154,6 +157,7 @@ cdef extern from "cantera/thermo/Species.h" namespace "Cantera": double charge double size CxxAnyMap parameters(CxxThermoPhase*) except +translate_exception + CxxAnyMap input cdef shared_ptr[CxxSpecies] CxxNewSpecies "newSpecies" (XML_Node&) cdef vector[shared_ptr[CxxSpecies]] CxxGetSpecies "getSpecies" (XML_Node&) @@ -187,6 +191,7 @@ cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera": string type() string phaseOfMatter() except +translate_exception void getSpeciesParameters(string, CxxAnyMap&) except +translate_exception + CxxAnyMap& input() string report(cbool, double) except +translate_exception cbool hasPhaseTransition() cbool isPure() @@ -376,6 +381,7 @@ cdef extern from "cantera/kinetics/Reaction.h" namespace "Cantera": string type() void validate() except +translate_exception CxxAnyMap parameters(cbool) except +translate_exception + CxxAnyMap input int reaction_type Composition reactants Composition products @@ -563,6 +569,7 @@ cdef extern from "cantera/transport/TransportData.h" namespace "Cantera": cdef cppclass CxxTransportData "Cantera::TransportData": CxxTransportData() CxxAnyMap parameters(cbool) except +translate_exception + CxxAnyMap input cdef cppclass CxxGasTransportData "Cantera::GasTransportData" (CxxTransportData): CxxGasTransportData() diff --git a/interfaces/cython/cantera/base.pyx b/interfaces/cython/cantera/base.pyx index f3a60801d6..bc58fd265b 100644 --- a/interfaces/cython/cantera/base.pyx +++ b/interfaces/cython/cantera/base.pyx @@ -232,6 +232,22 @@ cdef class _SolutionBase: def __get__(self): return anymap_to_dict(self.base.parameters(True)) + def update_user_data(self, data): + """ + Add the contents of the provided `dict` as additional fields when generating + YAML phase definition files with `write_yaml` or in the data returned by + `input_data`. Existing keys with matching names are overwritten. + """ + self.thermo.input().update(dict_to_anymap(data), False) + + def clear_user_data(self): + """ + Clear all saved input data, so that the data given by `input_data` or + `write_yaml` will only include values generated by Cantera based on the + current object state. + """ + self.thermo.input().clear() + def write_yaml(self, filename, phases=None, units=None, precision=None, skip_user_defined=None): """ @@ -251,7 +267,8 @@ cdef class _SolutionBase: 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. + be stripped from the output. These additional contents can also be + controlled using the `update_user_data` and `clear_user_data` functions. """ Y = YamlWriter() Y.add_solution(self) diff --git a/interfaces/cython/cantera/reaction.pyx b/interfaces/cython/cantera/reaction.pyx index ffe7cfa0f3..7c31a16319 100644 --- a/interfaces/cython/cantera/reaction.pyx +++ b/interfaces/cython/cantera/reaction.pyx @@ -329,6 +329,22 @@ cdef class Reaction: def __get__(self): return anymap_to_dict(self.reaction.parameters(True)) + def update_user_data(self, data): + """ + Add the contents of the provided `dict` as additional fields when generating + YAML phase definition files with `Solution.write_yaml` or in the data returned + by `input_data`. Existing keys with matching names are overwritten. + """ + self.reaction.input.update(dict_to_anymap(data), False) + + def clear_user_data(self): + """ + Clear all saved input data, so that the data given by `input_data` or + `Solution.write_yaml` will only include values generated by Cantera based on + the current object state. + """ + self.reaction.input.clear() + def __repr__(self): return '<{}: {}>'.format(self.__class__.__name__, self.equation) diff --git a/interfaces/cython/cantera/speciesthermo.pyx b/interfaces/cython/cantera/speciesthermo.pyx index b62a4dc99e..36f093a9a9 100644 --- a/interfaces/cython/cantera/speciesthermo.pyx +++ b/interfaces/cython/cantera/speciesthermo.pyx @@ -90,6 +90,22 @@ cdef class SpeciesThermo: def __get__(self): return anymap_to_dict(self.spthermo.parameters(True)) + def update_user_data(self, data): + """ + Add the contents of the provided `dict` as additional fields when generating + YAML phase definition files with `Solution.write_yaml` or in the data returned + by `input_data`. Existing keys with matching names are overwritten. + """ + self.spthermo.input().update(dict_to_anymap(data), False) + + def clear_user_data(self): + """ + Clear all saved input data, so that the data given by `input_data` or + `Solution.write_yaml` will only include values generated by Cantera based on + the current object state. + """ + self.spthermo.input().clear() + def cp(self, T): """ Molar heat capacity at constant pressure [J/kmol/K] at temperature *T*. diff --git a/interfaces/cython/cantera/test/test_composite.py b/interfaces/cython/cantera/test/test_composite.py index 51fdb1c5cb..4060f4d2f5 100644 --- a/interfaces/cython/cantera/test/test_composite.py +++ b/interfaces/cython/cantera/test/test_composite.py @@ -434,6 +434,19 @@ def test_input_data_simple(self): self.assertEqual(data['kinetics'], 'gas') self.assertEqual(data['transport'], 'mixture-averaged') + def test_input_data_user_modifications(self): + gas = ct.Solution("h2o2.yaml") + data1 = gas.input_data + gas.update_user_data({"foo": True}) # should get overwritten + extra = {"foo": [1.2, 3.4], "bar": [[1, 2], [3, 4]]} + gas.update_user_data(extra) + data2 = gas.input_data + self.assertEqual(extra["foo"], data2["foo"]) + self.assertEqual(extra["bar"], data2["bar"]) + gas.clear_user_data() + data3 = gas.input_data + self.assertEqual(data1, data3) + def test_input_data_state(self): gas = ct.Solution('h2o2.yaml', transport_model=None) data = gas.input_data @@ -569,6 +582,27 @@ def test_yaml_inconsistent_species(self): with self.assertRaisesRegex(ct.CanteraError, "different definitions"): gas.write_yaml('h2o2-error.yaml', phases=gas2) + def test_yaml_user_data(self): + gas = ct.Solution("h2o2.yaml") + extra = {"spam": {"A": 1, "B": 2}, "eggs": [1, 2.3, 4.5]} + gas.update_user_data(extra) + S = gas.species(2) + S.update_user_data({"foo": "bar"}) + S.transport.update_user_data({"baz": 1234.5}) + S.thermo.update_user_data({"something": (False, True)}) + gas.reaction(5).update_user_data({"baked-beans": True}) + + gas.write_yaml("h2o2-generated-user-data.yaml") + gas2 = ct.Solution("h2o2-generated-user-data.yaml") + data2 = gas2.species(2).input_data + + self.assertEqual(gas2.input_data["spam"], extra["spam"]) + self.assertEqual(gas2.input_data["eggs"], extra["eggs"]) + self.assertEqual(data2["foo"], "bar") + self.assertEqual(data2["transport"]["baz"], 1234.5) + self.assertEqual(data2["thermo"]["something"], [False, True]) + self.assertTrue(gas2.reaction(5).input_data["baked-beans"]) + class TestSpeciesSerialization(utilities.CanteraTest): def test_species_simple(self): diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index 53c4e171cc..26c9a0a2bb 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -267,6 +267,22 @@ cdef class Species: cdef CxxThermoPhase* phase = self._phase.thermo if self._phase else NULL return anymap_to_dict(self.species.parameters(phase)) + def update_user_data(self, data): + """ + Add the contents of the provided `dict` as additional fields when generating + YAML phase definition files with `Solution.write_yaml` or in the data returned + by `input_data`. Existing keys with matching names are overwritten. + """ + self.species.input.update(dict_to_anymap(data), False) + + def clear_user_data(self): + """ + Clear all saved input data, so that the data given by `input_data` or + `Solution.write_yaml` will only include values generated by Cantera based on + the current object state. + """ + self.species.input.clear() + def __repr__(self): return ''.format(self.name) diff --git a/interfaces/cython/cantera/transport.pyx b/interfaces/cython/cantera/transport.pyx index e6dedec1c8..33332ea839 100644 --- a/interfaces/cython/cantera/transport.pyx +++ b/interfaces/cython/cantera/transport.pyx @@ -60,6 +60,22 @@ cdef class GasTransportData: def __get__(self): return anymap_to_dict(self.data.parameters(True)) + def update_user_data(self, data): + """ + Add the contents of the provided `dict` as additional fields when generating + YAML phase definition files with `Solution.write_yaml` or in the data returned + by `input_data`. Existing keys with matching names are overwritten. + """ + self.data.input.update(dict_to_anymap(data), False) + + def clear_user_data(self): + """ + Clear all saved input data, so that the data given by `input_data` or + `Solution.write_yaml` will only include values generated by Cantera based on + the current object state. + """ + self.data.input.clear() + property geometry: """ Get/Set the string specifying the molecular geometry. One of `atom`,