diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index 48635dc4c0..7f5c37fd75 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -703,6 +703,24 @@ class Phase */ virtual void modifySpecies(size_t k, shared_ptr spec); + //! Add a species alias (i.e. user-defined alternative species name). + //! Aliases are case-sensitive. + //! @param name original species name std::string. + //! @param alias alternate name std::string. + //! @return `true` if the alias was successfully added + //! (i.e. the original species name is found) + void addSpeciesAlias(const std::string& name, const std::string& alias); + + //! Return a vector with isomers names matching a given composition map + //! @param compMap compositionMap of the species. + //! @return A vector of species names for matching species. + virtual std::vector findIsomers(const compositionMap& compMap) const; + + //! Return a vector with isomers names matching a given composition string + //! @param comp String containing a composition map + //! @return A vector of species names for matching species. + virtual std::vector findIsomers(const std::string& comp) const; + //! Return the Species object for the named species. Changes to this object //! do not affect the ThermoPhase object until the #modifySpecies function //! is called. diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index 67939086e2..58d8b2650a 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -183,6 +183,9 @@ cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera": void getAtoms(size_t, double*) except +translate_exception cbool caseSensitiveSpecies() void setCaseSensitiveSpecies(cbool) + void addSpeciesAlias(string, string) except +translate_exception + vector[string] findIsomers(Composition&) except +translate_exception + vector[string] findIsomers(string) except +translate_exception 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 17ba484d18..fa2b50f5de 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -1140,6 +1140,27 @@ def test_modify_thermo_invalid(self): with self.assertRaisesRegex(ct.CanteraError, 'modifySpecies'): self.gas.modify_species(self.gas.species_index('H2'), copy) + def test_alias(self): + self.gas.add_species_alias('H2', 'hydrogen') + self.assertTrue(self.gas.species_index('hydrogen') == 0) + self.gas.X = 'hydrogen:.5, O2:.5' + self.assertNear(self.gas.X[0], 0.5) + with self.assertRaisesRegex(ct.CanteraError, 'Invalid alias'): + self.gas.add_species_alias('H2', 'O2') + with self.assertRaisesRegex(ct.CanteraError, 'Unable to add alias'): + self.gas.add_species_alias('spam', 'eggs') + + def test_isomers(self): + gas = ct.Solution('nDodecane_Reitz.yaml') + iso = gas.find_isomers({'C':4, 'H':9, 'O':2}) + self.assertTrue(len(iso) == 2) + iso = gas.find_isomers('C:4, H:9, O:2') + self.assertTrue(len(iso) == 2) + iso = gas.find_isomers({'C':7, 'H':15}) + self.assertTrue(len(iso) == 1) + iso = gas.find_isomers({'C':7, 'H':16}) + self.assertTrue(len(iso) == 0) + class TestSpeciesThermo(utilities.CanteraTest): h2o_coeffs = [ diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index aa5dad5eb1..01defe4e97 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -524,6 +524,26 @@ cdef class ThermoPhase(_SolutionBase): if self.kinetics: self.kinetics.invalidateCache() + def add_species_alias(self, name, alias): + """ + Add the alternate species name *alias* for an original species *name*. + """ + self.thermo.addSpeciesAlias(stringify(name), stringify(alias)) + + def find_isomers(self, comp): + """ + Find species/isomers matching a composition specified by *comp*. + """ + + if isinstance(comp, dict): + iso = self.thermo.findIsomers(comp_map(comp)) + elif isinstance(comp, (str, bytes)): + iso = self.thermo.findIsomers(stringify(comp)) + else: + raise CanteraError('Invalid composition') + + return [pystr(b) for b in iso] + def n_atoms(self, species, element): """ Number of atoms of element *element* in species *species*. The element diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index 2c18552e38..c0fd0e1bb7 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -852,6 +852,40 @@ void Phase::modifySpecies(size_t k, shared_ptr spec) invalidateCache(); } +void Phase::addSpeciesAlias(const std::string& name, const std::string& alias) +{ + if (speciesIndex(alias) != npos) { + throw CanteraError("Phase::addSpeciesAlias", + "Invalid alias '{}': species already exists", alias); + } + size_t k = speciesIndex(name); + if (k != npos) { + m_speciesIndices[alias] = k; + } else { + throw CanteraError("Phase::addSpeciesAlias", + "Unable to add alias '{}' " + "(original species '{}' not found).", alias, name); + } +} + +vector Phase::findIsomers(const compositionMap& compMap) const +{ + vector isomerNames; + + for (const auto& k : m_species) { + if (k.second->composition == compMap) { + isomerNames.emplace_back(k.first); + } + } + + return isomerNames; +} + +vector Phase::findIsomers(const std::string& comp) const +{ + return findIsomers(parseCompString(comp)); +} + shared_ptr Phase::species(const std::string& name) const { size_t k = speciesIndex(name);