diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index d6f3a678121..a82e69d7842 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 @@ -747,6 +747,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,7 +805,15 @@ class Phase //! Flag determining behavior when adding species with an undefined element UndefElement::behavior m_undefinedElementBehavior; + //! 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 @@ -840,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/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index 26eff9b62e4..a37104633f3 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -171,6 +171,8 @@ cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera": 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/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index b91a988fe53..8ce4d846e6d 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -1253,6 +1253,39 @@ 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.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) + 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/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index b8db5d6857b..4980b69ecca 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 diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index 880bdfae9b1..a39cea99658 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,48 @@ void Phase::getAtoms(size_t k, double* atomArray) const } } +size_t Phase::findSpeciesLower(const std::string& name) const +{ + std::string nLower = toLowerCopy(name); + 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; +} + size_t Phase::speciesIndex(const std::string& nameStr) const { - size_t loc = getValue(m_speciesIndices, toLowerCopy(nameStr), npos); + size_t loc = npos; + + auto 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 = 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; + it = m_speciesIndices.find(nameStr); + if (it != m_speciesIndices.end()) { + return it->second; + } else if (!m_caseSensitiveSpecies) { + return findSpeciesLower(sn); + } } - } else { - return loc; } + return loc; } string Phase::speciesName(size_t k) const @@ -292,10 +321,12 @@ 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(toLowerCopy(sp.first))] = sp.second; - } catch (std::out_of_range&) { + size_t loc = speciesIndex(sp.first); + if (loc != npos) { + mf[loc] = sp.second; + } else { throw CanteraError("Phase::setMoleFractionsByName", "Unknown species '{}'", sp.first); } @@ -337,9 +368,10 @@ 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; - } catch (std::out_of_range&) { + size_t loc = speciesIndex(sp.first); + if (loc != npos) { + mf[loc] = sp.second; + } else { throw CanteraError("Phase::setMassFractionsByName", "Unknown species '{}'", sp.first); } @@ -699,7 +731,7 @@ 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()) { + if (m_species.find(spec->name) != m_species.end()) { throw CanteraError("Phase::addSpecies", "Phase '{}' already contains a species named '{}'.", m_name, spec->name); @@ -729,11 +761,18 @@ 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(); + 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) { @@ -794,24 +833,30 @@ 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(); } shared_ptr Phase::species(const std::string& name) const { - return m_species.at(toLowerCopy(name)); + size_t k = speciesIndex(name); + if (k != npos) { + return m_species.at(speciesName(k)); + } else { + throw CanteraError("Phase::setMassFractionsByName", + "Unknown species '{}'", 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/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index e950ed550b3..d79fc33343c 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" @@ -718,7 +718,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 +735,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 +753,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 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) {