From a6ac446021b8a8b3b314f4d8f8dc8bb81ee66ac2 Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Sun, 19 Feb 2017 20:29:09 -0500 Subject: [PATCH] [Thermo] Allow instantiation of IdealMolalSoln without XML --- include/cantera/thermo/IdealMolalSoln.h | 41 ++++++------ src/thermo/IdealMolalSoln.cpp | 83 +++++++++++-------------- test/thermo/phaseConstructors.cpp | 60 ++++++++++++++---- 3 files changed, 108 insertions(+), 76 deletions(-) diff --git a/include/cantera/thermo/IdealMolalSoln.h b/include/cantera/thermo/IdealMolalSoln.h index 162e58255..fe72492e6 100644 --- a/include/cantera/thermo/IdealMolalSoln.h +++ b/include/cantera/thermo/IdealMolalSoln.h @@ -47,14 +47,8 @@ namespace Cantera * ThermoPhase, and overloads the virtual methods defined there with ones that * use expressions appropriate for incompressible mixtures. * - * The standard concentrations can have three different forms depending on the - * value of the member attribute m_formGC, which is supplied in the XML file. - * - * | m_formGC | ActivityConc | StandardConc | - * | -------- | -------------------------------- | ------------------ | - * | 0 | \f$ {m_k}/ { m^{\Delta}}\f$ | \f$ 1.0 \f$ | - * | 1 | \f$ m_k / (m^{\Delta} V_k)\f$ | \f$ 1.0 / V_k \f$ | - * | 2 | \f$ m_k / (m^{\Delta} V^0_0)\f$ | \f$ 1.0 / V^0_0\f$ | + * The standard concentrations can have three different forms. + * See setStandardConcentrationModel(). * * \f$ V^0_0 \f$ is the solvent standard molar volume. \f$ m^{\Delta} \f$ is a * constant equal to a molality of \f$ 1.0 \quad\mbox{gm kmol}^{-1} \f$. @@ -414,6 +408,24 @@ public: virtual void initThermoXML(XML_Node& phaseNode, const std::string& id=""); + virtual void initThermo(); + + //! Set the standard concentration model. + /*! + * Must be one of 'unity', 'molar_volume', or 'solvent_volume'. + * The default is 'solvent_volume'. + * + * | model | ActivityConc | StandardConc | + * | -------------- | -------------------------------- | ------------------ | + * | unity | \f$ {m_k}/ { m^{\Delta}}\f$ | \f$ 1.0 \f$ | + * | molar_volume | \f$ m_k / (m^{\Delta} V_k)\f$ | \f$ 1.0 / V_k \f$ | + * | solvent_volume | \f$ m_k / (m^{\Delta} V^0_0)\f$ | \f$ 1.0 / V^0_0\f$ | + */ + void setStandardConcentrationModel(const std::string& model); + + //! Set cutoff model. Must be one of 'none', 'poly', or 'polyExp'. + void setCutoffModel(const std::string& model); + //! Report the molar volume of species k /*! * units - \f$ m^3 kmol^{-1} \f$ @@ -436,19 +448,12 @@ protected: vector_fp m_speciesMolarVolume; /** - * The standard concentrations can have three different forms depending on - * the value of the member attribute m_formGC, which is supplied in the XML - * file. - * - * | m_formGC | ActivityConc | StandardConc | - * | -------- | -------------------------------- | ------------------ | - * | 0 | \f$ {m_k}/ { m^{\Delta}}\f$ | \f$ 1.0 \f$ | - * | 1 | \f$ m_k / (m^{\Delta} V_k)\f$ | \f$ 1.0 / V_k \f$ | - * | 2 | \f$ m_k / (m^{\Delta} V^0_0)\f$ | \f$ 1.0 / V^0_0\f$ | + * The standard concentrations can have one of three different forms: + * 0 = 'unity', 1 = 'molar_volume', 2 = 'solvent_volume'. See + * setStandardConcentrationModel(). */ int m_formGC; -public: //! Cutoff type int IMS_typeCutoff_; diff --git a/src/thermo/IdealMolalSoln.cpp b/src/thermo/IdealMolalSoln.cpp index 1ea4fd5de..48a9e4cbf 100644 --- a/src/thermo/IdealMolalSoln.cpp +++ b/src/thermo/IdealMolalSoln.cpp @@ -18,6 +18,7 @@ #include "cantera/thermo/IdealMolalSoln.h" #include "cantera/thermo/ThermoFactory.h" #include "cantera/base/ctml.h" +#include "cantera/base/stringUtils.h" #include namespace Cantera @@ -356,8 +357,7 @@ bool IdealMolalSoln::addSpecies(shared_ptr spec) void IdealMolalSoln::initThermoXML(XML_Node& phaseNode, const std::string& id_) { - // Initialize the whole thermo object, using a virtual function. - initThermo(); + MolalityVPSSTP::initThermoXML(phaseNode, id_); if (id_.size() > 0 && phaseNode.id() != id_) { throw CanteraError("IdealMolalSoln::initThermo", @@ -374,20 +374,7 @@ void IdealMolalSoln::initThermoXML(XML_Node& phaseNode, const std::string& id_) // Possible change the form of the standard concentrations if (thermoNode.hasChild("standardConc")) { XML_Node& scNode = thermoNode.child("standardConc"); - m_formGC = 2; - std::string formString = scNode.attrib("model"); - if (formString != "") { - if (formString == "unity") { - m_formGC = 0; - } else if (formString == "molar_volume") { - m_formGC = 1; - } else if (formString == "solvent_volume") { - m_formGC = 2; - } else { - throw CanteraError("IdealMolalSoln::initThermo", - "Unknown standardConc model: " + formString); - } - } + setStandardConcentrationModel(scNode["model"]); } // Get the Name of the Solvent: @@ -406,7 +393,6 @@ void IdealMolalSoln::initThermoXML(XML_Node& phaseNode, const std::string& id_) if (thermoNode.hasChild("activityCoefficients")) { XML_Node& acNode = thermoNode.child("activityCoefficients"); std::string modelString = acNode.attrib("model"); - IMS_typeCutoff_ = 0; if (modelString != "IdealMolalSoln") { throw CanteraError("IdealMolalSoln::initThermoXML", "unknown ActivityCoefficient model: " + modelString); @@ -415,15 +401,7 @@ void IdealMolalSoln::initThermoXML(XML_Node& phaseNode, const std::string& id_) XML_Node& ccNode = acNode.child("idealMolalSolnCutoff"); modelString = ccNode.attrib("model"); if (modelString != "") { - if (modelString == "polyExp") { - IMS_typeCutoff_ = 2; - } else if (modelString == "poly") { - IMS_typeCutoff_ = 1; - } else { - throw CanteraError("IdealMolalSoln::initThermoXML", - "Unknown idealMolalSolnCutoff form: " + modelString); - } - + setCutoffModel(modelString); if (ccNode.hasChild("gamma_o_limit")) { IMS_gamma_o_min_ = getFloat(ccNode, "gamma_o_limit"); } @@ -443,6 +421,8 @@ void IdealMolalSoln::initThermoXML(XML_Node& phaseNode, const std::string& id_) IMS_slopegCut_ = getFloat(ccNode, "slope_g_limit"); } } + } else { + setCutoffModel("none"); } } @@ -464,32 +444,45 @@ void IdealMolalSoln::initThermoXML(XML_Node& phaseNode, const std::string& id_) "Solvent " + solventName + " should be first species"); } +} - // Now go get the molar volumes - XML_Node& speciesList = phaseNode.child("speciesArray"); - XML_Node* speciesDB = - get_XML_NameID("speciesData", speciesList["datasrc"], - &phaseNode.root()); - const std::vector &sss = speciesNames(); - - for (size_t k = 0; k < m_kk; k++) { - XML_Node* s = speciesDB->findByAttr("name", sss[k]); - XML_Node* ss = s->findByName("standardState"); - m_speciesMolarVolume[k] = getFloat(*ss, "molarVolume", "toSI"); +void IdealMolalSoln::initThermo() +{ + MolalityVPSSTP::initThermo(); + for (size_t k = 0; k < nSpecies(); k++) { + m_speciesMolarVolume[k] = providePDSS(k)->molarVolume(); } - - IMS_typeCutoff_ = 2; if (IMS_typeCutoff_ == 2) { calcIMSCutoffParams_(); } - - MolalityVPSSTP::initThermoXML(phaseNode, id_); setMoleFSolventMin(1.0E-5); +} - // Set the state - if (phaseNode.hasChild("state")) { - XML_Node& stateNode = phaseNode.child("state"); - setStateFromXML(stateNode); +void IdealMolalSoln::setStandardConcentrationModel(const std::string& model) +{ + if (ba::iequals(model, "unity")) { + m_formGC = 0; + } else if (ba::iequals(model, "molar_volume")) { + m_formGC = 1; + } else if (ba::iequals(model, "solvent_volume")) { + m_formGC = 2; + } else { + throw CanteraError("IdealSolnGasVPSS::setStandardConcentrationModel", + "Unknown standard concentration model '{}'", model); + } +} + +void IdealMolalSoln::setCutoffModel(const std::string& model) +{ + if (ba::iequals(model, "none")) { + IMS_typeCutoff_ = 0; + } else if (ba::iequals(model, "poly")) { + IMS_typeCutoff_ = 1; + } else if (ba::iequals(model, "polyexp")) { + IMS_typeCutoff_ = 2; + } else { + throw CanteraError("IdealMolalSoln::setCutoffModel", + "Unknown cutoff model '{}'", model); } } diff --git a/test/thermo/phaseConstructors.cpp b/test/thermo/phaseConstructors.cpp index 701633480..a88b83da9 100644 --- a/test/thermo/phaseConstructors.cpp +++ b/test/thermo/phaseConstructors.cpp @@ -1,12 +1,14 @@ #include "gtest/gtest.h" #include "cantera/thermo/ThermoFactory.h" #include "cantera/thermo/PDSSFactory.h" +#include "cantera/thermo/PDSS_ConstVol.h" #include "cantera/thermo/FixedChemPotSSTP.h" #include "cantera/thermo/PureFluidPhase.h" #include "cantera/thermo/WaterSSTP.h" #include "cantera/thermo/RedlichKwongMFTP.h" #include "cantera/thermo/IonsFromNeutralVPSSTP.h" #include "cantera/thermo/IdealSolnGasVPSS.h" +#include "cantera/thermo/IdealMolalSoln.h" #include "cantera/thermo/NasaPoly2.h" #include "cantera/thermo/ShomatePoly.h" #include "cantera/thermo/IdealGasPhase.h" @@ -18,6 +20,14 @@ namespace Cantera { +shared_ptr make_species(const std::string& name, + const std::string& composition, const double* nasa_coeffs) +{ + auto species = make_shared(name, parseCompString(composition)); + species->thermo.reset(new NasaPoly2(200, 3500, 101325, nasa_coeffs)); + return species; +} + class FixedChemPotSstpConstructorTest : public testing::Test { }; @@ -115,18 +125,13 @@ class ConstructFromScratch : public testing::Test { public: ConstructFromScratch() - : sH2O(new Species("H2O", parseCompString("H:2 O:1"))) - , sH2(new Species("H2", parseCompString("H:2"))) - , sO2(new Species("O2", parseCompString("O:2"))) - , sOH(new Species("OH", parseCompString("H:1 O:1"))) - , sCO(new Species("CO", parseCompString("C:1 O:1"))) + : sH2O(make_species("H2O", "H:2 O:1", h2o_nasa_coeffs)) + , sH2(make_species("H2", "H:2", h2_nasa_coeffs)) + , sO2(make_species("O2", "O:2", o2_nasa_coeffs)) + , sOH(make_species("OH", "H:1 O:1", oh_nasa_coeffs)) + , sCO(make_species("CO", "C:1 O:1", o2_nasa_coeffs)) , sCO2(new Species("CO2", parseCompString("C:1 O:2"))) { - sH2O->thermo.reset(new NasaPoly2(200, 3500, 101325, h2o_nasa_coeffs)); - sH2->thermo.reset(new NasaPoly2(200, 3500, 101325, h2_nasa_coeffs)); - sO2->thermo.reset(new NasaPoly2(200, 3500, 101325, o2_nasa_coeffs)); - sOH->thermo.reset(new NasaPoly2(200, 3500, 101325, oh_nasa_coeffs)); - sCO->thermo.reset(new NasaPoly2(200, 3500, 101325, o2_nasa_coeffs)); sCO2->thermo.reset(new ShomatePoly2(200, 3500, 101325, co2_shomate_coeffs)); } @@ -268,13 +273,42 @@ TEST(PureFluidFromScratch, CarbonDioxide) TEST(WaterSSTP, fromScratch) { WaterSSTP water; - auto sH2O = make_shared("H2O", parseCompString("H:2 O:1")); - sH2O->thermo.reset(new NasaPoly2(200, 3500, 101325, h2o_nasa_coeffs)); // unused water.addUndefinedElements(); - water.addSpecies(sH2O); + water.addSpecies(make_species("H2O", "H:2, O:1", h2o_nasa_coeffs)); water.initThermo(); water.setState_TP(298.15, 1e5); EXPECT_NEAR(water.enthalpy_mole() / 1e6, -285.83, 2e-2); } +TEST(IdealMolalSoln, fromScratch) +{ + IdealMolalSoln p; + p.addUndefinedElements(); + p.addSpecies(make_species("H2O(l)", "H:2, O:1", h2_nasa_coeffs)); + p.addSpecies(make_species("CO2(aq)", "C:1, O:2", h2_nasa_coeffs)); + p.addSpecies(make_species("H2S(aq)", "H:2, S:1", h2_nasa_coeffs)); + p.addSpecies(make_species("CH4(aq)", "C:1, H:4", h2_nasa_coeffs)); + size_t k = 0; + for (double v : {1.5, 1.3, 0.1, 0.1}) { + std::unique_ptr ss(new PDSS_ConstVol()); + ss->setMolarVolume(v); + p.installPDSS(k++, std::move(ss)); + } + p.setStandardConcentrationModel("solvent_volume"); + p.setCutoffModel("polyexp"); + // These propreties probably shouldn't be public + p.IMS_X_o_cutoff_ = 0.20; + p.IMS_gamma_o_min_ = 0.00001; + p.IMS_gamma_k_min_ = 10.0; + p.IMS_slopefCut_ = 0.6; + p.IMS_slopegCut_ = 0.0; + p.IMS_cCut_ = .05; + p.initThermo(); + p.setState_TPM(298.15, OneAtm, "CH4(aq):0.01, H2S(aq):0.03, CO2(aq):0.1"); + + EXPECT_NEAR(p.enthalpy_mole(), 0.013282, 1e-6); + EXPECT_NEAR(p.gibbs_mole(), -3.8986e7, 1e3); + EXPECT_NEAR(p.density(), 12.058, 1e-3); +} + } // namespace Cantera