diff --git a/include/cantera/base/stringUtils.h b/include/cantera/base/stringUtils.h index bcc86aa2b..0d6e355bb 100644 --- a/include/cantera/base/stringUtils.h +++ b/include/cantera/base/stringUtils.h @@ -94,6 +94,18 @@ std::string lowercase(const std::string& s); compositionMap parseCompString(const std::string& ss, const std::vector& names); +//! Parse a composition string into a map consisting of individual +//! key:composition pairs. +/*! + * This version of the function returns a map containing only those keys in + * the provided string, and does not require them to be drawn from a specific + * set of names. + * + * @param ss original string consisting of multiple key:composition pairs + * @return map of names to values + */ +compositionMap parseCompString(const std::string& ss); + //! Parse a composition string into individual key:composition pairs /*! * @param ss original string consisting of multiple key:composition diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index f00269dab..29feb245a 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -9,6 +9,7 @@ #include "cantera/base/ctexceptions.h" #include "cantera/thermo/Elements.h" +#include "cantera/thermo/Species.h" #include "cantera/base/ValueCache.h" namespace Cantera @@ -716,6 +717,8 @@ public: doublereal entropy298 = ENTROPY298_UNKNOWN, int elem_type = CT_ELEM_TYPE_ABSPOS); + virtual void addSpecies(const Species& spec); + void addSpecies(const std::string& name, const doublereal* comp, doublereal charge = 0.0, doublereal size = 1.0); @@ -777,6 +780,8 @@ protected: vector_fp m_speciesCharge; //!< Vector of species charges. length m_kk. + std::map m_species; + private: XML_Node* m_xml; //!< XML node containing the XML info for this phase diff --git a/include/cantera/thermo/Species.h b/include/cantera/thermo/Species.h new file mode 100644 index 000000000..609890030 --- /dev/null +++ b/include/cantera/thermo/Species.h @@ -0,0 +1,59 @@ +//! @file Species.h Declaration for class Cantera::Species. + +#ifndef CT_SPECIES_H +#define CT_SPECIES_H + +#include "cantera/base/ct_defs.h" + +namespace Cantera +{ + +class SpeciesThermoInterpType; + +//! Contains data about a single chemical species +/*! + * This class stores the data about a species which may be needed to add it to + * a ThermoPhase or Transport object. + */ +class Species +{ +public: + Species(); + + //! Constructor + /*! + * The Species object takes ownership of the SpeciesThermoInterpType + * object, if provided. + */ + Species(const std::string& name, const compositionMap& comp, + SpeciesThermoInterpType* thermo=0, double charge=0.0, + double size=0.0); + + Species(const Species& other); + Species& operator=(const Species& other); + ~Species(); + + //! Access the thermodynamic parameterization for the species + const SpeciesThermoInterpType& thermo() const; + + //! The name of the species + std::string name; + + //! The elemental composition of the species. Keys are element names; values + //! are the corresponding atomicities. + compositionMap composition; + + //! The electrical charge on the species, in units of the elementary charge. + double charge; + + //! The effective size [m] of the species + double size; + +protected: + //! Thermodynamic data for the species + SpeciesThermoInterpType* thermo_; +}; + +} + +#endif diff --git a/include/cantera/thermo/ThermoPhase.h b/include/cantera/thermo/ThermoPhase.h index 954e873ad..6a5b0b324 100644 --- a/include/cantera/thermo/ThermoPhase.h +++ b/include/cantera/thermo/ThermoPhase.h @@ -1277,6 +1277,9 @@ public: */ //@{ + using Phase::addSpecies; + virtual void addSpecies(const Species& spec); + //! Store a reference pointer to the XML tree containing the species data //! for this phase. /*! diff --git a/src/base/stringUtils.cpp b/src/base/stringUtils.cpp index 868a58058..ea5799da3 100644 --- a/src/base/stringUtils.cpp +++ b/src/base/stringUtils.cpp @@ -169,6 +169,35 @@ compositionMap parseCompString(const std::string& ss, return x; } +compositionMap parseCompString(const std::string& ss) +{ + compositionMap x; + std::string s = ss; + std::string num; + while (!s.empty()) { + size_t ibegin = s.find_first_not_of(", ;\n\t"); + if (ibegin != std::string::npos) { + s = s.substr(ibegin,s.size()); + size_t icolon = s.find(':'); + size_t iend = s.find_first_of(", ;\n\t"); + if (icolon != std::string::npos) { + std::string name = stripws(s.substr(0, icolon)); + if (iend != std::string::npos) { + num = s.substr(icolon+1, iend-icolon-1); + s = s.substr(iend+1, s.size()); + } else { + num = s.substr(icolon+1, s.size()); + s = ""; + } + x[name] = fpValueCheck(num); + } else { + s = ""; + } + } + }; + return x; +} + void split(const std::string& ss, std::vector& w) { std::string s = ss; diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index d68749f23..e98746132 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -817,47 +817,54 @@ size_t Phase::addUniqueElementAfterFreeze(const std::string& symbol, return addElement(symbol, weight, atomicNumber, entropy298, elem_type); } -void Phase::addSpecies(const std::string& name_, const doublereal* comp, - doublereal charge_, doublereal size_) -{ - m_speciesNames.push_back(name_); - m_speciesCharge.push_back(charge_); - m_speciesSize.push_back(size_); - size_t ne = nElements(); - // Create a changeable copy of the element composition. We now change - // the charge potentially - vector_fp compNew(ne); - for (size_t m = 0; m < ne; m++) { - compNew[m] = comp[m]; +void Phase::addSpecies(const Species& spec) { + m_species[spec.name] = spec; + vector_fp comp(nElements()); + for (map::const_iterator iter = spec.composition.begin(); + iter != spec.composition.end(); + iter++) { + size_t m = elementIndex(iter->first); + if (m == npos) { + throw CanteraError("Phase::addSpecies", + "Species '" + spec.name + "' contains an " + "undefined element '" + iter->first + "'."); + } + comp[m] = iter->second; } + + m_speciesNames.push_back(spec.name); + m_speciesCharge.push_back(spec.charge); + m_speciesSize.push_back(spec.size); + size_t ne = nElements(); + double wt = 0.0; const vector_fp& aw = atomicWeights(); - if (charge_ != 0.0) { + if (spec.charge != 0.0) { size_t eindex = elementIndex("E"); if (eindex != npos) { - doublereal ecomp = compNew[eindex]; - if (fabs(charge_ + ecomp) > 0.001) { + doublereal ecomp = comp[eindex]; + if (fabs(spec.charge + ecomp) > 0.001) { if (ecomp != 0.0) { throw CanteraError("Phase::addSpecies", "Input charge and element E compositions differ " - "for species " + name_); + "for species " + spec.name); } else { // Just fix up the element E composition based on the input // species charge - compNew[eindex] = -charge_; + comp[eindex] = -spec.charge; } } } else { addElement("E", 0.000545, 0, 0.0, CT_ELEM_TYPE_ELECTRONCHARGE); ne = nElements(); eindex = elementIndex("E"); - compNew.resize(ne); - compNew[ne - 1] = - charge_; + comp.resize(ne); + comp[ne - 1] = - spec.charge; } } for (size_t m = 0; m < ne; m++) { - m_speciesComp.push_back(compNew[m]); - wt += compNew[m] * aw[m]; + m_speciesComp.push_back(comp[m]); + wt += comp[m] * aw[m]; } // Some surface phases may define species representing empty sites @@ -881,6 +888,18 @@ void Phase::addSpecies(const std::string& name_, const doublereal* comp, } } +void Phase::addSpecies(const std::string& name_, const doublereal* comp, + doublereal charge_, doublereal size_) +{ + compositionMap cmap; + for (size_t i = 0; i < nElements(); i++) { + if (comp[i]) { + cmap[elementName(i)] = comp[i]; + } + } + Phase::addSpecies(Species(name_, cmap, 0, charge_, size_)); +} + void Phase::addUniqueSpecies(const std::string& name_, const doublereal* comp, doublereal charge_, doublereal size_) { diff --git a/src/thermo/Species.cpp b/src/thermo/Species.cpp new file mode 100644 index 000000000..00df0a0f9 --- /dev/null +++ b/src/thermo/Species.cpp @@ -0,0 +1,75 @@ +#include "cantera/thermo/Species.h" + +#include "cantera/thermo/SpeciesThermoInterpType.h" +#include "cantera/base/stringUtils.h" +#include "cantera/base/ctexceptions.h" +#include +#include + +namespace Cantera { + +Species::Species() + : charge(std::numeric_limits::quiet_NaN()) + , size(std::numeric_limits::quiet_NaN()) + , thermo_(0) +{ +} + +Species::Species(const std::string& name_, const compositionMap& comp_, + SpeciesThermoInterpType* therm, double charge_, double size_) + : name(name_) + , composition(comp_) + , charge(charge_) + , size(size_) + , thermo_(therm) +{ +} + +Species::~Species() +{ + delete thermo_; +} + +Species::Species(const Species& other) + : name(other.name) + , composition(other.composition) + , charge(other.charge) + , size(other.size) +{ + if (other.thermo_) { + thermo_ = other.thermo_->duplMyselfAsSpeciesThermoInterpType(); + } else { + thermo_ = 0; + } +} + +Species& Species::operator=(const Species& other) +{ + if (this == &other) { + return *this; + } + name = other.name; + composition = other.composition; + charge = other.charge; + size = other.size; + delete thermo_; + if (other.thermo_) { + thermo_ = other.thermo_->duplMyselfAsSpeciesThermoInterpType(); + } else { + thermo_ = 0; + } + return *this; +} + + +const SpeciesThermoInterpType& Species::thermo() const +{ + if (thermo_) { + return *thermo_; + } else { + throw CanteraError("Species::thermo", + "No thermo for species " + name); + } +} + +} diff --git a/src/thermo/ThermoPhase.cpp b/src/thermo/ThermoPhase.cpp index 7aa0827d9..885864803 100644 --- a/src/thermo/ThermoPhase.cpp +++ b/src/thermo/ThermoPhase.cpp @@ -10,6 +10,7 @@ #include "cantera/thermo/ThermoPhase.h" #include "cantera/base/stringUtils.h" #include "cantera/thermo/ThermoFactory.h" +#include "cantera/thermo/SpeciesThermoInterpType.h" #include "cantera/thermo/GeneralSpeciesThermo.h" #include "cantera/base/ctml.h" #include "cantera/base/vec_functions.h" @@ -694,6 +695,12 @@ void ThermoPhase::installSlavePhases(Cantera::XML_Node* phaseNode) { } +void ThermoPhase::addSpecies(const Species& spec) +{ + Phase::addSpecies(spec); + m_spthermo->install_STIT(spec.thermo().duplMyselfAsSpeciesThermoInterpType()); +} + void ThermoPhase::saveSpeciesData(const size_t k, const XML_Node* const data) { if (m_speciesData.size() < (k + 1)) { diff --git a/test/thermo/phaseConstructors.cpp b/test/thermo/phaseConstructors.cpp index 72f413e22..ec75d429e 100644 --- a/test/thermo/phaseConstructors.cpp +++ b/test/thermo/phaseConstructors.cpp @@ -100,81 +100,4 @@ TEST_F(ChemkinConversionTest, FailedConversion) { } #endif -// 2-region NASA coefficients; Order is significantly different from the -// standard NASA format. -double h2o_coeffs[] = { - 1000.0, -3.029372670E+04, -8.490322080E-01, 4.198640560E+00, - -2.036434100E-03, 6.520402110E-06, -5.487970620E-09, 1.771978170E-12, - -3.000429710E+04, 4.966770100E+00, 3.033992490E+00, 2.176918040E-03, - -1.640725180E-07, -9.704198700E-11, 1.682009920E-14}; -double h2o_comp[] = {2.0, 1.0, 0.0}; - -double h2_coeffs[] = { - 1000.0, -9.17935173E+02, 6.83010238E-01, 2.34433112E+00, - 7.98052075E-03, -1.94781510E-05, 2.01572094E-08, -7.37611761E-12, - -9.50158922E+02, -3.20502331E+00, 3.33727920E+00, -4.94024731E-05, - 4.99456778E-07, -1.79566394E-10, 2.00255376E-14}; -double h2_comp[] = {2.0, 0.0, 0.0}; - -double o2_coeffs[] = { - 1000.0, -1.063943560E+03, 3.657675730E+00, 3.782456360E+00, - -2.996734160E-03, 9.847302010E-06, -9.681295090E-09, 3.243728370E-12, - -1.088457720E+03, 5.453231290E+00, 3.282537840E+00, 1.483087540E-03, - -7.579666690E-07, 2.094705550E-10, -2.167177940E-14}; -double o2_comp[] = {0.0, 2.0, 0.0}; - -double oh_coeffs[] = { - 1000.0, 3.615080560E+03, -1.039254580E-01, 3.992015430E+00, - -2.401317520E-03, 4.617938410E-06, -3.881133330E-09, 1.364114700E-12, - 3.858657000E+03, 4.476696100E+00, 3.092887670E+00, 5.484297160E-04, - 1.265052280E-07, -8.794615560E-11, 1.174123760E-14}; -double oh_comp[] = {1.0, 1.0, 0.0}; - -// 2-region Shomate coefficients -double co2_coeffs[] = { - 1200.0, 24.99735, 55.18696, -33.69137, 7.948387, -0.136638, -403.6075, 228.2431, - 58.16639, 2.720074, -0.492289, 0.038844, -6.447293, -425.9186, 263.6125}; -double co2_comp[] = {0.0, 2.0, 1.0}; - -class ConstructFromScratch : public testing::Test -{ -public: - ConstructFromScratch() { - p.addElement("H"); - p.addElement("O"); - p.addElement("C"); - } - IdealGasPhase p; -}; - -TEST_F(ConstructFromScratch, AddElements) -{ - ASSERT_EQ((size_t) 3, p.nElements()); - ASSERT_EQ("H", p.elementName(0)); - ASSERT_EQ((size_t) 1, p.elementIndex("O")); -} - -TEST_F(ConstructFromScratch, AddSpeciesNasa) -{ - p.setSpeciesThermo(newSpeciesThermoMgr(NASA)); - SpeciesThermo& sp = p.speciesThermo(); - - p.addUniqueSpecies("H2O", h2o_comp); - sp.install("H2O", 0, NASA, h2o_coeffs, 200.0, 3500.0, 101325.0); - p.addUniqueSpecies("H2", h2_comp); - sp.install("H2", 1, NASA, h2_coeffs, 200.0, 3500.0, 101325.0); - - ASSERT_EQ((size_t) 2, p.nSpecies()); - - p.addUniqueSpecies("O2", o2_comp); - sp.install("O2", 2, NASA, o2_coeffs, 200.0, 3500.0, 101325.0); - p.addUniqueSpecies("OH", oh_comp); - sp.install("OH", 3, NASA, oh_coeffs, 200.0, 3500.0, 101325.0); - - ASSERT_EQ((size_t) 4, p.nSpecies()); - ASSERT_EQ("H2", p.speciesName(1)); - ASSERT_EQ(2, p.nAtoms(2, 1)); // O in O2 - ASSERT_EQ(2, p.nAtoms(0, 0)); // H in H2O -} - } // namespace Cantera diff --git a/test/thermo/thermoParameterizations.cpp b/test/thermo/thermoParameterizations.cpp index c3fac0ef9..dc073a6f1 100644 --- a/test/thermo/thermoParameterizations.cpp +++ b/test/thermo/thermoParameterizations.cpp @@ -13,28 +13,13 @@ using namespace Cantera; class SpeciesThermoInterpTypeTest : public testing::Test { public: - void makePhase0() { + SpeciesThermoInterpTypeTest() { p.addElement("H"); p.addElement("O"); p.addElement("C"); - st = &p.speciesThermo(); - } - - void makePhase1() { - makePhase0(); - p.addSpecies("O2", o2_comp); - p.addSpecies("H2", h2_comp); - p.addSpecies("H2O", h2o_comp); - } - - void makePhase2() { - makePhase0(); - p.addSpecies("CO", co_comp); - p.addSpecies("CO2", co2_comp); } IdealGasPhase p; - SpeciesThermo* st; }; // {T0, h0, s0, cp0} (in J/kmol) @@ -47,13 +32,12 @@ TEST_F(SpeciesThermoInterpTypeTest, install_const_cp) { // Compare against instantiation from CTI file IdealGasPhase p2("../data/simplephases.cti", "simple1"); - makePhase1(); SpeciesThermoInterpType* stit_o2 = new ConstCpPoly(0, 200, 5000, 101325, c_o2); SpeciesThermoInterpType* stit_h2 = new ConstCpPoly(1, 200, 5000, 101325, c_h2); SpeciesThermoInterpType* stit_h2o = new ConstCpPoly(2, 200, 5000, 101325, c_h2o); - st->install_STIT(stit_o2); - st->install_STIT(stit_h2); - st->install_STIT(stit_h2o); + p.addSpecies(Species("O2", parseCompString("O:2"), stit_o2)); + p.addSpecies(Species("H2", parseCompString("H:2"), stit_h2)); + p.addSpecies(Species("H2O", parseCompString("H:2 O:1"), stit_h2o)); p.initThermo(); p2.setState_TPX(298.15, 101325, "H2:0.2, O2:0.7, H2O:0.1"); p.setState_TPX(298.15, 101325, "H2:0.2, O2:0.7, H2O:0.1"); @@ -67,12 +51,11 @@ TEST_F(SpeciesThermoInterpTypeTest, DISABLED_install_bad_pref) { // Currently broken because GeneralSpeciesThermo does not enforce reference // pressure consistency. - makePhase1(); SpeciesThermoInterpType* stit_o2 = new ConstCpPoly(0, 200, 5000, 101325, c_o2); SpeciesThermoInterpType* stit_h2 = new ConstCpPoly(1, 200, 5000, 100000, c_h2); - st->install_STIT(stit_o2); + p.addSpecies(Species("O2", parseCompString("O:2"), stit_o2)); // Pref does not match - ASSERT_THROW(st->install_STIT(stit_h2), CanteraError); + ASSERT_THROW(p.addSpecies(Species("H2", parseCompString("H:2"), stit_h2)), CanteraError); delete stit_h2; } @@ -80,13 +63,12 @@ TEST_F(SpeciesThermoInterpTypeTest, install_nasa) { // Compare against instantiation from CTI file IdealGasPhase p2("../data/simplephases.cti", "nasa1"); - makePhase1(); SpeciesThermoInterpType* stit_o2 = new NasaPoly2(0, 200, 3500, 101325, o2_nasa_coeffs); SpeciesThermoInterpType* stit_h2 = new NasaPoly2(1, 200, 3500, 101325, h2_nasa_coeffs); SpeciesThermoInterpType* stit_h2o = new NasaPoly2(2, 200, 3500, 101325, h2o_nasa_coeffs); - st->install_STIT(stit_o2); - st->install_STIT(stit_h2); - st->install_STIT(stit_h2o); + p.addSpecies(Species("O2", parseCompString("O:2"), stit_o2)); + p.addSpecies(Species("H2", parseCompString("H:2"), stit_h2)); + p.addSpecies(Species("H2O", parseCompString("H:2 O:1"), stit_h2o)); p.initThermo(); p2.setState_TPX(900, 101325, "H2:0.2, O2:0.7, H2O:0.1"); p.setState_TPX(900, 101325, "H2:0.2, O2:0.7, H2O:0.1"); @@ -100,11 +82,10 @@ TEST_F(SpeciesThermoInterpTypeTest, install_shomate) { // Compare against instantiation from CTI file IdealGasPhase p2("../data/simplephases.cti", "shomate1"); - makePhase2(); SpeciesThermoInterpType* stit_co = new ShomatePoly2(0, 200, 6000, 101325, co_shomate_coeffs); SpeciesThermoInterpType* stit_co2 = new ShomatePoly2(1, 200, 6000, 101325, co2_shomate_coeffs); - st->install_STIT(stit_co); - st->install_STIT(stit_co2); + p.addSpecies(Species("CO", parseCompString("C:1 O:1"), stit_co)); + p.addSpecies(Species("CO2", parseCompString("C:1 O:2"), stit_co2)); p.initThermo(); p2.setState_TPX(900, 101325, "CO:0.2, CO2:0.8"); p.setState_TPX(900, 101325, "CO:0.2, CO2:0.8");