From a2c33b95b5c5d818e2947738ff580391c5f3f0fb Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Sun, 11 Aug 2019 20:38:13 -0500 Subject: [PATCH 1/6] [Thermo] fix compiler warning for RedlichKwongMFTP --- src/thermo/RedlichKwongMFTP.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index e950ed550b3..e389fe6f811 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -1,7 +1,7 @@ //! @file RedlichKwongMFTP.cpp // This file is part of Cantera. See License.txt in the top-level directory or -// at http://www.cantera.org/license.txt for license and copyright information. +// at https://cantera.org/license.txt for license and copyright information. #include "cantera/thermo/RedlichKwongMFTP.h" #include "cantera/thermo/ThermoFactory.h" @@ -710,6 +710,8 @@ vector RedlichKwongMFTP::getCoeff(const std::string& iName) // based on crit properties T_c and P_c: for (size_t isp = 0; isp < nDatabase; isp++) { XML_Node& acNodeDoc = doc->child(isp); + // not enforcing case sensitive species names as this is external + // to CTI or YAML input files std::string iNameLower = toLowerCopy(iName); std::string dbName = toLowerCopy(acNodeDoc.attrib("name")); @@ -718,7 +720,8 @@ vector RedlichKwongMFTP::getCoeff(const std::string& iName) if (iNameLower == dbName) { // Read from database and calculate a and b coefficients double vParams; - double T_crit, P_crit; + double T_crit=0.; + double P_crit=0.; if (acNodeDoc.hasChild("Tc")) { vParams = 0.0; @@ -734,6 +737,9 @@ vector RedlichKwongMFTP::getCoeff(const std::string& iName) "Critical Temperature must be positive "); } T_crit = vParams; + } else { + throw CanteraError("RedlichKwongMFTP::GetCoeff", + "Critical Temperature not in database "); } if (acNodeDoc.hasChild("Pc")) { vParams = 0.0; @@ -749,6 +755,9 @@ vector RedlichKwongMFTP::getCoeff(const std::string& iName) "Critical Pressure must be positive "); } P_crit = vParams; + } else { + throw CanteraError("RedlichKwongMFTP::GetCoeff", + "Critical Pressure not in database "); } //Assuming no temperature dependence From 84cbab315153d47a549f64958203752a6a820495 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Sun, 11 Aug 2019 18:41:54 -0500 Subject: [PATCH 2/6] [Thermo] add flag that makes species names case sensitive * store species information with case sensitive names * retain lookup for non-case sensitive species names, e.g. Phase::speciesIndex * implement flag that enforces case sensitive species names as a member variable of Phase * add exception handling for species that are not uniquely defined unless case sensitive (e.g. Cs and CS in nasa.cti if cs is specified and case sensitivity is not enforced) * deprecate Phase::species(std::string&) --- include/cantera/thermo/Phase.h | 25 ++++++- interfaces/cython/cantera/_cantera.pxd | 3 +- interfaces/cython/cantera/thermo.pyx | 12 +++- src/thermo/Phase.cpp | 91 ++++++++++++++++++++------ src/transport/GasTransport.cpp | 2 +- 5 files changed, 107 insertions(+), 26 deletions(-) diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index d6f3a678121..9f9c1d7eaa9 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -4,7 +4,7 @@ */ // This file is part of Cantera. See License.txt in the top-level directory or -// at http://www.cantera.org/license.txt for license and copyright information. +// at https://cantera.org/license.txt for license and copyright information. #ifndef CT_PHASE_H #define CT_PHASE_H @@ -707,6 +707,10 @@ class Phase //! Return the Species object for the named species. Changes to this object //! do not affect the ThermoPhase object until the #modifySpecies function //! is called. + /*! + * @deprecated To be removed after Cantera 2.5. Replaceable with + * Phase::species(size_t) via Phase::speciesIndex(std::string&). + */ shared_ptr species(const std::string& name) const; //! Return the Species object for species whose index is *k*. Changes to @@ -747,6 +751,17 @@ class Phase //! change in state is detected virtual void invalidateCache(); + //! Returns `true` if case sensitive species names are enforced + bool caseSensitiveSpecies() const { + return m_caseSensitiveSpecies; + } + + //! Set flag that determines whether case sensitive species are enforced + //! in look-up operations, e.g. speciesIndex + void setCaseSensitiveSpecies(bool cflag = true) { + m_caseSensitiveSpecies = cflag; + } + protected: //! Cached for saved calculations within each ThermoPhase. /*! @@ -794,6 +809,14 @@ class Phase //! Flag determining behavior when adding species with an undefined element UndefElement::behavior m_undefinedElementBehavior; + //! Find lowercase species name in m_speciesIndices when case sensitive + //! species names are not enforced. Raise exception if lowercase name + //! is not unique. + size_t findSpeciesLower(const std::string& nameStr) const; + + //! Flag determining whether case sensitive species names are enforced + bool m_caseSensitiveSpecies; + private: XML_Node* m_xml; //!< XML node containing the XML info for this phase diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index 26eff9b62e4..af5f9c14735 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -165,12 +165,13 @@ cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera": # species properties size_t nSpecies() - shared_ptr[CxxSpecies] species(string) except +translate_exception shared_ptr[CxxSpecies] species(size_t) except +translate_exception size_t speciesIndex(string) except +translate_exception string speciesName(size_t) except +translate_exception double nAtoms(size_t, size_t) except +translate_exception void getAtoms(size_t, double*) except +translate_exception + cbool caseSensitiveSpecies() + void setCaseSensitiveSpecies(cbool) double molecularWeight(size_t) except +translate_exception double meanMolecularWeight() diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index b8db5d6857b..f54d6b332b5 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -1,5 +1,5 @@ # This file is part of Cantera. See License.txt in the top-level directory or -# at http://www.cantera.org/license.txt for license and copyright information. +# at https://cantera.org/license.txt for license and copyright information. import warnings import weakref @@ -470,6 +470,13 @@ cdef class ThermoPhase(_SolutionBase): return index + property case_sensitive_species_names: + """Enforce case-sensitivity for look up of species names""" + def __get__(self): + return self.thermo.caseSensitiveSpecies() + def __set__(self, val): + self.thermo.setCaseSensitiveSpecies(bool(val)) + def species(self, k=None): """ Return the `Species` object for species *k*, where *k* is either the @@ -483,7 +490,8 @@ cdef class ThermoPhase(_SolutionBase): s = Species(init=False) if isinstance(k, (str, bytes)): - s._assign(self.thermo.species(stringify(k))) + kk = self.thermo.speciesIndex(stringify(k)) + s._assign(self.thermo.species(kk)) elif isinstance(k, (int, float)): s._assign(self.thermo.species(k)) else: diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index 880bdfae9b1..ffe01fe10cd 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -4,7 +4,7 @@ */ // This file is part of Cantera. See License.txt in the top-level directory or -// at http://www.cantera.org/license.txt for license and copyright information. +// at https://cantera.org/license.txt for license and copyright information. #include "cantera/thermo/Phase.h" #include "cantera/base/utilities.h" @@ -21,6 +21,7 @@ Phase::Phase() : m_kk(0), m_ndim(3), m_undefinedElementBehavior(UndefElement::add), + m_caseSensitiveSpecies(false), m_xml(new XML_Node("phase")), m_id(""), m_temp(0.001), @@ -172,20 +173,47 @@ void Phase::getAtoms(size_t k, double* atomArray) const } } +size_t Phase::findSpeciesLower(const std::string& name) const +{ + size_t loc = npos; + std::string nLower = toLowerCopy(name); + for (const auto& k : m_speciesIndices) { + if (toLowerCopy(k.first) == nLower) { + if (loc == npos) { + loc = k.second; + } else { + throw CanteraError("Phase::findSpeciesLower", + "Lowercase species name '{}' is not unique", nLower); + } + } + } + return loc; +} + size_t Phase::speciesIndex(const std::string& nameStr) const { - size_t loc = getValue(m_speciesIndices, toLowerCopy(nameStr), npos); + size_t loc = npos; + try { + return m_speciesIndices.at(nameStr); + } catch (std::out_of_range&) { + if (!m_caseSensitiveSpecies) { + loc = findSpeciesLower(nameStr); + } + } if (loc == npos && nameStr.find(':') != npos) { std::string pn; - std::string sn = toLowerCopy(parseSpeciesName(nameStr, pn)); + std::string sn = parseSpeciesName(nameStr, pn); if (pn == "" || pn == m_name || pn == m_id) { - return getValue(m_speciesIndices, sn, npos); - } else { - return npos; + try { + return m_speciesIndices.at(sn); + } catch (std::out_of_range&) { + if (!m_caseSensitiveSpecies) { + return findSpeciesLower(sn); + } + } } - } else { - return loc; } + return loc; } string Phase::speciesName(size_t k) const @@ -294,10 +322,18 @@ void Phase::setMoleFractionsByName(const compositionMap& xMap) vector_fp mf(m_kk, 0.0); for (const auto& sp : xMap) { try { - mf[m_speciesIndices.at(toLowerCopy(sp.first))] = sp.second; + mf[m_speciesIndices.at(sp.first)] = sp.second; } catch (std::out_of_range&) { - throw CanteraError("Phase::setMoleFractionsByName", - "Unknown species '{}'", sp.first); + size_t loc = npos; + if (!m_caseSensitiveSpecies) { + loc = findSpeciesLower(sp.first); + } + if (loc == npos) { + throw CanteraError("Phase::setMoleFractionsByName", + "Unknown species '{}'", sp.first); + } else { + mf[loc] = sp.second; + } } } setMoleFractions(&mf[0]); @@ -338,10 +374,18 @@ void Phase::setMassFractionsByName(const compositionMap& yMap) vector_fp mf(m_kk, 0.0); for (const auto& sp : yMap) { try { - mf[m_speciesIndices.at(toLowerCopy(sp.first))] = sp.second; + mf[m_speciesIndices.at(sp.first)] = sp.second; } catch (std::out_of_range&) { - throw CanteraError("Phase::setMassFractionsByName", - "Unknown species '{}'", sp.first); + size_t loc = npos; + if (!m_caseSensitiveSpecies) { + loc = findSpeciesLower(sp.first); + } + if (loc == npos) { + throw CanteraError("Phase::setMassFractionsByName", + "Unknown species '{}'", sp.first); + } else { + mf[loc] = sp.second; + } } } setMassFractions(&mf[0]); @@ -699,7 +743,8 @@ size_t Phase::addElement(const std::string& symbol, doublereal weight, } bool Phase::addSpecies(shared_ptr spec) { - if (m_species.find(toLowerCopy(spec->name)) != m_species.end()) { + // species names are case sensitive + if (m_species.find(spec->name) != m_species.end()) { throw CanteraError("Phase::addSpecies", "Phase '{}' already contains a species named '{}'.", m_name, spec->name); @@ -729,8 +774,8 @@ bool Phase::addSpecies(shared_ptr spec) { } m_speciesNames.push_back(spec->name); - m_species[toLowerCopy(spec->name)] = spec; - m_speciesIndices[toLowerCopy(spec->name)] = m_kk; + m_species[spec->name] = spec; + m_speciesIndices[spec->name] = m_kk; m_speciesCharge.push_back(spec->charge); size_t ne = nElements(); @@ -794,24 +839,28 @@ void Phase::modifySpecies(size_t k, shared_ptr spec) "New species name '{}' does not match existing name '{}'", spec->name, speciesName(k)); } - const shared_ptr& old = m_species[toLowerCopy(spec->name)]; + 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[toLowerCopy(spec->name)] = spec; + m_species[spec->name] = spec; invalidateCache(); } +//! @deprecated To be removed after Cantera 2.5. shared_ptr Phase::species(const std::string& name) const { - return m_species.at(toLowerCopy(name)); + warn_deprecated("Phase::species(std::string&)", + "To be removed after Cantera 2.5. " + "Use Phase::species(size_t) instead."); + return m_species.at(name); } shared_ptr Phase::species(size_t k) const { - return species(m_speciesNames[k]); + return m_species.at(m_speciesNames[k]); } void Phase::ignoreUndefinedElements() { diff --git a/src/transport/GasTransport.cpp b/src/transport/GasTransport.cpp index f8447a884a6..6cc8222bca0 100644 --- a/src/transport/GasTransport.cpp +++ b/src/transport/GasTransport.cpp @@ -395,7 +395,7 @@ void GasTransport::setupCollisionIntegral() void GasTransport::getTransportData() { for (size_t k = 0; k < m_thermo->nSpecies(); k++) { - shared_ptr s = m_thermo->species(m_thermo->speciesName(k)); + shared_ptr s = m_thermo->species(k); const GasTransportData* sptran = dynamic_cast(s->transport.get()); if (!sptran) { From cacffb742d8e8468975b931db929f64bb8596470 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 12 Aug 2019 08:12:25 -0500 Subject: [PATCH 3/6] [Thermo] update unit tests to handle case sensitive species names --- interfaces/cython/cantera/test/test_thermo.py | 35 +++++++++++++++++-- test/thermo/ThermoPhase_Test.cpp | 2 +- test/thermo/phaseConstructors.cpp | 2 +- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index b91a988fe53..eb46af9309d 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -123,7 +123,7 @@ def test_setComposition_singleton(self): self.assertEqual(list(X[0,:,0]), list(Y)) def test_setCompositionString(self): - self.phase.X = 'h2:1.0, o2:1.0' + self.phase.X = 'H2:1.0, O2:1.0' X = self.phase.X self.assertNear(X[0], 0.5) self.assertNear(X[3], 0.5) @@ -161,7 +161,7 @@ def test_setCompositionDict(self): self.assertNear(Y[3], 0.75) def test_getCompositionDict(self): - self.phase.X = 'oh:1e-9, O2:0.4, AR:0.6' + self.phase.X = 'OH:1e-9, O2:0.4, AR:0.6' self.assertEqual(len(self.phase.mole_fraction_dict(1e-7)), 2) self.assertEqual(len(self.phase.mole_fraction_dict()), 3) @@ -202,7 +202,7 @@ def test_setCompositionDict_bad2(self): self.phase.Y = {'H2':1.0, 'O2':'xx'} def test_setCompositionSlice(self): - self.phase['h2', 'o2'].X = 0.1, 0.9 + self.phase['H2', 'O2'].X = 0.1, 0.9 X = self.phase.X self.assertNear(X[0], 0.1) self.assertNear(X[3], 0.9) @@ -1253,6 +1253,35 @@ def test_stringify_bad(self): with self.assertRaises(AttributeError): ct.Solution(3) + def test_case_sensitive_names(self): + gas = ct.Solution('h2o2.xml') + self.assertFalse(gas.case_sensitive_species_names) + self.assertTrue(gas.species_index('h2') == 0) + + gas.case_sensitive_species_names = True + self.assertTrue(gas.case_sensitive_species_names) + with self.assertRaises(ValueError): + gas.species_index('h2') + with self.assertRaisesRegex(ct.CanteraError, 'Unknown species'): + gas.X = 'h2:1.0, o2:1.0' + with self.assertRaisesRegex(ct.CanteraError, 'Unknown species'): + gas.Y = 'h2:1.0, o2:1.0' + + gas_cti = """ideal_gas( + name="gas", + elements=" S C Cs ", + species=" nasa: all ", + options=["skip_undeclared_elements"], + initial_state=state(temperature=300, pressure=(1, "bar")) + )""" + ct.suppress_thermo_warnings(True) + gas = ct.Solution(source=gas_cti) + with self.assertRaisesRegex(ct.CanteraError, 'is not unique'): + gas.species_index('cs') + gas.case_sensitive_species_names = True + with self.assertRaises(ValueError): + gas.species_index('cs') + class TestElement(utilities.CanteraTest): @classmethod diff --git a/test/thermo/ThermoPhase_Test.cpp b/test/thermo/ThermoPhase_Test.cpp index 21a64c86f9c..ad60e781e8a 100644 --- a/test/thermo/ThermoPhase_Test.cpp +++ b/test/thermo/ThermoPhase_Test.cpp @@ -40,7 +40,7 @@ TEST_F(TestThermoMethods, getMoleFractionsByName) EXPECT_DOUBLE_EQ(X["H2"], 0.3); EXPECT_DOUBLE_EQ(X["AR"], 0.5); - thermo->setMoleFractionsByName("OH:1e-9, O2:0.2, h2:0.3, AR:0.5"); + thermo->setMoleFractionsByName("OH:1e-9, O2:0.2, H2:0.3, AR:0.5"); X = thermo->getMoleFractionsByName(); EXPECT_EQ(X.size(), (size_t) 4); diff --git a/test/thermo/phaseConstructors.cpp b/test/thermo/phaseConstructors.cpp index f9808be6ea4..2deea16eb9c 100644 --- a/test/thermo/phaseConstructors.cpp +++ b/test/thermo/phaseConstructors.cpp @@ -299,7 +299,7 @@ TEST_F(ConstructFromScratch, addUndefinedElements) ASSERT_EQ((size_t) 4, p.nSpecies()); ASSERT_EQ((size_t) 3, p.nElements()); ASSERT_EQ((size_t) 1, p.nAtoms(p.speciesIndex("CO2"), p.elementIndex("C"))); - ASSERT_EQ((size_t) 2, p.nAtoms(p.speciesIndex("co2"), p.elementIndex("O"))); + ASSERT_EQ((size_t) 2, p.nAtoms(p.speciesIndex("CO2"), p.elementIndex("O"))); p.setMassFractionsByName("H2:0.5, CO2:0.5"); ASSERT_DOUBLE_EQ(0.5, p.massFraction("CO2")); } From 919cadbe995ab90861d37cebed3c1d5c935183f2 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Thu, 19 Sep 2019 12:24:26 -0500 Subject: [PATCH 4/6] Add case-sensitive/lowercase logic to Phase::species Further: * revert unit tests to previous species definitions (some case mis-matches) * remove non-essential comments * opt to maintain case-sensitive species maps with lowercase as fallback --- include/cantera/thermo/Phase.h | 14 ++-- interfaces/cython/cantera/test/test_thermo.py | 8 +- src/thermo/Phase.cpp | 81 +++++++++++-------- src/thermo/RedlichKwongMFTP.cpp | 2 - test/thermo/ThermoPhase_Test.cpp | 2 +- test/thermo/phaseConstructors.cpp | 2 +- 6 files changed, 60 insertions(+), 49 deletions(-) diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index 9f9c1d7eaa9..37ab5d5de3c 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -707,10 +707,6 @@ class Phase //! Return the Species object for the named species. Changes to this object //! do not affect the ThermoPhase object until the #modifySpecies function //! is called. - /*! - * @deprecated To be removed after Cantera 2.5. Replaceable with - * Phase::species(size_t) via Phase::speciesIndex(std::string&). - */ shared_ptr species(const std::string& name) const; //! Return the Species object for species whose index is *k*. Changes to @@ -809,15 +805,15 @@ class Phase //! Flag determining behavior when adding species with an undefined element UndefElement::behavior m_undefinedElementBehavior; - //! Find lowercase species name in m_speciesIndices when case sensitive - //! species names are not enforced. Raise exception if lowercase name - //! is not unique. - size_t findSpeciesLower(const std::string& nameStr) const; - //! Flag determining whether case sensitive species names are enforced bool m_caseSensitiveSpecies; private: + //! Find lowercase species name in m_speciesIndices when case sensitive + //! species names are not enforced and a user specifies a non-canonical + //! species name. Raise exception if lowercase name is not unique. + size_t findSpeciesLower(const std::string& nameStr) const; + XML_Node* m_xml; //!< XML node containing the XML info for this phase //! ID of the phase. This is the value of the ID attribute of the XML diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index eb46af9309d..d2fddd4fb18 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -123,7 +123,7 @@ def test_setComposition_singleton(self): self.assertEqual(list(X[0,:,0]), list(Y)) def test_setCompositionString(self): - self.phase.X = 'H2:1.0, O2:1.0' + self.phase.X = 'h2:1.0, o2:1.0' X = self.phase.X self.assertNear(X[0], 0.5) self.assertNear(X[3], 0.5) @@ -161,7 +161,7 @@ def test_setCompositionDict(self): self.assertNear(Y[3], 0.75) def test_getCompositionDict(self): - self.phase.X = 'OH:1e-9, O2:0.4, AR:0.6' + self.phase.X = 'oh:1e-9, O2:0.4, AR:0.6' self.assertEqual(len(self.phase.mole_fraction_dict(1e-7)), 2) self.assertEqual(len(self.phase.mole_fraction_dict()), 3) @@ -202,7 +202,7 @@ def test_setCompositionDict_bad2(self): self.phase.Y = {'H2':1.0, 'O2':'xx'} def test_setCompositionSlice(self): - self.phase['H2', 'O2'].X = 0.1, 0.9 + self.phase['h2', 'o2'].X = 0.1, 0.9 X = self.phase.X self.assertNear(X[0], 0.1) self.assertNear(X[3], 0.9) @@ -1257,7 +1257,7 @@ def test_case_sensitive_names(self): gas = ct.Solution('h2o2.xml') self.assertFalse(gas.case_sensitive_species_names) self.assertTrue(gas.species_index('h2') == 0) - + gas.case_sensitive_species_names = True self.assertTrue(gas.case_sensitive_species_names) with self.assertRaises(ValueError): diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index ffe01fe10cd..62fe85cd9c6 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -183,7 +183,9 @@ size_t Phase::findSpeciesLower(const std::string& name) const loc = k.second; } else { throw CanteraError("Phase::findSpeciesLower", - "Lowercase species name '{}' is not unique", nLower); + "Lowercase species name '{}' is not unique. " + "Set Phase::caseSensitiveSpecies to true to " + "enforce case sensitive species names", nLower); } } } @@ -193,23 +195,23 @@ size_t Phase::findSpeciesLower(const std::string& name) const size_t Phase::speciesIndex(const std::string& nameStr) const { size_t loc = npos; - try { - return m_speciesIndices.at(nameStr); - } catch (std::out_of_range&) { - if (!m_caseSensitiveSpecies) { - loc = findSpeciesLower(nameStr); - } + std::map::const_iterator it; + + it = m_speciesIndices.find(nameStr); + if (it != m_speciesIndices.end()) { + return it->second; + } else if (!m_caseSensitiveSpecies) { + loc = findSpeciesLower(nameStr); } if (loc == npos && nameStr.find(':') != npos) { std::string pn; std::string sn = parseSpeciesName(nameStr, pn); if (pn == "" || pn == m_name || pn == m_id) { - try { - return m_speciesIndices.at(sn); - } catch (std::out_of_range&) { - if (!m_caseSensitiveSpecies) { - return findSpeciesLower(sn); - } + it = m_speciesIndices.find(nameStr); + if (it != m_speciesIndices.end()) { + return it->second; + } else if (!m_caseSensitiveSpecies) { + return findSpeciesLower(sn); } } } @@ -320,20 +322,14 @@ void Phase::setMoleFractions_NoNorm(const doublereal* const x) void Phase::setMoleFractionsByName(const compositionMap& xMap) { vector_fp mf(m_kk, 0.0); + for (const auto& sp : xMap) { - try { - mf[m_speciesIndices.at(sp.first)] = sp.second; - } catch (std::out_of_range&) { - size_t loc = npos; - if (!m_caseSensitiveSpecies) { - loc = findSpeciesLower(sp.first); - } - if (loc == npos) { - throw CanteraError("Phase::setMoleFractionsByName", - "Unknown species '{}'", sp.first); - } else { - mf[loc] = sp.second; - } + size_t loc = speciesIndex(sp.first); + if (loc != npos) { + mf[loc] = sp.second; + } else { + throw CanteraError("Phase::setMoleFractionsByName", + "Unknown species '{}'", sp.first); } } setMoleFractions(&mf[0]); @@ -743,7 +739,6 @@ size_t Phase::addElement(const std::string& symbol, doublereal weight, } bool Phase::addSpecies(shared_ptr spec) { - // species names are case sensitive if (m_species.find(spec->name) != m_species.end()) { throw CanteraError("Phase::addSpecies", "Phase '{}' already contains a species named '{}'.", @@ -849,13 +844,35 @@ void Phase::modifySpecies(size_t k, shared_ptr spec) invalidateCache(); } -//! @deprecated To be removed after Cantera 2.5. shared_ptr Phase::species(const std::string& name) const { - warn_deprecated("Phase::species(std::string&)", - "To be removed after Cantera 2.5. " - "Use Phase::species(size_t) instead."); - return m_species.at(name); + std::map >::const_iterator it; + + it = m_species.find(name); + if (it != m_species.end()) { + return it->second; + } else if (!m_caseSensitiveSpecies) { + std::string nLower = toLowerCopy(name); + bool found = false; + shared_ptr sp; + for (const auto& k : m_species) { + if (toLowerCopy(k.first) == nLower) { + if (!found) { + found = true; + sp = k.second; + } else { + throw CanteraError("Phase::species", + "Lowercase species name '{}' is not unique. " + "Set Phase::caseSensitiveSpecies to true to " + "enforce case sensitive species names", nLower); + } + } + } + return sp; + } else { + throw CanteraError("Phase::setMassFractionsByName", + "Unknown species '{}'", name); + } } shared_ptr Phase::species(size_t k) const diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index e389fe6f811..d79fc33343c 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -710,8 +710,6 @@ vector RedlichKwongMFTP::getCoeff(const std::string& iName) // based on crit properties T_c and P_c: for (size_t isp = 0; isp < nDatabase; isp++) { XML_Node& acNodeDoc = doc->child(isp); - // not enforcing case sensitive species names as this is external - // to CTI or YAML input files std::string iNameLower = toLowerCopy(iName); std::string dbName = toLowerCopy(acNodeDoc.attrib("name")); diff --git a/test/thermo/ThermoPhase_Test.cpp b/test/thermo/ThermoPhase_Test.cpp index ad60e781e8a..21a64c86f9c 100644 --- a/test/thermo/ThermoPhase_Test.cpp +++ b/test/thermo/ThermoPhase_Test.cpp @@ -40,7 +40,7 @@ TEST_F(TestThermoMethods, getMoleFractionsByName) EXPECT_DOUBLE_EQ(X["H2"], 0.3); EXPECT_DOUBLE_EQ(X["AR"], 0.5); - thermo->setMoleFractionsByName("OH:1e-9, O2:0.2, H2:0.3, AR:0.5"); + thermo->setMoleFractionsByName("OH:1e-9, O2:0.2, h2:0.3, AR:0.5"); X = thermo->getMoleFractionsByName(); EXPECT_EQ(X.size(), (size_t) 4); diff --git a/test/thermo/phaseConstructors.cpp b/test/thermo/phaseConstructors.cpp index 2deea16eb9c..f9808be6ea4 100644 --- a/test/thermo/phaseConstructors.cpp +++ b/test/thermo/phaseConstructors.cpp @@ -299,7 +299,7 @@ TEST_F(ConstructFromScratch, addUndefinedElements) ASSERT_EQ((size_t) 4, p.nSpecies()); ASSERT_EQ((size_t) 3, p.nElements()); ASSERT_EQ((size_t) 1, p.nAtoms(p.speciesIndex("CO2"), p.elementIndex("C"))); - ASSERT_EQ((size_t) 2, p.nAtoms(p.speciesIndex("CO2"), p.elementIndex("O"))); + ASSERT_EQ((size_t) 2, p.nAtoms(p.speciesIndex("co2"), p.elementIndex("O"))); p.setMassFractionsByName("H2:0.5, CO2:0.5"); ASSERT_DOUBLE_EQ(0.5, p.massFraction("CO2")); } From 55e7a30f0e19b4eba79895b54ff271c7e170236a Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 23 Sep 2019 16:37:34 -0500 Subject: [PATCH 5/6] Isolate lowercase fallback in speciesIndex --- interfaces/cython/cantera/_cantera.pxd | 1 + interfaces/cython/cantera/test/test_thermo.py | 4 ++ interfaces/cython/cantera/thermo.pyx | 3 +- src/thermo/Phase.cpp | 48 ++++--------------- 4 files changed, 16 insertions(+), 40 deletions(-) diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index af5f9c14735..a37104633f3 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -165,6 +165,7 @@ cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera": # species properties size_t nSpecies() + shared_ptr[CxxSpecies] species(string) except +translate_exception shared_ptr[CxxSpecies] species(size_t) except +translate_exception size_t speciesIndex(string) except +translate_exception string speciesName(size_t) except +translate_exception diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index d2fddd4fb18..8ce4d846e6d 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -1257,6 +1257,10 @@ def test_case_sensitive_names(self): gas = ct.Solution('h2o2.xml') self.assertFalse(gas.case_sensitive_species_names) self.assertTrue(gas.species_index('h2') == 0) + gas.X = 'h2:.5, o2:.5' + self.assertNear(gas.X[0], 0.5) + gas.Y = 'h2:.5, o2:.5' + self.assertNear(gas.Y[0], 0.5) gas.case_sensitive_species_names = True self.assertTrue(gas.case_sensitive_species_names) diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index f54d6b332b5..4980b69ecca 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -490,8 +490,7 @@ cdef class ThermoPhase(_SolutionBase): s = Species(init=False) if isinstance(k, (str, bytes)): - kk = self.thermo.speciesIndex(stringify(k)) - s._assign(self.thermo.species(kk)) + s._assign(self.thermo.species(stringify(k))) elif isinstance(k, (int, float)): s._assign(self.thermo.species(k)) else: diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index 62fe85cd9c6..a457c7ccb54 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -195,9 +195,8 @@ size_t Phase::findSpeciesLower(const std::string& name) const size_t Phase::speciesIndex(const std::string& nameStr) const { size_t loc = npos; - std::map::const_iterator it; - it = m_speciesIndices.find(nameStr); + auto it = m_speciesIndices.find(nameStr); if (it != m_speciesIndices.end()) { return it->second; } else if (!m_caseSensitiveSpecies) { @@ -369,19 +368,12 @@ void Phase::setMassFractionsByName(const compositionMap& yMap) { vector_fp mf(m_kk, 0.0); for (const auto& sp : yMap) { - try { - mf[m_speciesIndices.at(sp.first)] = sp.second; - } catch (std::out_of_range&) { - size_t loc = npos; - if (!m_caseSensitiveSpecies) { - loc = findSpeciesLower(sp.first); - } - if (loc == npos) { - throw CanteraError("Phase::setMassFractionsByName", - "Unknown species '{}'", sp.first); - } else { - mf[loc] = sp.second; - } + size_t loc = speciesIndex(sp.first); + if (loc != npos) { + mf[loc] = sp.second; + } else { + throw CanteraError("Phase::setMassFractionsByName", + "Unknown species '{}'", sp.first); } } setMassFractions(&mf[0]); @@ -846,29 +838,9 @@ void Phase::modifySpecies(size_t k, shared_ptr spec) shared_ptr Phase::species(const std::string& name) const { - std::map >::const_iterator it; - - it = m_species.find(name); - if (it != m_species.end()) { - return it->second; - } else if (!m_caseSensitiveSpecies) { - std::string nLower = toLowerCopy(name); - bool found = false; - shared_ptr sp; - for (const auto& k : m_species) { - if (toLowerCopy(k.first) == nLower) { - if (!found) { - found = true; - sp = k.second; - } else { - throw CanteraError("Phase::species", - "Lowercase species name '{}' is not unique. " - "Set Phase::caseSensitiveSpecies to true to " - "enforce case sensitive species names", nLower); - } - } - } - return sp; + size_t k = speciesIndex(name); + if (k != npos) { + return m_species.at(speciesName(k)); } else { throw CanteraError("Phase::setMassFractionsByName", "Unknown species '{}'", name); From fb9c7497fdd79975bdc89f93c46335b1cf986d8d Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Mon, 23 Sep 2019 17:59:11 -0500 Subject: [PATCH 6/6] Implement faster fallback for non-canonical/lowercase name lookup --- include/cantera/thermo/Phase.h | 3 +++ src/thermo/Phase.cpp | 29 ++++++++++++++++++----------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index 37ab5d5de3c..a82e69d7842 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -859,6 +859,9 @@ class Phase //! Map of species names to indices std::map m_speciesIndices; + //! Map of lower-case species names to indices + std::map m_speciesLower; + size_t m_mm; //!< Number of elements. vector_fp m_atomicWeights; //!< element atomic weights (kg kmol-1) vector_int m_atomicNumbers; //!< element atomic numbers diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index a457c7ccb54..a39cea99658 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -175,18 +175,18 @@ void Phase::getAtoms(size_t k, double* atomArray) const size_t Phase::findSpeciesLower(const std::string& name) const { - size_t loc = npos; std::string nLower = toLowerCopy(name); - for (const auto& k : m_speciesIndices) { - if (toLowerCopy(k.first) == nLower) { - if (loc == npos) { - loc = k.second; - } else { - throw CanteraError("Phase::findSpeciesLower", - "Lowercase species name '{}' is not unique. " - "Set Phase::caseSensitiveSpecies to true to " - "enforce case sensitive species names", nLower); - } + size_t loc = npos; + auto it = m_speciesLower.find(nLower); + if (it == m_speciesLower.end()) { + return npos; + } else { + loc = it->second; + if (loc == npos) { + throw CanteraError("Phase::findSpeciesLower", + "Lowercase species name '{}' is not unique. " + "Set Phase::caseSensitiveSpecies to true to " + "enforce case sensitive species names", nLower); } } return loc; @@ -766,6 +766,13 @@ bool Phase::addSpecies(shared_ptr spec) { m_speciesCharge.push_back(spec->charge); size_t ne = nElements(); + std::string nLower = toLowerCopy(spec->name); + if (m_speciesLower.find(nLower) == m_speciesLower.end()) { + m_speciesLower[nLower] = m_kk; + } else { + m_speciesLower[nLower] = npos; + } + double wt = 0.0; const vector_fp& aw = atomicWeights(); if (spec->charge != 0.0) {