/** * @file MaskellSolidSolnPhase.cpp Implementation file for an ideal solid * solution model with incompressible thermodynamics (see \ref * thermoprops and \link Cantera::MaskellSolidSolnPhase * MaskellSolidSolnPhase\endlink). */ /* * Copyright 2006 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/MaskellSolidSolnPhase.h" #include "cantera/base/stringUtils.h" #include "cantera/base/xml.h" #include namespace Cantera { //===================================================================================================== MaskellSolidSolnPhase::MaskellSolidSolnPhase() : m_Pref(OneAtm), m_Pcurrent(OneAtm), m_h0_RT(2), m_cp0_R(2), m_g0_RT(2), m_s0_R(2), h_mixing(0.0), product_species_index(0), reactant_species_index(1) { } //===================================================================================================== MaskellSolidSolnPhase::MaskellSolidSolnPhase(const MaskellSolidSolnPhase& b) : m_Pref(OneAtm), m_Pcurrent(OneAtm), m_h0_RT(2), m_cp0_R(2), m_g0_RT(2), m_s0_R(2), h_mixing(0.0), product_species_index(0), reactant_species_index(1) { *this = b; } //===================================================================================================== MaskellSolidSolnPhase& MaskellSolidSolnPhase::operator=(const MaskellSolidSolnPhase& b) { if (this != &b) { VPStandardStateTP::operator=(b); } return *this; } //===================================================================================================== ThermoPhase* MaskellSolidSolnPhase::duplMyselfAsThermoPhase() const { return new MaskellSolidSolnPhase(*this); } //===================================================================================================== void MaskellSolidSolnPhase::getActivityConcentrations(doublereal* c) const { getActivityCoefficients(c); for(unsigned sp=0; sp < m_kk; ++sp) { c[sp] *= moleFraction(sp); } } /******************************************************************** * Molar Thermodynamic Properties of the Solution ********************************************************************/ //===================================================================================================== doublereal MaskellSolidSolnPhase::enthalpy_mole() const { _updateThermo(); const doublereal h0 = GasConstant * temperature() * mean_X(&m_h0_RT[0]); const doublereal r = moleFraction(product_species_index); const doublereal fmval = fm(r); return h0 + r * fmval * h_mixing; } //===================================================================================================== doublereal xlogx(doublereal x) { return x * std::log(x); } doublereal MaskellSolidSolnPhase::entropy_mole() const { _updateThermo(); const doublereal s0 = GasConstant * mean_X(&m_s0_R[0]); const doublereal r = moleFraction(product_species_index); const doublereal fmval = fm(r); const doublereal rfm = r * fmval; return s0 + GasConstant * (xlogx(1-rfm) - xlogx(rfm) - xlogx(1-r-rfm) - xlogx((1-fmval)*r) - xlogx(1-r) - xlogx(r)); } /******************************************************************** * Mechanical Equation of State ********************************************************************/ void MaskellSolidSolnPhase::setDensity(const doublereal rho) { /* * Unless the input density is exactly equal to the density * calculated and stored in the State object, we throw an * exception. This is because the density is NOT an * independent variable. */ double dens = density(); if (rho != dens) { throw CanteraError("MaskellSolidSolnPhase::setDensity", "Density is not an independent variable"); } } void MaskellSolidSolnPhase::calcDensity() { const vector_fp & vbar = getStandardVolumes(); vector_fp moleFracs(m_kk); Phase::getMoleFractions(&moleFracs[0]); doublereal vtotal = 0.0; for (size_t i = 0; i < m_kk; i++) { vtotal += vbar[i] * moleFracs[i]; } doublereal dd = meanMolecularWeight() / vtotal; Phase::setDensity(dd); } void MaskellSolidSolnPhase::setPressure(doublereal p) { m_Pcurrent = p; } void MaskellSolidSolnPhase::setMolarDensity(const doublereal n) { throw CanteraError("MaskellSolidSolnPhase::setMolarDensity", "Density is not an independent variable"); } /******************************************************************** * Chemical Potentials and Activities ********************************************************************/ void MaskellSolidSolnPhase::getActivityCoefficients(doublereal* ac) const { _updateThermo(); static const int cacheId = m_cache.getId(); CachedArray cached = m_cache.getArray(cacheId); if( !cached.validate(temperature(), pressure(), stateMFNumber()) ) { cached.value.resize(2); const doublereal r = moleFraction(product_species_index); const doublereal pval = p(r); const doublereal fmval = fm(r); const doublereal rfm = r * fmval; const doublereal RT = GasConstant * temperature(); const doublereal A = (std::pow(1 - rfm, pval) * std::pow(rfm, pval) * std::pow(r - rfm, 1 - pval)) / (std::pow(1 - r - rfm, 1 + pval) * (1 - r)); const doublereal B = pval * h_mixing / RT; cached.value[product_species_index] = A * std::exp(B); cached.value[reactant_species_index] = 1 / (A * r * (1-r) ) * std::exp(-B); } std::copy(cached.value.begin(), cached.value.end(), ac); } void MaskellSolidSolnPhase::getChemPotentials(doublereal* mu) const { _updateThermo(); const doublereal r = moleFraction(product_species_index); const doublereal pval = p(r); const doublereal fmval = fm(r); const doublereal rfm = r * fmval; const doublereal RT = GasConstant * temperature(); const doublereal DgbarDr = pval * h_mixing + GasConstant * temperature() * std::log( (std::pow(1 - rfm, pval) * std::pow(rfm, pval) * std::pow(r - rfm, 1 - pval) * r) / (std::pow(1 - r - rfm, 1 + pval) * (1 - r)) ); mu[product_species_index] = RT * m_g0_RT[product_species_index] + DgbarDr; mu[reactant_species_index] = RT * m_g0_RT[reactant_species_index] - DgbarDr; } void MaskellSolidSolnPhase::getChemPotentials_RT(doublereal* mu) const { const doublereal invRT = 1.0 / (GasConstant * temperature()); getChemPotentials(mu); for(unsigned sp=0; sp < m_kk; ++sp) { mu[sp] *= invRT; } } /******************************************************************** * Partial Molar Properties ********************************************************************/ void MaskellSolidSolnPhase::getPartialMolarEnthalpies(doublereal* hbar) const { throw CanteraError("MaskellSolidSolnPhase::getPartialMolarEnthalpies()", "Not yet implemented."); } void MaskellSolidSolnPhase::getPartialMolarEntropies(doublereal* sbar) const { throw CanteraError("MaskellSolidSolnPhase::getPartialMolarEntropies()", "Not yet implemented."); } void MaskellSolidSolnPhase::getPartialMolarCp(doublereal* cpbar) const { throw CanteraError("MaskellSolidSolnPhase::getPartialMolarCp()", "Not yet implemented."); } void MaskellSolidSolnPhase::getPartialMolarVolumes(doublereal* vbar) const { getStandardVolumes(vbar); } void MaskellSolidSolnPhase::getPureGibbs(doublereal* gpure) const { _updateThermo(); const doublereal RT = GasConstant * temperature(); for(unsigned sp=0; sp < m_kk; ++sp) { gpure[sp] = RT * m_g0_RT[sp]; } } void MaskellSolidSolnPhase::getStandardChemPotentials(doublereal* mu) const { // What is the difference between this and getPureGibbs? IdealSolidSolnPhase gives the same for both getPureGibbs(mu); } /********************************************************************* * Utility Functions *********************************************************************/ void MaskellSolidSolnPhase::initThermoXML(XML_Node& phaseNode, const std::string& id_) { std::string subname = "MaskellSolidSolnPhase::initThermoXML"; if (id_.size() > 0) { std::string idp = phaseNode.id(); if (idp != id_) { throw CanteraError(subname.c_str(), "phasenode and Id are incompatible"); } } /* * Check on the thermo field. Must have: * */ if (phaseNode.hasChild("thermo")) { XML_Node& thNode = phaseNode.child("thermo"); std::string mStringa = thNode.attrib("model"); std::string mString = lowercase(mStringa); if (mString != "maskellsolidsolnphase") { throw CanteraError(subname.c_str(), "Unknown thermo model: " + mStringa); } /* * Parse the enthalpy of mixing constant */ if (thNode.hasChild("h_mix")) { XML_Node& scNode = thNode.child("h_mix"); set_h_mix(fpValue(scNode.value())); } else { throw CanteraError(subname.c_str(), "Mixing enthalpy parameter not specified."); } if (thNode.hasChild("product_species")) { XML_Node& scNode = thNode.child("product_species"); std::string product_species_name = scNode.value(); product_species_index = speciesIndex(product_species_name); if( product_species_index == static_cast(npos) ) { throw CanteraError(subname.c_str(), "Species " + product_species_name + " not found."); } if( product_species_index == 0 ) { reactant_species_index = 1; } else { reactant_species_index = 0; } } } else { throw CanteraError(subname.c_str(), "Unspecified thermo model"); } // Confirm that the phase only contains 2 species if( m_kk != 2 ) { throw CanteraError( subname.c_str(), "MaskellSolidSolution model requires exactly 2 species."); } /* * Call the base initThermo, which handles setting the initial * state. */ VPStandardStateTP::initThermoXML(phaseNode, id_); } void MaskellSolidSolnPhase::_updateThermo() const { assert(m_kk == 2); static const int cacheId = m_cache.getId(); CachedScalar cached = m_cache.getScalar(cacheId); /* * Update the thermodynamic functions of the reference state. */ doublereal tnow = temperature(); if( !cached.validate(tnow) ) { m_spthermo->update(tnow, DATA_PTR(m_cp0_R), DATA_PTR(m_h0_RT), DATA_PTR(m_s0_R)); for (size_t k = 0; k < m_kk; k++) { m_g0_RT[k] = m_h0_RT[k] - m_s0_R[k]; } } } doublereal MaskellSolidSolnPhase::s() const { return 1 + std::exp(h_mixing / (GasConstant * temperature())); } doublereal MaskellSolidSolnPhase::fm(const doublereal r) const { const doublereal sval = s(); return (1 - std::sqrt(1 - 4*r*(1-r)/sval)) / (2*r); } doublereal MaskellSolidSolnPhase::p(const doublereal r) const { const doublereal sval = s(); return (1 - 2*r) / std::sqrt(sval*sval - 4 * sval * r + 4 * sval * r * r); } } // end namespace Cantera