CanteraError inerits from std:exception, so now it has a what() method that is used to print a message describing the exception. Adding an exception to the Cantera error stack now requires explicitly calling the .save() method.
1094 lines
32 KiB
C++
1094 lines
32 KiB
C++
/**
|
|
* @file MolalityVPSSTP.cpp
|
|
* Definitions for intermediate ThermoPhase object for phases which
|
|
* employ molality based activity coefficient formulations
|
|
* (see \ref thermoprops
|
|
* and class \link Cantera::MolalityVPSSTP MolalityVPSSTP\endlink).
|
|
*
|
|
* Header file for a derived class of ThermoPhase that handles
|
|
* variable pressure standard state methods for calculating
|
|
* thermodynamic properties that are further based upon activities
|
|
* based on the molality scale. These include most of the methods for
|
|
* calculating liquid electrolyte thermodynamics.
|
|
*/
|
|
/*
|
|
* Copyright (2005) Sandia Corporation. Under the terms of
|
|
* Contract DE-AC04-94AL85000 with Sandia Corporation, the
|
|
* U.S. Government retains certain rights in this software.
|
|
*/
|
|
#include "cantera/thermo/MolalityVPSSTP.h"
|
|
#include <iomanip>
|
|
using namespace std;
|
|
|
|
namespace Cantera
|
|
{
|
|
|
|
/*
|
|
* Default constructor.
|
|
*
|
|
* This doesn't do much more than initialize constants with
|
|
* default values for water at 25C. Water molecular weight
|
|
* comes from the default elements.xml file. It actually
|
|
* differs slightly from the IAPWS95 value of 18.015268. However,
|
|
* density conservation and therefore element conservation
|
|
* is the more important principle to follow.
|
|
*/
|
|
MolalityVPSSTP::MolalityVPSSTP() :
|
|
VPStandardStateTP(),
|
|
m_indexSolvent(0),
|
|
m_pHScalingType(PHSCALE_PITZER),
|
|
m_indexCLM(-1),
|
|
m_weightSolvent(18.01528),
|
|
m_xmolSolventMIN(0.01),
|
|
m_Mnaught(18.01528E-3)
|
|
{
|
|
/*
|
|
* Change the default to be that charge neutrality in the
|
|
* phase is necessary condition for the proper specification
|
|
* of thermodynamic functions within the phase
|
|
*/
|
|
m_chargeNeutralityNecessary = true;
|
|
}
|
|
|
|
/*
|
|
* Copy Constructor:
|
|
*
|
|
* Note this stuff will not work until the underlying phase
|
|
* has a working copy constructor
|
|
*/
|
|
MolalityVPSSTP::MolalityVPSSTP(const MolalityVPSSTP& b) :
|
|
VPStandardStateTP(),
|
|
m_indexSolvent(b.m_indexSolvent),
|
|
m_pHScalingType(b.m_pHScalingType),
|
|
m_indexCLM(b.m_indexCLM),
|
|
m_xmolSolventMIN(b.m_xmolSolventMIN),
|
|
m_Mnaught(b.m_Mnaught),
|
|
m_molalities(b.m_molalities)
|
|
{
|
|
*this = operator=(b);
|
|
}
|
|
|
|
/*
|
|
* operator=()
|
|
*
|
|
* Note this stuff will not work until the underlying phase
|
|
* has a working assignment operator
|
|
*/
|
|
MolalityVPSSTP& MolalityVPSSTP::
|
|
operator=(const MolalityVPSSTP& b)
|
|
{
|
|
if (&b != this) {
|
|
VPStandardStateTP::operator=(b);
|
|
m_indexSolvent = b.m_indexSolvent;
|
|
m_pHScalingType = b.m_pHScalingType;
|
|
m_indexCLM = b.m_indexCLM;
|
|
m_weightSolvent = b.m_weightSolvent;
|
|
m_xmolSolventMIN = b.m_xmolSolventMIN;
|
|
m_Mnaught = b.m_Mnaught;
|
|
m_molalities = b.m_molalities;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* ~MolalityVPSSTP(): (virtual)
|
|
*
|
|
* Destructor: does nothing:
|
|
*
|
|
*/
|
|
MolalityVPSSTP::~MolalityVPSSTP()
|
|
{
|
|
}
|
|
|
|
/*
|
|
* This routine duplicates the current object and returns
|
|
* a pointer to ThermoPhase.
|
|
*/
|
|
ThermoPhase*
|
|
MolalityVPSSTP::duplMyselfAsThermoPhase() const
|
|
{
|
|
MolalityVPSSTP* mtp = new MolalityVPSSTP(*this);
|
|
return (ThermoPhase*) mtp;
|
|
}
|
|
|
|
/*
|
|
* -------------- Utilities -------------------------------
|
|
*/
|
|
|
|
// Equation of state type flag.
|
|
/*
|
|
* The ThermoPhase base class returns
|
|
* zero. Subclasses should define this to return a unique
|
|
* non-zero value. Known constants defined for this purpose are
|
|
* listed in mix_defs.h. The MolalityVPSSTP class also returns
|
|
* zero, as it is a non-complete class.
|
|
*/
|
|
int MolalityVPSSTP::eosType() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Set the pH scale, which determines the scale for single-ion activity
|
|
// coefficients.
|
|
/*
|
|
* Single ion activity coefficients are not unique in terms of the
|
|
* representing actual measurable quantities.
|
|
*/
|
|
void MolalityVPSSTP::setpHScale(const int pHscaleType)
|
|
{
|
|
m_pHScalingType = pHscaleType;
|
|
if (pHscaleType != PHSCALE_PITZER && pHscaleType != PHSCALE_NBS) {
|
|
throw CanteraError("MolalityVPSSTP::setpHScale",
|
|
"Unknown scale type: " + int2str(pHscaleType));
|
|
}
|
|
}
|
|
|
|
// Reports the pH scale, which determines the scale for single-ion activity
|
|
// coefficients.
|
|
/*
|
|
* Single ion activity coefficients are not unique in terms of the
|
|
* representing actual measurable quantities.
|
|
*/
|
|
int MolalityVPSSTP::pHScale() const
|
|
{
|
|
return m_pHScalingType;
|
|
}
|
|
|
|
/*
|
|
* setSolvent():
|
|
* Utilities for Solvent ID and Molality
|
|
* Here we also calculate and store the molecular weight
|
|
* of the solvent and the m_Mnaught parameter.
|
|
* @param k index of the solvent.
|
|
*/
|
|
void MolalityVPSSTP::setSolvent(size_t k)
|
|
{
|
|
if (k >= m_kk) {
|
|
throw CanteraError("MolalityVPSSTP::setSolute ",
|
|
"bad value");
|
|
}
|
|
m_indexSolvent = k;
|
|
AssertThrowMsg(m_indexSolvent==0, "MolalityVPSSTP::setSolvent",
|
|
"Molality-based methods limit solvent id to being 0");
|
|
m_weightSolvent = molecularWeight(k);
|
|
m_Mnaught = m_weightSolvent / 1000.;
|
|
}
|
|
|
|
/*
|
|
* return the solvent id index number.
|
|
*/
|
|
size_t MolalityVPSSTP::solventIndex() const
|
|
{
|
|
return m_indexSolvent;
|
|
}
|
|
|
|
/*
|
|
* Sets the minimum mole fraction in the molality formulation. The
|
|
* minimum mole fraction must be in the range 0 to 0.9.
|
|
*/
|
|
void MolalityVPSSTP::
|
|
setMoleFSolventMin(doublereal xmolSolventMIN)
|
|
{
|
|
if (xmolSolventMIN <= 0.0) {
|
|
throw CanteraError("MolalityVPSSTP::setSolute ", "trouble");
|
|
} else if (xmolSolventMIN > 0.9) {
|
|
throw CanteraError("MolalityVPSSTP::setSolute ", "trouble");
|
|
}
|
|
m_xmolSolventMIN = xmolSolventMIN;
|
|
}
|
|
|
|
/**
|
|
* Returns the minimum mole fraction in the molality formulation.
|
|
*/
|
|
doublereal MolalityVPSSTP::moleFSolventMin() const
|
|
{
|
|
return m_xmolSolventMIN;
|
|
}
|
|
|
|
/*
|
|
* calcMolalities():
|
|
* We calculate the vector of molalities of the species
|
|
* in the phase and store the result internally:
|
|
* \f[
|
|
* m_i = (n_i) / (1000 * M_o * n_{o,p})
|
|
* \f]
|
|
* where
|
|
* - \f$ M_o \f$ is the molecular weight of the solvent
|
|
* - \f$ n_o \f$ is the mole fraction of the solvent
|
|
* - \f$ n_i \f$ is the mole fraction of the solute.
|
|
* - \f$ n_{o,p} = max (n_{o, min}, n_o) \f$
|
|
* - \f$ n_{o,min} \f$ = minimum mole fraction of solvent allowed
|
|
* in the denominator.
|
|
*/
|
|
void MolalityVPSSTP::calcMolalities() const
|
|
{
|
|
getMoleFractions(DATA_PTR(m_molalities));
|
|
double xmolSolvent = m_molalities[m_indexSolvent];
|
|
if (xmolSolvent < m_xmolSolventMIN) {
|
|
xmolSolvent = m_xmolSolventMIN;
|
|
}
|
|
double denomInv = 1.0/ (m_Mnaught * xmolSolvent);
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
m_molalities[k] *= denomInv;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* getMolalities():
|
|
* We calculate the vector of molalities of the species
|
|
* in the phase
|
|
* \f[
|
|
* m_i = (n_i) / (1000 * M_o * n_{o,p})
|
|
* \f]
|
|
* where
|
|
* - \f$ M_o \f$ is the molecular weight of the solvent
|
|
* - \f$ n_o \f$ is the mole fraction of the solvent
|
|
* - \f$ n_i \f$ is the mole fraction of the solute.
|
|
* - \f$ n_{o,p} = max (n_{o, min}, n_o) \f$
|
|
* - \f$ n_{o,min} \f$ = minimum mole fraction of solvent allowed
|
|
* in the denominator.
|
|
*/
|
|
void MolalityVPSSTP::getMolalities(doublereal* const molal) const
|
|
{
|
|
calcMolalities();
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
molal[k] = m_molalities[k];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* setMolalities():
|
|
* We are supplied with the molalities of all of the
|
|
* solute species. We then calculate the mole fractions of all
|
|
* species and update the ThermoPhase object.
|
|
*
|
|
* m_i = (n_i) / (W_o/1000 * n_o_p)
|
|
*
|
|
* where M_o is the molecular weight of the solvent
|
|
* n_o is the mole fraction of the solvent
|
|
* n_i is the mole fraction of the solute.
|
|
* n_o_p = max (n_o_min, n_o)
|
|
* n_o_min = minimum mole fraction of solvent allowed
|
|
* in the denominator.
|
|
*/
|
|
void MolalityVPSSTP::setMolalities(const doublereal* const molal)
|
|
{
|
|
|
|
double Lsum = 1.0 / m_Mnaught;
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
m_molalities[k] = molal[k];
|
|
Lsum += molal[k];
|
|
}
|
|
double tmp = 1.0 / Lsum;
|
|
m_molalities[m_indexSolvent] = tmp / m_Mnaught;
|
|
double sum = m_molalities[m_indexSolvent];
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
m_molalities[k] = tmp * molal[k];
|
|
sum += m_molalities[k];
|
|
}
|
|
if (sum != 1.0) {
|
|
tmp = 1.0 / sum;
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
m_molalities[k] *= tmp;
|
|
}
|
|
}
|
|
setMoleFractions(DATA_PTR(m_molalities));
|
|
/*
|
|
* Essentially we don't trust the input: We calculate
|
|
* the molalities from the mole fractions that we
|
|
* just obtained.
|
|
*/
|
|
calcMolalities();
|
|
}
|
|
|
|
/*
|
|
* setMolalitiesByName()
|
|
*
|
|
* This routine sets the molalities by name
|
|
* HKM -> Might need to be more complicated here, setting
|
|
* neutrals so that the existing mole fractions are
|
|
* preserved.
|
|
*/
|
|
void MolalityVPSSTP::setMolalitiesByName(compositionMap& mMap)
|
|
{
|
|
size_t kk = nSpecies();
|
|
doublereal x;
|
|
/*
|
|
* Get a vector of mole fractions
|
|
*/
|
|
vector_fp mf(kk, 0.0);
|
|
getMoleFractions(DATA_PTR(mf));
|
|
double xmolS = mf[m_indexSolvent];
|
|
double xmolSmin = std::max(xmolS, m_xmolSolventMIN);
|
|
compositionMap::iterator p;
|
|
for (size_t k = 0; k < kk; k++) {
|
|
p = mMap.find(speciesName(k));
|
|
if (p != mMap.end()) {
|
|
x = mMap[speciesName(k)];
|
|
if (x > 0.0) {
|
|
mf[k] = x * m_Mnaught * xmolSmin;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* check charge neutrality
|
|
*/
|
|
size_t largePos = -1;
|
|
double cPos = 0.0;
|
|
size_t largeNeg = -1;
|
|
double cNeg = 0.0;
|
|
double sum = 0.0;
|
|
for (size_t k = 0; k < kk; k++) {
|
|
double ch = charge(k);
|
|
if (mf[k] > 0.0) {
|
|
if (ch > 0.0) {
|
|
if (ch * mf[k] > cPos) {
|
|
largePos = k;
|
|
cPos = ch * mf[k];
|
|
}
|
|
}
|
|
if (ch < 0.0) {
|
|
if (fabs(ch) * mf[k] > cNeg) {
|
|
largeNeg = k;
|
|
cNeg = fabs(ch) * mf[k];
|
|
}
|
|
}
|
|
}
|
|
sum += mf[k] * ch;
|
|
}
|
|
if (sum != 0.0) {
|
|
if (sum > 0.0) {
|
|
if (cPos > sum) {
|
|
mf[largePos] -= sum / charge(largePos);
|
|
} else {
|
|
throw CanteraError("MolalityVPSSTP:setMolalitiesbyName",
|
|
"unbalanced charges");
|
|
}
|
|
} else {
|
|
if (cNeg > (-sum)) {
|
|
mf[largeNeg] -= (-sum) / fabs(charge(largeNeg));
|
|
} else {
|
|
throw CanteraError("MolalityVPSSTP:setMolalitiesbyName",
|
|
"unbalanced charges");
|
|
}
|
|
}
|
|
|
|
}
|
|
sum = 0.0;
|
|
for (size_t k = 0; k < kk; k++) {
|
|
sum += mf[k];
|
|
}
|
|
sum = 1.0/sum;
|
|
for (size_t k = 0; k < kk; k++) {
|
|
mf[k] *= sum;
|
|
}
|
|
setMoleFractions(DATA_PTR(mf));
|
|
/*
|
|
* After we formally set the mole fractions, we
|
|
* calculate the molalities again and store it in
|
|
* this object.
|
|
*/
|
|
calcMolalities();
|
|
}
|
|
|
|
/*
|
|
* setMolalitiesByNames()
|
|
*
|
|
* Set the molalities of the solutes by name
|
|
*/
|
|
void MolalityVPSSTP::setMolalitiesByName(const std::string& x)
|
|
{
|
|
compositionMap xx;
|
|
for (size_t k = 0; k < nSpecies(); k++) {
|
|
xx[speciesName(k)] = -1.0;
|
|
}
|
|
parseCompString(x, xx);
|
|
setMolalitiesByName(xx);
|
|
}
|
|
|
|
|
|
/*
|
|
* ------------ Molar Thermodynamic Properties ----------------------
|
|
*/
|
|
|
|
|
|
/*
|
|
* - Activities, Standard States, Activity Concentrations -----------
|
|
*/
|
|
|
|
/*
|
|
* This method returns the activity convention.
|
|
* Currently, there are two activity conventions
|
|
* Molar-based activities
|
|
* Unit activity of species at either a hypothetical pure
|
|
* solution of the species or at a hypothetical
|
|
* pure ideal solution at infinite dilution
|
|
* cAC_CONVENTION_MOLAR 0
|
|
* - default
|
|
*
|
|
* Molality based activities
|
|
* (unit activity of solutes at a hypothetical 1 molal
|
|
* solution referenced to infinite dilution at all
|
|
* pressures and temperatures).
|
|
* (solvent is still on molar basis).
|
|
* cAC_CONVENTION_MOLALITY 1
|
|
*
|
|
* We set the convention to molality here.
|
|
*/
|
|
int MolalityVPSSTP::activityConvention() const
|
|
{
|
|
return cAC_CONVENTION_MOLALITY;
|
|
}
|
|
|
|
void MolalityVPSSTP::getActivityConcentrations(doublereal* c) const
|
|
{
|
|
err("getActivityConcentrations");
|
|
}
|
|
|
|
doublereal MolalityVPSSTP::standardConcentration(size_t k) const
|
|
{
|
|
err("standardConcentration");
|
|
return -1.0;
|
|
}
|
|
|
|
doublereal MolalityVPSSTP::logStandardConc(size_t k) const
|
|
{
|
|
err("logStandardConc");
|
|
return -1.0;
|
|
}
|
|
|
|
void MolalityVPSSTP::getActivities(doublereal* ac) const
|
|
{
|
|
err("getActivities");
|
|
}
|
|
|
|
/*
|
|
* Get the array of non-dimensional activity coefficients at
|
|
* the current solution temperature, pressure, and
|
|
* solution concentration.
|
|
* These are mole fraction based activity coefficients. In this
|
|
* object, their calculation is based on translating the values
|
|
* of Molality based activity coefficients.
|
|
* See Denbigh p. 278 for a thorough discussion.
|
|
*
|
|
* Note, the solvent is treated differently. getMolalityActivityCoeff()
|
|
* returns the molar based solvent activity coefficient already.
|
|
* Therefore, we do not have to divide by x_s here.
|
|
*/
|
|
void MolalityVPSSTP::getActivityCoefficients(doublereal* ac) const
|
|
{
|
|
getMolalityActivityCoefficients(ac);
|
|
AssertThrow(m_indexSolvent==0, "MolalityVPSSTP::getActivityCoefficients");
|
|
double xmolSolvent = moleFraction(m_indexSolvent);
|
|
if (xmolSolvent < m_xmolSolventMIN) {
|
|
xmolSolvent = m_xmolSolventMIN;
|
|
}
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
ac[k] /= xmolSolvent;
|
|
}
|
|
}
|
|
|
|
// Get the array of non-dimensional molality based
|
|
// activity coefficients at the current solution temperature,
|
|
// pressure, and solution concentration.
|
|
/*
|
|
* See Denbigh p. 278 for a thorough discussion. This class must be overwritten in
|
|
* classes which derive from %MolalityVPSSTP. This function takes over from the
|
|
* molar-based activity coefficient calculation, getActivityCoefficients(), in
|
|
* derived classes.
|
|
*
|
|
* Note these activity coefficients have the current pH scale applied to them.
|
|
*
|
|
* @param acMolality Output vector containing the molality based activity coefficients.
|
|
* length: m_kk.
|
|
*/
|
|
void MolalityVPSSTP::getMolalityActivityCoefficients(doublereal* acMolality) const
|
|
{
|
|
getUnscaledMolalityActivityCoefficients(acMolality);
|
|
applyphScale(acMolality);
|
|
}
|
|
|
|
/*
|
|
* osmotic coefficient:
|
|
*
|
|
* Calculate the osmotic coefficient of the solvent. Note there
|
|
* are lots of definitions of the osmotic coefficient floating
|
|
* around. We use the one defined in the Pitzer's book:
|
|
* (Activity Coeff in Electrolyte Solutions, K. S. Pitzer
|
|
* CRC Press, Boca Raton, 1991, p. 85, Eqn. 28).
|
|
*
|
|
* Definition:
|
|
* - sum(m_i) * Mnaught * oc = ln(activity_solvent)
|
|
*/
|
|
doublereal MolalityVPSSTP::osmoticCoefficient() const
|
|
{
|
|
/*
|
|
* First, we calculate the activities all over again
|
|
*/
|
|
vector_fp act(m_kk);
|
|
getActivities(DATA_PTR(act));
|
|
/*
|
|
* Then, we calculate the sum of the solvent molalities
|
|
*/
|
|
double sum = 0;
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
sum += std::max(m_molalities[k], 0.0);
|
|
}
|
|
double oc = 1.0;
|
|
double lac = log(act[m_indexSolvent]);
|
|
if (sum > 1.0E-200) {
|
|
oc = - lac / (m_Mnaught * sum);
|
|
}
|
|
return oc;
|
|
}
|
|
|
|
|
|
void MolalityVPSSTP::getElectrochemPotentials(doublereal* mu) const
|
|
{
|
|
getChemPotentials(mu);
|
|
double ve = Faraday * electricPotential();
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
mu[k] += ve*charge(k);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ------------ Partial Molar Properties of the Solution ------------
|
|
*/
|
|
|
|
|
|
doublereal MolalityVPSSTP::err(std::string msg) const
|
|
{
|
|
throw CanteraError("MolalityVPSSTP","Base class method "
|
|
+msg+" called. Equation of state type: "+int2str(eosType()));
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Returns the units of the standard and general concentrations
|
|
* Note they have the same units, as their divisor is
|
|
* defined to be equal to the activity of the kth species
|
|
* in the solution, which is unitless.
|
|
*
|
|
* This routine is used in print out applications where the
|
|
* units are needed. Usually, MKS units are assumed throughout
|
|
* the program and in the XML input files.
|
|
*
|
|
* On return uA contains the powers of the units (MKS assumed)
|
|
* of the standard concentrations and generalized concentrations
|
|
* for the kth species.
|
|
*
|
|
* uA[0] = kmol units - default = 1
|
|
* uA[1] = m units - default = -nDim(), the number of spatial
|
|
* dimensions in the Phase class.
|
|
* uA[2] = kg units - default = 0;
|
|
* uA[3] = Pa(pressure) units - default = 0;
|
|
* uA[4] = Temperature units - default = 0;
|
|
* uA[5] = time units - default = 0
|
|
*/
|
|
void MolalityVPSSTP::getUnitsStandardConc(double* uA, int k, int sizeUA) const
|
|
{
|
|
for (int i = 0; i < sizeUA; i++) {
|
|
if (i == 0) {
|
|
uA[0] = 1.0;
|
|
}
|
|
if (i == 1) {
|
|
uA[1] = -int(nDim());
|
|
}
|
|
if (i == 2) {
|
|
uA[2] = 0.0;
|
|
}
|
|
if (i == 3) {
|
|
uA[3] = 0.0;
|
|
}
|
|
if (i == 4) {
|
|
uA[4] = 0.0;
|
|
}
|
|
if (i == 5) {
|
|
uA[5] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MolalityVPSSTP::setToEquilState(const doublereal* lambda_RT)
|
|
{
|
|
updateStandardStateThermo();
|
|
err("setToEquilState");
|
|
}
|
|
|
|
/*
|
|
* Set the thermodynamic state.
|
|
*/
|
|
void MolalityVPSSTP::setStateFromXML(const XML_Node& state)
|
|
{
|
|
VPStandardStateTP::setStateFromXML(state);
|
|
string comp = ctml::getChildValue(state,"soluteMolalities");
|
|
if (comp != "") {
|
|
setMolalitiesByName(comp);
|
|
}
|
|
if (state.hasChild("pressure")) {
|
|
double p = ctml::getFloat(state, "pressure", "pressure");
|
|
setPressure(p);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set the temperature (K), pressure (Pa), and molalities
|
|
* (gmol kg-1) of the solutes
|
|
*/
|
|
void MolalityVPSSTP::setState_TPM(doublereal t, doublereal p,
|
|
const doublereal* const molalities)
|
|
{
|
|
setMolalities(molalities);
|
|
setState_TP(t, p);
|
|
}
|
|
|
|
/*
|
|
* Set the temperature (K), pressure (Pa), and molalities.
|
|
*/
|
|
void MolalityVPSSTP::setState_TPM(doublereal t, doublereal p, compositionMap& m)
|
|
{
|
|
setMolalitiesByName(m);
|
|
setState_TP(t, p);
|
|
}
|
|
|
|
/*
|
|
* Set the temperature (K), pressure (Pa), and molality.
|
|
*/
|
|
void MolalityVPSSTP::setState_TPM(doublereal t, doublereal p, const std::string& m)
|
|
{
|
|
setMolalitiesByName(m);
|
|
setState_TP(t, p);
|
|
}
|
|
|
|
|
|
/*
|
|
* @internal Initialize. This method is provided to allow
|
|
* subclasses to perform any initialization required after all
|
|
* species have been added. For example, it might be used to
|
|
* resize internal work arrays that must have an entry for
|
|
* each species. The base class implementation does nothing,
|
|
* and subclasses that do not require initialization do not
|
|
* need to overload this method. When importing a CTML phase
|
|
* description, this method is called just prior to returning
|
|
* from function importPhase.
|
|
*
|
|
* @see importCTML.cpp
|
|
*/
|
|
void MolalityVPSSTP::initThermo()
|
|
{
|
|
initLengths();
|
|
VPStandardStateTP::initThermo();
|
|
|
|
/*
|
|
* The solvent defaults to species 0
|
|
*/
|
|
setSolvent(0);
|
|
/*
|
|
* Find the Cl- species
|
|
*/
|
|
m_indexCLM = findCLMIndex();
|
|
}
|
|
|
|
// Get the array of unscaled non-dimensional molality based
|
|
// activity coefficients at the current solution temperature,
|
|
// pressure, and solution concentration.
|
|
/*
|
|
* See Denbigh p. 278 for a thorough discussion. This class must be overwritten in
|
|
* classes which derive from %MolalityVPSSTP. This function takes over from the
|
|
* molar-based activity coefficient calculation, getActivityCoefficients(), in
|
|
* derived classes.
|
|
*
|
|
* @param acMolality Output vector containing the molality based activity coefficients.
|
|
* length: m_kk.
|
|
*/
|
|
void MolalityVPSSTP::getUnscaledMolalityActivityCoefficients(doublereal* acMolality) const
|
|
{
|
|
err("getUnscaledMolalityActivityCoefficients");
|
|
}
|
|
|
|
// Apply the current phScale to a set of activity Coefficients or activities
|
|
/*
|
|
* See the Eq3/6 Manual for a thorough discussion.
|
|
*
|
|
* @param acMolality input/Output vector containing the molality based
|
|
* activity coefficients. length: m_kk.
|
|
*/
|
|
void MolalityVPSSTP::applyphScale(doublereal* acMolality) const
|
|
{
|
|
err("applyphScale");
|
|
}
|
|
|
|
// Returns the index of the Cl- species.
|
|
/*
|
|
* The Cl- species is special in the sense that its single ion
|
|
* molality-based activity coefficient is used in the specification
|
|
* of the pH scale for single ions. Therefore, we need to know
|
|
* what species index Cl- is. If the species isn't in the species
|
|
* list then this routine returns -1, and we can't use the NBS
|
|
* pH scale.
|
|
*
|
|
* Right now we use a restrictive interpretation. The species
|
|
* must be named "Cl-". It must consist of exactly one Cl and one E
|
|
* atom.
|
|
*/
|
|
size_t MolalityVPSSTP::findCLMIndex() const
|
|
{
|
|
size_t indexCLM = -1;
|
|
size_t eCl = -1;
|
|
size_t eE = -1;
|
|
size_t ne = nElements();
|
|
string sn;
|
|
for (size_t e = 0; e < ne; e++) {
|
|
sn = elementName(e);
|
|
if (sn == "Cl" || sn == "CL") {
|
|
eCl = e;
|
|
break;
|
|
}
|
|
}
|
|
// We have failed if we can't find the Cl element index
|
|
if (eCl == npos) {
|
|
return -1;
|
|
}
|
|
for (size_t e = 0; e < ne; e++) {
|
|
sn = elementName(e);
|
|
if (sn == "E" || sn == "e") {
|
|
eE = e;
|
|
break;
|
|
}
|
|
}
|
|
// We have failed if we can't find the E element index
|
|
if (eE == npos) {
|
|
return npos;
|
|
}
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
doublereal nCl = nAtoms(k, eCl);
|
|
if (nCl != 1.0) {
|
|
continue;
|
|
}
|
|
doublereal nE = nAtoms(k, eE);
|
|
if (nE != 1.0) {
|
|
continue;
|
|
}
|
|
for (size_t e = 0; e < ne; e++) {
|
|
if (e != eE && e != eCl) {
|
|
doublereal nA = nAtoms(k, e);
|
|
if (nA != 0.0) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
sn = speciesName(k);
|
|
if (sn != "Cl-" && sn != "CL-") {
|
|
continue;
|
|
}
|
|
|
|
indexCLM = k;
|
|
break;
|
|
}
|
|
return indexCLM;
|
|
}
|
|
|
|
// Initialize lengths of local variables after all species have
|
|
// been identified.
|
|
void MolalityVPSSTP::initLengths()
|
|
{
|
|
m_kk = nSpecies();
|
|
m_molalities.resize(m_kk);
|
|
}
|
|
|
|
/*
|
|
* initThermoXML() (virtual from ThermoPhase)
|
|
* Import and initialize a ThermoPhase object
|
|
*
|
|
* @param phaseNode This object must be the phase node of a
|
|
* complete XML tree
|
|
* description of the phase, including all of the
|
|
* species data. In other words while "phase" must
|
|
* point to an XML phase object, it must have
|
|
* sibling nodes "speciesData" that describe
|
|
* the species in the phase.
|
|
* @param id ID of the phase. If nonnull, a check is done
|
|
* to see if phaseNode is pointing to the phase
|
|
* with the correct id.
|
|
*/
|
|
void MolalityVPSSTP::initThermoXML(XML_Node& phaseNode, std::string id)
|
|
{
|
|
|
|
initLengths();
|
|
/*
|
|
* The solvent defaults to species 0
|
|
*/
|
|
setSolvent(0);
|
|
|
|
VPStandardStateTP::initThermoXML(phaseNode, id);
|
|
}
|
|
|
|
/**
|
|
* Format a summary of the mixture state for output.
|
|
*/
|
|
std::string MolalityVPSSTP::report(bool show_thermo) const
|
|
{
|
|
|
|
|
|
char p[800];
|
|
string s = "";
|
|
try {
|
|
if (name() != "") {
|
|
sprintf(p, " \n %s:\n", name().c_str());
|
|
s += p;
|
|
}
|
|
sprintf(p, " \n temperature %12.6g K\n", temperature());
|
|
s += p;
|
|
sprintf(p, " pressure %12.6g Pa\n", pressure());
|
|
s += p;
|
|
sprintf(p, " density %12.6g kg/m^3\n", density());
|
|
s += p;
|
|
sprintf(p, " mean mol. weight %12.6g amu\n", meanMolecularWeight());
|
|
s += p;
|
|
|
|
doublereal phi = electricPotential();
|
|
sprintf(p, " potential %12.6g V\n", phi);
|
|
s += p;
|
|
|
|
size_t kk = nSpecies();
|
|
vector_fp x(kk);
|
|
vector_fp molal(kk);
|
|
vector_fp mu(kk);
|
|
vector_fp muss(kk);
|
|
vector_fp acMolal(kk);
|
|
vector_fp actMolal(kk);
|
|
getMoleFractions(&x[0]);
|
|
getMolalities(&molal[0]);
|
|
getChemPotentials(&mu[0]);
|
|
getStandardChemPotentials(&muss[0]);
|
|
getMolalityActivityCoefficients(&acMolal[0]);
|
|
getActivities(&actMolal[0]);
|
|
|
|
size_t iHp = speciesIndex("H+");
|
|
if (iHp != npos) {
|
|
double pH = -log(actMolal[iHp]) / log(10.0);
|
|
sprintf(p, " pH %12.4g \n", pH);
|
|
s += p;
|
|
}
|
|
|
|
if (show_thermo) {
|
|
sprintf(p, " \n");
|
|
s += p;
|
|
sprintf(p, " 1 kg 1 kmol\n");
|
|
s += p;
|
|
sprintf(p, " ----------- ------------\n");
|
|
s += p;
|
|
sprintf(p, " enthalpy %12.6g %12.4g J\n",
|
|
enthalpy_mass(), enthalpy_mole());
|
|
s += p;
|
|
sprintf(p, " internal energy %12.6g %12.4g J\n",
|
|
intEnergy_mass(), intEnergy_mole());
|
|
s += p;
|
|
sprintf(p, " entropy %12.6g %12.4g J/K\n",
|
|
entropy_mass(), entropy_mole());
|
|
s += p;
|
|
sprintf(p, " Gibbs function %12.6g %12.4g J\n",
|
|
gibbs_mass(), gibbs_mole());
|
|
s += p;
|
|
sprintf(p, " heat capacity c_p %12.6g %12.4g J/K\n",
|
|
cp_mass(), cp_mole());
|
|
s += p;
|
|
try {
|
|
sprintf(p, " heat capacity c_v %12.6g %12.4g J/K\n",
|
|
cv_mass(), cv_mole());
|
|
s += p;
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
sprintf(p, " heat capacity c_v <not implemented> \n");
|
|
s += p;
|
|
}
|
|
}
|
|
|
|
sprintf(p, " \n");
|
|
s += p;
|
|
if (show_thermo) {
|
|
sprintf(p, " X "
|
|
" Molalities Chem.Pot. ChemPotSS ActCoeffMolal\n");
|
|
s += p;
|
|
sprintf(p, " "
|
|
" (J/kmol) (J/kmol) \n");
|
|
s += p;
|
|
sprintf(p, " ------------- "
|
|
" ------------ ------------ ------------ ------------\n");
|
|
s += p;
|
|
for (size_t k = 0; k < kk; k++) {
|
|
if (x[k] > SmallNumber) {
|
|
sprintf(p, "%18s %12.6g %12.6g %12.6g %12.6g %12.6g\n",
|
|
speciesName(k).c_str(), x[k], molal[k], mu[k], muss[k], acMolal[k]);
|
|
} else {
|
|
sprintf(p, "%18s %12.6g %12.6g N/A %12.6g %12.6g \n",
|
|
speciesName(k).c_str(), x[k], molal[k], muss[k], acMolal[k]);
|
|
}
|
|
s += p;
|
|
}
|
|
} else {
|
|
sprintf(p, " X"
|
|
"Molalities\n");
|
|
s += p;
|
|
sprintf(p, " -------------"
|
|
" ------------\n");
|
|
s += p;
|
|
for (size_t k = 0; k < kk; k++) {
|
|
sprintf(p, "%18s %12.6g %12.6g\n",
|
|
speciesName(k).c_str(), x[k], molal[k]);
|
|
s += p;
|
|
}
|
|
}
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
* Format a summary of the mixture state for output.
|
|
*/
|
|
void MolalityVPSSTP::reportCSV(std::ofstream& csvFile) const
|
|
{
|
|
|
|
|
|
csvFile.precision(3);
|
|
int tabS = 15;
|
|
int tabM = 30;
|
|
int tabL = 40;
|
|
try {
|
|
if (name() != "") {
|
|
csvFile << "\n"+name()+"\n\n";
|
|
}
|
|
csvFile << setw(tabL) << "temperature (K) =" << setw(tabS) << temperature() << endl;
|
|
csvFile << setw(tabL) << "pressure (Pa) =" << setw(tabS) << pressure() << endl;
|
|
csvFile << setw(tabL) << "density (kg/m^3) =" << setw(tabS) << density() << endl;
|
|
csvFile << setw(tabL) << "mean mol. weight (amu) =" << setw(tabS) << meanMolecularWeight() << endl;
|
|
csvFile << setw(tabL) << "potential (V) =" << setw(tabS) << electricPotential() << endl;
|
|
csvFile << endl;
|
|
|
|
csvFile << setw(tabL) << "enthalpy (J/kg) = " << setw(tabS) << enthalpy_mass() << setw(tabL) << "enthalpy (J/kmol) = " << setw(tabS) << enthalpy_mole() << endl;
|
|
csvFile << setw(tabL) << "internal E (J/kg) = " << setw(tabS) << intEnergy_mass() << setw(tabL) << "internal E (J/kmol) = " << setw(tabS) << intEnergy_mole() << endl;
|
|
csvFile << setw(tabL) << "entropy (J/kg) = " << setw(tabS) << entropy_mass() << setw(tabL) << "entropy (J/kmol) = " << setw(tabS) << entropy_mole() << endl;
|
|
csvFile << setw(tabL) << "Gibbs (J/kg) = " << setw(tabS) << gibbs_mass() << setw(tabL) << "Gibbs (J/kmol) = " << setw(tabS) << gibbs_mole() << endl;
|
|
csvFile << setw(tabL) << "heat capacity c_p (J/K/kg) = " << setw(tabS) << cp_mass() << setw(tabL) << "heat capacity c_p (J/K/kmol) = " << setw(tabS) << cp_mole() << endl;
|
|
csvFile << setw(tabL) << "heat capacity c_v (J/K/kg) = " << setw(tabS) << cv_mass() << setw(tabL) << "heat capacity c_v (J/K/kmol) = " << setw(tabS) << cv_mole() << endl;
|
|
|
|
csvFile.precision(8);
|
|
|
|
vector<std::string> pNames;
|
|
vector<vector_fp> data;
|
|
vector_fp temp(nSpecies());
|
|
|
|
getMoleFractions(&temp[0]);
|
|
pNames.push_back("X");
|
|
data.push_back(temp);
|
|
try {
|
|
getMolalities(&temp[0]);
|
|
pNames.push_back("Molal");
|
|
data.push_back(temp);
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
try {
|
|
getChemPotentials(&temp[0]);
|
|
pNames.push_back("Chem. Pot. (J/kmol)");
|
|
data.push_back(temp);
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
try {
|
|
getStandardChemPotentials(&temp[0]);
|
|
pNames.push_back("Chem. Pot. SS (J/kmol)");
|
|
data.push_back(temp);
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
try {
|
|
getMolalityActivityCoefficients(&temp[0]);
|
|
pNames.push_back("Molal Act. Coeff.");
|
|
data.push_back(temp);
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
try {
|
|
getActivities(&temp[0]);
|
|
pNames.push_back("Molal Activity");
|
|
data.push_back(temp);
|
|
size_t iHp = speciesIndex("H+");
|
|
if (iHp != npos) {
|
|
double pH = -log(temp[iHp]) / log(10.0);
|
|
csvFile << setw(tabL) << "pH = " << setw(tabS) << pH << endl;
|
|
}
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
try {
|
|
getPartialMolarEnthalpies(&temp[0]);
|
|
pNames.push_back("Part. Mol Enthalpy (J/kmol)");
|
|
data.push_back(temp);
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
try {
|
|
getPartialMolarEntropies(&temp[0]);
|
|
pNames.push_back("Part. Mol. Entropy (J/K/kmol)");
|
|
data.push_back(temp);
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
try {
|
|
getPartialMolarIntEnergies(&temp[0]);
|
|
pNames.push_back("Part. Mol. Energy (J/kmol)");
|
|
data.push_back(temp);
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
try {
|
|
getPartialMolarCp(&temp[0]);
|
|
pNames.push_back("Part. Mol. Cp (J/K/kmol");
|
|
data.push_back(temp);
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
try {
|
|
getPartialMolarVolumes(&temp[0]);
|
|
pNames.push_back("Part. Mol. Cv (J/K/kmol)");
|
|
data.push_back(temp);
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
|
|
csvFile << endl << setw(tabS) << "Species,";
|
|
for (size_t i = 0; i < pNames.size(); i++) {
|
|
csvFile << setw(tabM) << pNames[i] << ",";
|
|
}
|
|
csvFile << endl;
|
|
/*
|
|
csvFile.fill('-');
|
|
csvFile << setw(tabS+(tabM+1)*pNames.size()) << "-\n";
|
|
csvFile.fill(' ');
|
|
*/
|
|
for (size_t k = 0; k < nSpecies(); k++) {
|
|
csvFile << setw(tabS) << speciesName(k) + ",";
|
|
if (data[0][k] > SmallNumber) {
|
|
for (size_t i = 0; i < pNames.size(); i++) {
|
|
csvFile << setw(tabM) << data[i][k] << ",";
|
|
}
|
|
csvFile << endl;
|
|
} else {
|
|
for (size_t i = 0; i < pNames.size(); i++) {
|
|
csvFile << setw(tabM) << 0 << ",";
|
|
}
|
|
csvFile << endl;
|
|
}
|
|
}
|
|
} catch (CanteraError& err) {
|
|
err.save();
|
|
}
|
|
}
|
|
|
|
}
|
|
|