From 4428a62f3c58780d3298f977a1f13208eef7024a Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Tue, 22 Mar 2016 14:57:58 -0400 Subject: [PATCH] [Thermo] Add ability to modify species data for existing Phase objects --- include/cantera/thermo/GeneralSpeciesThermo.h | 6 +++ include/cantera/thermo/Phase.h | 9 +++++ include/cantera/thermo/SpeciesThermo.h | 8 ++++ include/cantera/thermo/ThermoPhase.h | 2 + interfaces/cython/cantera/_cantera.pxd | 1 + .../cython/cantera/test/test_kinetics.py | 26 +++++++++++++ interfaces/cython/cantera/test/test_thermo.py | 38 +++++++++++++++++++ interfaces/cython/cantera/thermo.pyx | 5 +++ src/thermo/GeneralSpeciesThermo.cpp | 32 ++++++++++++++++ src/thermo/Phase.cpp | 17 +++++++++ src/thermo/ThermoPhase.cpp | 12 ++++++ 11 files changed, 156 insertions(+) diff --git a/include/cantera/thermo/GeneralSpeciesThermo.h b/include/cantera/thermo/GeneralSpeciesThermo.h index 4928d48f08..6f49e74ab7 100644 --- a/include/cantera/thermo/GeneralSpeciesThermo.h +++ b/include/cantera/thermo/GeneralSpeciesThermo.h @@ -37,6 +37,9 @@ class GeneralSpeciesThermo : public SpeciesThermo virtual void install_STIT(size_t index, shared_ptr stit_ptr); + virtual void modifySpecies(size_t index, + shared_ptr stit_ptr); + //! Install a PDSS object to handle the reference state thermodynamics //! calculation /*! @@ -101,6 +104,9 @@ class GeneralSpeciesThermo : public SpeciesThermo //! Temperature polynomials for each thermo parameterization mutable tpoly_map m_tpoly; + //! Map from species index to location within #m_sp, such that + //! `m_sp[m_speciesLoc[k].first][m_speciesLoc[k].second]` is the + //! SpeciesThermoInterpType object for species `k`. std::map > m_speciesLoc; //! Maximum value of the lowest temperature diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index 5b0d1bdee3..96eb1827db 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -689,6 +689,15 @@ class Phase //! @see ignoreUndefinedElements addUndefinedElements throwUndefinedElements virtual bool addSpecies(shared_ptr spec); + //! Modify the thermodynamic data associated with a species. + /*! + * The species name, elemental composition, and type of thermo + * parameterization must be unchanged. If there are Kinetics objects that + * depend on this phase, Kinetics::invalidateCache() should be called on + * those objects after calling this function. + */ + virtual void modifySpecies(size_t k, shared_ptr spec); + //! Return the Species object for the named species. shared_ptr species(const std::string& name) const; diff --git a/include/cantera/thermo/SpeciesThermo.h b/include/cantera/thermo/SpeciesThermo.h index 8d4ae8ddb3..77dc7ba2b5 100644 --- a/include/cantera/thermo/SpeciesThermo.h +++ b/include/cantera/thermo/SpeciesThermo.h @@ -142,6 +142,14 @@ class SpeciesThermo virtual void install_STIT(size_t index, shared_ptr stit) = 0; + //! Modify the species thermodynamic property parameterization for a species + /*! + * @param index Index of the species being installed + * @param spec Pointer to the SpeciesThermoInterpType object + */ + virtual void modifySpecies(size_t index, + shared_ptr spec) = 0; + //! Compute the reference-state properties for all species. /*! * Given temperature T in K, this method updates the values of the non- diff --git a/include/cantera/thermo/ThermoPhase.h b/include/cantera/thermo/ThermoPhase.h index dc814e333a..ab726e3c33 100644 --- a/include/cantera/thermo/ThermoPhase.h +++ b/include/cantera/thermo/ThermoPhase.h @@ -1384,6 +1384,8 @@ class ThermoPhase : public Phase using Phase::addSpecies; virtual bool addSpecies(shared_ptr spec); + virtual void modifySpecies(size_t k, shared_ptr spec); + //! Store a reference pointer to the XML tree containing the species data //! for this phase. /*! diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index 699b4645be..ff355740e1 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -123,6 +123,7 @@ cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera": # initialization void addUndefinedElements() except + cbool addSpecies(shared_ptr[CxxSpecies]) except + + void modifySpecies(size_t, shared_ptr[CxxSpecies]) except + void initThermo() except + void invalidateCache() except + diff --git a/interfaces/cython/cantera/test/test_kinetics.py b/interfaces/cython/cantera/test/test_kinetics.py index ca9437f59c..eddf3fe342 100644 --- a/interfaces/cython/cantera/test/test_kinetics.py +++ b/interfaces/cython/cantera/test/test_kinetics.py @@ -344,6 +344,32 @@ def test_pdep_temperature(self): def test_pdep_pressure(self): self.check_rates_pressure('pdep-test.xml') + def test_modify_thermo(self): + # Make sure that thermo modifications propagate through to Kinetics + + # Set a gas state that is near enough to equilibrium that changes in the + # reverse rate always show up in the net rate + gas = self.setup_gas('gri30.xml') + gas.TPX = self.T0, self.P0, self.X0 + gas.equilibrate('TP') + gas.TP = gas.T + 20, None + + S = {sp.name: sp for sp in ct.Species.listFromFile('gri30.xml')} + w1 = gas.net_rates_of_progress + + OH = gas.species('OH') + OH.thermo = S['CO2'].thermo + gas.modify_species(gas.species_index('OH'), OH) + w2 = gas.net_rates_of_progress + + for i,R in enumerate(gas.reactions()): + if ('OH' in R.reactants or 'OH' in R.products) and R.reversible: + # Rate should be different if reaction involves OH + self.assertNotAlmostEqual(w2[i] / w1[i], 1.0) + else: + # Rate should be the same if reaction does not involve OH + self.assertAlmostEqual(w2[i] / w1[i], 1.0) + class TestEmptyKinetics(utilities.CanteraTest): def test_empty(self): diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index 16814dc630..c265fa0956 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -871,6 +871,44 @@ def test_listFromXml(self): self.assertEqual({sp.name for sp in S}, set(self.gas.species_names)) + def test_modify_thermo(self): + S = {sp.name: sp for sp in ct.Species.listFromFile('h2o2.xml')} + self.gas.TPX = 400, 2*ct.one_atm, 'H2:1.0' + g0 = self.gas.gibbs_mole + + self.gas.TPX = None, None, 'O2:1.0' + self.assertNotAlmostEqual(g0, self.gas.gibbs_mole) + # Replace O2 thermo with the data from H2 + S['O2'].thermo = S['H2'].thermo + self.gas.modify_species(self.gas.species_index('O2'), S['O2']) + self.assertAlmostEqual(g0, self.gas.gibbs_mole) + + def test_modify_thermo_invalid(self): + S = {sp.name: sp for sp in ct.Species.listFromFile('h2o2.xml')} + + orig = S['H2'] + thermo = orig.thermo + copy = ct.Species('foobar', orig.composition) + copy.thermo = thermo + with self.assertRaises(Exception): + self.gas.modify_species(self.gas.species_index('H2'), copy) + + copy = ct.Species('H2', {'H': 3}) + copy.thermo = thermo + with self.assertRaises(Exception): + self.gas.modify_species(self.gas.species_index('H2'), copy) + + copy = ct.Species('H2', orig.composition) + copy.thermo = ct.ConstantCp(thermo.min_temp, thermo.max_temp, + thermo.reference_pressure, [300, 123, 456, 789]) + with self.assertRaises(Exception): + self.gas.modify_species(self.gas.species_index('H2'), copy) + + copy = ct.Species('H2', orig.composition) + copy.thermo = ct.NasaPoly2(thermo.min_temp+200, thermo.max_temp, + thermo.reference_pressure, thermo.coeffs) + with self.assertRaises(Exception): + self.gas.modify_species(self.gas.species_index('H2'), copy) class TestSpeciesThermo(utilities.CanteraTest): diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index 3f3e6c265a..eeb1933040 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -432,6 +432,11 @@ cdef class ThermoPhase(_SolutionBase): " Got {!r}.".format(k)) return s + def modify_species(self, k, Species species): + self.thermo.modifySpecies(k, species._species) + if self.kinetics: + self.kinetics.invalidateCache() + def n_atoms(self, species, element): """ Number of atoms of element *element* in species *species*. The element diff --git a/src/thermo/GeneralSpeciesThermo.cpp b/src/thermo/GeneralSpeciesThermo.cpp index 3aec7ee963..e84161b78c 100644 --- a/src/thermo/GeneralSpeciesThermo.cpp +++ b/src/thermo/GeneralSpeciesThermo.cpp @@ -96,6 +96,38 @@ void GeneralSpeciesThermo::install_STIT(size_t index, markInstalled(index); } +void GeneralSpeciesThermo::modifySpecies(size_t index, + shared_ptr spthermo) +{ + if (!spthermo) { + throw CanteraError("GeneralSpeciesThermo::modifySpecies", + "null pointer"); + } + if (m_speciesLoc.find(index) == m_speciesLoc.end()) { + throw CanteraError("GeneralSpeciesThermo::modifySpecies", + "Species with this index not previously added: {}", + index); + } + int type = spthermo->reportType(); + if (m_speciesLoc[index].first != type) { + throw CanteraError("GeneralSpeciesThermo::modifySpecies", + "Type of parameterization changed: {} != {}", type, + m_speciesLoc[index].first); + } + if (spthermo->minTemp() > m_tlow_max) { + throw CanteraError("GeneralSpeciesThermo::modifySpecies", + "Cannot increase minimum temperature for phase from {} to {}", + m_tlow_max, spthermo->minTemp()); + } + if (spthermo->maxTemp() < m_thigh_min) { + throw CanteraError("GeneralSpeciesThermo::modifySpecies", + "Cannot increase minimum temperature for phase from {} to {}", + m_thigh_min, spthermo->maxTemp()); + } + + m_sp[type][m_speciesLoc[index].second] = {index, spthermo}; +} + void GeneralSpeciesThermo::installPDSShandler(size_t k, PDSS* PDSS_ptr, VPSSMgr* vpssmgr_ptr) { diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index 2097074e06..ded0873204 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -837,6 +837,23 @@ bool Phase::addSpecies(shared_ptr spec) { return true; } +void Phase::modifySpecies(size_t k, shared_ptr spec) +{ + if (speciesName(k) != spec->name) { + throw CanteraError("Phase::modifySpecies", + "New species name '{}' does not match existing name '{}'", + spec->name, speciesName(k)); + } + const shared_ptr& old = m_species[spec->name]; + if (spec->composition != old->composition) { + throw CanteraError("Phase::modifySpecies", + "New composition for '{}' does not match existing composition", + spec->name); + } + m_species[spec->name] = spec; + invalidateCache(); +} + shared_ptr Phase::species(const std::string& name) const { return getValue(m_species, name); diff --git a/src/thermo/ThermoPhase.cpp b/src/thermo/ThermoPhase.cpp index acc1637bee..6ce1f801a2 100644 --- a/src/thermo/ThermoPhase.cpp +++ b/src/thermo/ThermoPhase.cpp @@ -715,6 +715,18 @@ bool ThermoPhase::addSpecies(shared_ptr spec) return added; } +void ThermoPhase::modifySpecies(size_t k, shared_ptr spec) +{ + Phase::modifySpecies(k, spec); + if (speciesName(k) != spec->name) { + throw CanteraError("ThermoPhase::modifySpecies", + "New species '{}' does not match existing species '{}' at index {}", + spec->name, speciesName(k), k); + } + spec->thermo->validate(spec->name); + m_spthermo->modifySpecies(k, spec->thermo); +} + void ThermoPhase::saveSpeciesData(const size_t k, const XML_Node* const data) { if (m_speciesData.size() < (k + 1)) {