822 lines
28 KiB
C++
822 lines
28 KiB
C++
#include "MultiPhaseEquil.h"
|
|
#include "MultiPhase.h"
|
|
#include "sort.h"
|
|
#include "global.h"
|
|
|
|
#include <math.h>
|
|
#include <iostream>
|
|
using namespace std;
|
|
|
|
#if DARWIN == 1
|
|
#define ISNAN __isnand
|
|
#else
|
|
#ifdef WIN32
|
|
#include <float.h>
|
|
#define ISNAN _isnan
|
|
#else
|
|
#define ISNAN isnan
|
|
#endif
|
|
#endif
|
|
|
|
namespace Cantera {
|
|
|
|
const doublereal TINY = 1.0e-20;
|
|
|
|
/// Used to print reaction equations. Given a stoichiometric
|
|
/// coefficient 'nu' and a chemical symbol 'sym', return a string
|
|
/// for this species in the reaction.
|
|
/// @param first if this is false, then a " + " string will be
|
|
/// added to the beginning of the string.
|
|
/// @param nu Stoichiometric coefficient. May be positive or negative. The
|
|
/// absolute value will be used in the string.
|
|
/// @param sym Species chemical symbol.
|
|
///
|
|
static string coeffString(bool first, doublereal nu, string sym) {
|
|
if (nu == 0.0) return "";
|
|
string strt = " + ";
|
|
if (first) strt = "";
|
|
if (nu == 1.0 || nu == -1.0)
|
|
return strt + sym;
|
|
string s = fp2str(fabs(nu));
|
|
return strt + s + " " + sym;
|
|
}
|
|
|
|
|
|
/// Constructor. Construct a multiphase equilibrium manager for a
|
|
/// multiphase mixture.
|
|
/// @param mix Pointer to a multiphase mixture object.
|
|
/// @param start If true, the initial composition will be
|
|
/// determined by a linear Gibbs minimization, otherwise the
|
|
/// initial mixture composition will be used.
|
|
MultiPhaseEquil::MultiPhaseEquil(mix_t* mix, bool start) : m_mix(mix)
|
|
{
|
|
// the multi-phase mixture
|
|
// m_mix = mix;
|
|
|
|
// store some mixture parameters locally
|
|
m_nel_mix = mix->nElements();
|
|
m_nsp_mix = mix->nSpecies();
|
|
m_np = mix->nPhases();
|
|
m_press = mix->pressure();
|
|
m_temp = mix->temperature();
|
|
|
|
index_t m, k;
|
|
m_nel = 0;
|
|
m_nsp = 0;
|
|
m_eloc = 1000;
|
|
m_incl_species.resize(m_nsp_mix,1);
|
|
m_incl_element.resize(m_nel_mix,1);
|
|
for (m = 0; m < m_nel_mix; m++) {
|
|
string enm = mix->elementName(m);
|
|
// element 'E' or 'e' represents an electron; this
|
|
// requires special handling, so save its index
|
|
// for later use
|
|
if (enm == "E" || enm == "e") {
|
|
m_eloc = m;
|
|
}
|
|
// if an element other than electrons is not present in
|
|
// the mixture, then exclude it and all species containing
|
|
// it from the calculation. Electrons are a special case,
|
|
// since a species can have a negative number of 'atoms'
|
|
// of electrons (positive ions).
|
|
if (m_mix->elementMoles(m) <= 0.0) {
|
|
if (m != m_eloc) {
|
|
m_incl_element[m] = 0;
|
|
for (k = 0; k < m_nsp_mix; k++) {
|
|
if (m_mix->nAtoms(k,m) != 0.0) {
|
|
m_incl_species[k] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now build the list of elements to be included, starting with
|
|
// electrons, if they are present.
|
|
if (m_eloc < m_nel_mix) {
|
|
m_element.push_back(m_eloc);
|
|
m_nel++;
|
|
}
|
|
// add the included elements other than electrons
|
|
for (m = 0; m < m_nel_mix; m++) {
|
|
if (m_incl_element[m] == 1 && m != m_eloc) {
|
|
m_nel++;
|
|
m_element.push_back(m);
|
|
}
|
|
}
|
|
|
|
// include pure single-constituent phases only if their thermo
|
|
// data are valid for this temperature. This is necessary,
|
|
// since some thermo polynomial fits are done only for a
|
|
// limited temperature range. For example, using the NASA
|
|
// polynomial fits for solid ice and liquid water, if this
|
|
// were not done the calculation would predict solid ice to be
|
|
// present far above its melting point, since the thermo
|
|
// polynomial fits only extend to 273.15 K, and give
|
|
// unphysical results above this temperature, leading
|
|
// (incorrectly) to Gibbs free energies at high temperature
|
|
// lower than for liquid water.
|
|
index_t ip;
|
|
for (k = 0; k < m_nsp_mix; k++) {
|
|
ip = m_mix->speciesPhaseIndex(k);
|
|
if (!m_mix->solutionSpecies(k) &&
|
|
!m_mix->tempOK(ip)) {
|
|
m_incl_species[k] = 0;
|
|
if (m_mix->speciesMoles(k) > 0.0) {
|
|
throw CanteraError("MultiPhaseEquil",
|
|
"condensed-phase species"+ m_mix->speciesName(k)
|
|
+ " is excluded since its thermo properties are \n"
|
|
"not valid at this temperature, but it has "
|
|
"non-zero moles in the initial state.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now build the list of all species to be included in the
|
|
// calculation.
|
|
for (k = 0; k < m_nsp_mix; k++) {
|
|
if (m_incl_species[k] ==1) {
|
|
m_nsp++;
|
|
m_species.push_back(k);
|
|
}
|
|
}
|
|
|
|
// some work arrays for internal use
|
|
m_work.resize(m_nsp);
|
|
m_work2.resize(m_nsp);
|
|
m_work3.resize(m_nsp_mix);
|
|
m_mu.resize(m_nsp_mix);
|
|
|
|
// number of moles of each species
|
|
m_moles.resize(m_nsp);
|
|
m_lastmoles.resize(m_nsp);
|
|
m_dxi.resize(m_nsp - m_nel);
|
|
|
|
// initialize the mole numbers to the mixture composition
|
|
index_t ik;
|
|
for (ik = 0; ik < m_nsp; ik++) {
|
|
m_moles[ik] = m_mix->speciesMoles(m_species[ik]);
|
|
}
|
|
|
|
// Delta G / RT for each reaction
|
|
m_deltaG_RT.resize(m_nsp - m_nel, 0.0);
|
|
|
|
m_majorsp.resize(m_nsp);
|
|
m_sortindex.resize(m_nsp,0);
|
|
m_lastsort.resize(m_nel);
|
|
m_solnrxn.resize(m_nsp - m_nel);
|
|
m_A.resize(m_nel, m_nsp, 0.0);
|
|
m_N.resize(m_nsp, m_nsp - m_nel);
|
|
m_order.resize(m_nsp, 0);
|
|
|
|
// if the 'start' flag is set, estimate the initial mole
|
|
// numbers by doing a linear Gibbs minimization. In this case,
|
|
// only the elemental composition of the initial mixture state
|
|
// matters.
|
|
if (start) {
|
|
setInitialMoles();
|
|
}
|
|
computeN();
|
|
|
|
// Take a very small step in composition space, so that no
|
|
// species has precisely zero moles.
|
|
vector_fp dxi(m_nsp - m_nel, 1.0e-20);
|
|
multiply(m_N, DATA_PTR(dxi), DATA_PTR(m_work));
|
|
unsort(m_work);
|
|
|
|
for (k = 0; k < m_nsp; k++) {
|
|
m_moles[k] += m_work[k];
|
|
m_lastmoles[k] = m_moles[k];
|
|
if (m_mix->solutionSpecies(m_species[k]))
|
|
m_dsoln.push_back(1);
|
|
else
|
|
m_dsoln.push_back(0);
|
|
}
|
|
m_force = false;
|
|
updateMixMoles();
|
|
|
|
// At this point, the instance has been created, the species
|
|
// to be included have been determined, and an initial
|
|
// composition has been selected that has all non-zero mole
|
|
// numbers for the included species.
|
|
}
|
|
|
|
|
|
doublereal MultiPhaseEquil::equilibrate(int XY, doublereal err,
|
|
int maxsteps, int loglevel) {
|
|
int i;
|
|
m_iter = 0;
|
|
string iterstr;
|
|
beginLogGroup("MultiPhaseEquil::equilibrate", loglevel);
|
|
|
|
for (i = 0; i < maxsteps; i++) {
|
|
iterstr = "iteration "+int2str(i);
|
|
beginLogGroup(iterstr);
|
|
stepComposition();
|
|
addLogEntry("error",fp2str(error()));
|
|
endLogGroup(iterstr);
|
|
if (error() < err) break;
|
|
}
|
|
if (i >= maxsteps) {
|
|
addLogEntry("Error","no convergence in "+int2str(maxsteps)
|
|
+" iterations");
|
|
endLogGroup("MultiPhaseEquil::equilibrate");
|
|
throw CanteraError("MultiPhaseEquil::equilibrate",
|
|
"no convergence in " + int2str(maxsteps) +
|
|
" iterations. Error = " + fp2str(error()));
|
|
}
|
|
addLogEntry("iterations",int2str(iterations()));
|
|
addLogEntry("error tolerance",fp2str(err));
|
|
addLogEntry("error",fp2str(error()));
|
|
endLogGroup("MultiPhaseEquil::equilibrate");
|
|
finish();
|
|
return error();
|
|
}
|
|
|
|
void MultiPhaseEquil::updateMixMoles() {
|
|
fill(m_work3.begin(), m_work3.end(), 0.0);
|
|
index_t k;
|
|
for (k = 0; k < m_nsp; k++) {
|
|
m_work3[m_species[k]] = m_moles[k];
|
|
}
|
|
m_mix->setMoles(DATA_PTR(m_work3));
|
|
}
|
|
|
|
/// Clean up the composition. The solution algorithm can leave
|
|
/// some species in stoichiometric condensed phases with very
|
|
/// small negative mole numbers. This method simply sets these to
|
|
/// zero.
|
|
void MultiPhaseEquil::finish() {
|
|
fill(m_work3.begin(), m_work3.end(), 0.0);
|
|
index_t k;
|
|
for (k = 0; k < m_nsp; k++) {
|
|
m_work3[m_species[k]] = (m_moles[k] > 0.0 ? m_moles[k] : 0.0);
|
|
}
|
|
m_mix->setMoles(DATA_PTR(m_work3));
|
|
}
|
|
|
|
|
|
/// Extimate the initial mole numbers. This is done by running
|
|
/// each reaction as far forward or backward as possible, subject
|
|
/// to the constraint that all mole numbers remain
|
|
/// non-negative. Reactions for which \f$ \Delta \mu^0 \f$ are
|
|
/// positive are run in reverse, and ones for which it is negative
|
|
/// are run in the forward direction. The end result is equivalent
|
|
/// to solving the linear programming problem of minimizing the
|
|
/// linear Gibbs function subject to the element and
|
|
/// non-negativity constraints.
|
|
int MultiPhaseEquil::setInitialMoles() {
|
|
index_t ik, j;
|
|
|
|
double not_mu = 1.0e12;
|
|
beginLogGroup("MultiPhaseEquil::setInitialMoles");
|
|
|
|
m_mix->getValidChemPotentials(not_mu, DATA_PTR(m_mu), true);
|
|
doublereal dg_rt;
|
|
|
|
int idir;
|
|
double nu;
|
|
double delta_xi, dxi_min = 1.0e10;
|
|
bool redo = true;
|
|
int iter = 0;
|
|
while (redo) {
|
|
|
|
// choose a set of components based on the current
|
|
// composition
|
|
computeN();
|
|
addLogEntry("iteration",iter);
|
|
redo = false;
|
|
iter++;
|
|
if (iter > 4) break;
|
|
|
|
// loop over all reactions
|
|
for (j = 0; j < m_nsp - m_nel; j++) {
|
|
dg_rt = 0.0;
|
|
dxi_min = 1.0e10;
|
|
for (ik = 0; ik < m_nsp; ik++) {
|
|
dg_rt += mu(ik) * m_N(ik,j);
|
|
}
|
|
// fwd or rev direction
|
|
idir = (dg_rt < 0.0 ? 1 : -1);
|
|
|
|
for (ik = 0; ik < m_nsp; ik++) {
|
|
nu = m_N(ik, j);
|
|
|
|
// set max change in progress variable by
|
|
// non-negativity requirement
|
|
if (nu*idir < 0) {
|
|
delta_xi = fabs(moles(ik)/nu);
|
|
// if a component has nearly zero moles, redo
|
|
// with a new set of components
|
|
if (!redo && delta_xi < 1.0e-10 && ik < m_nel) {
|
|
addLogEntry("component too small",speciesName(ik));
|
|
redo = true;
|
|
}
|
|
if (delta_xi < dxi_min) dxi_min = delta_xi;
|
|
}
|
|
}
|
|
// step the composition by dxi_min
|
|
for (ik = 0; ik < m_nsp; ik++) {
|
|
moles(ik) += m_N(ik, j) * idir*dxi_min;
|
|
}
|
|
}
|
|
// set the moles of the phase objects to match
|
|
updateMixMoles();
|
|
}
|
|
for (ik = 0; ik < m_nsp; ik++)
|
|
if (moles(ik) != 0.0) addLogEntry(speciesName(ik), moles(ik));
|
|
|
|
endLogGroup("MultiPhaseEquil::setInitialMoles");
|
|
return 0;
|
|
}
|
|
|
|
|
|
/// This method finds a set of component species and a complete
|
|
/// set of formation reactions for the non-components in terms of
|
|
/// the components. Note that in most cases, many different
|
|
/// component sets are possible, and therefore neither the
|
|
/// components returned by this method nor the formation
|
|
/// reactions are unique. The algorithm used here is described in
|
|
/// Smith and Missen, Chemical Reaction Equilibrium Analysis.
|
|
///
|
|
/// The component species are taken to be the first M species
|
|
/// in array 'species' that have linearly-independent compositions.
|
|
///
|
|
/// @param order On entry, vector \a order should contain species
|
|
/// index numbers in the order of decreasing desirability as a
|
|
/// component. For example, if it is desired to choose the
|
|
/// components from among the major species, this array might
|
|
/// list species index numbers in decreasing order of mole
|
|
/// fraction. If array 'species' does not have length =
|
|
/// nSpecies(), then the species will be considered as candidates
|
|
/// to be components in declaration order, beginning with the
|
|
/// first phase added.
|
|
///
|
|
void MultiPhaseEquil::getComponents(const vector_int& order) {
|
|
index_t m, k, j;
|
|
int n;
|
|
|
|
// if the input species array has the wrong size, ignore it
|
|
// and consider the species for components in declarationi order.
|
|
if (order.size() != m_nsp) {
|
|
for (k = 0; k < m_nsp; k++) m_order[k] = k;
|
|
}
|
|
else {
|
|
for (k = 0; k < m_nsp; k++) m_order[k] = order[k];
|
|
}
|
|
doublereal tmp;
|
|
index_t itmp;
|
|
|
|
index_t nRows = m_nel;
|
|
index_t nColumns = m_nsp;
|
|
doublereal fctr;
|
|
|
|
// set up the atomic composition matrix
|
|
for (m = 0; m < nRows; m++) {
|
|
for (k = 0; k < nColumns; k++) {
|
|
m_A(m, k) = m_mix->nAtoms(m_species[m_order[k]], m_element[m]);
|
|
}
|
|
}
|
|
|
|
// Do Gauss elimination
|
|
for (m = 0; m < nRows; m++) {
|
|
|
|
// If a pivot is zero, exchange columns. This occurs when
|
|
// a species has an elemental composition that is not
|
|
// linearly independent of the component species that have
|
|
// already been assigned
|
|
if (m_A(m,m) == 0.0) {
|
|
|
|
// First, we need to find a good candidate for a
|
|
// component species to swap in for the one that has
|
|
// zero pivot. It must contain element m, be linearly
|
|
// independent of the components processed so far
|
|
// (m_A(m,k) != 0), and should be a major species if
|
|
// possible. We'll choose the species with greatest
|
|
// mole fraction that satisfies these criteria.
|
|
doublereal maxmoles = -999.0;
|
|
index_t kmax = 0;
|
|
for (k = m+1; k < nColumns; k++) {
|
|
if (m_A(m,k) != 0.0) {
|
|
if (fabs(m_moles[m_order[k]]) > maxmoles) {
|
|
kmax = k;
|
|
maxmoles = fabs(m_moles[m_order[k]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now exchange the column with zero pivot with the
|
|
// column for this major species
|
|
for (n = 0; n < int(nRows); n++) {
|
|
tmp = m_A(n,m);
|
|
m_A(n, m) = m_A(n, kmax);
|
|
m_A(n, kmax) = tmp;
|
|
}
|
|
|
|
// exchange the species labels on the columns
|
|
itmp = m_order[m];
|
|
m_order[m] = m_order[kmax];
|
|
m_order[kmax] = itmp;
|
|
|
|
}
|
|
|
|
// scale row m so that the diagonal element is unity
|
|
fctr = 1.0/m_A(m,m);
|
|
for (k = 0; k < nColumns; k++) {
|
|
m_A(m,k) *= fctr;
|
|
}
|
|
|
|
// For all rows below the diagonal, subtract A(n,m)/A(m,m)
|
|
// * (row m) from row n, so that A(n,m) = 0.
|
|
for (n = int(m+1); n < int(m_nel); n++) {
|
|
fctr = m_A(n,m)/m_A(m,m);
|
|
for (k = 0; k < m_nsp; k++) {
|
|
m_A(n,k) -= m_A(m,k)*fctr;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// The left m_nel columns of A are now upper-diagonal. Now
|
|
// reduce the m_nel columns to diagonal form by back-solving
|
|
for (m = nRows-1; m > 0; m--) {
|
|
for (n = m-1; n>= 0; n--) {
|
|
if (m_A(n,m) != 0.0) {
|
|
fctr = m_A(n,m);
|
|
for (k = m; k < m_nsp; k++) {
|
|
m_A(n,k) -= fctr*m_A(m,k);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// create stoichometric coefficient matrix.
|
|
for (n = 0; n < int(m_nsp); n++) {
|
|
if (n < int(m_nel))
|
|
for (k = 0; k < m_nsp - m_nel; k++)
|
|
m_N(n, k) = -m_A(n, k + m_nel);
|
|
else {
|
|
for (k = 0; k < m_nsp - m_nel; k++) m_N(n, k) = 0.0;
|
|
m_N(n, n - m_nel) = 1.0;
|
|
}
|
|
}
|
|
|
|
// find reactions involving solution phase species
|
|
for (j = 0; j < m_nsp - m_nel; j++) {
|
|
m_solnrxn[j] = false;
|
|
for (k = 0; k < m_nsp; k++) {
|
|
if (m_N(k, j) != 0)
|
|
if (m_mix->solutionSpecies(m_species[m_order[k]]))
|
|
m_solnrxn[j] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Re-arrange a vector of species properties in sorted form
|
|
/// (components first) into unsorted, sequential form.
|
|
void MultiPhaseEquil::unsort(vector_fp& x) {
|
|
copy(x.begin(), x.end(), m_work2.begin());
|
|
index_t k;
|
|
for (k = 0; k < m_nsp; k++) {
|
|
x[m_order[k]] = m_work2[k];
|
|
}
|
|
}
|
|
|
|
|
|
void MultiPhaseEquil::printInfo() {
|
|
index_t m, ik, k;
|
|
beginLogGroup("info");
|
|
beginLogGroup("components");
|
|
for (m = 0; m < m_nel; m++) {
|
|
ik = m_order[m];
|
|
k = m_species[ik];
|
|
addLogEntry(m_mix->speciesName(k), fp2str(m_moles[ik]));
|
|
}
|
|
endLogGroup("components");
|
|
beginLogGroup("non-components");
|
|
for (m = m_nel; m < m_nsp; m++) {
|
|
ik = m_order[m];
|
|
k = m_species[ik];
|
|
addLogEntry(m_mix->speciesName(k), fp2str(m_moles[ik]));
|
|
}
|
|
endLogGroup("non-components");
|
|
addLogEntry("Error",fp2str(error()));
|
|
beginLogGroup("Delta G / RT");
|
|
for (k = 0; k < m_nsp - m_nel; k++) {
|
|
addLogEntry(reactionString(k), fp2str(m_deltaG_RT[k]));
|
|
}
|
|
endLogGroup("Delta G / RT");
|
|
endLogGroup("info");
|
|
}
|
|
|
|
/// Return a string specifying the jth reaction.
|
|
string MultiPhaseEquil::reactionString(index_t j) {
|
|
string sr = "", sp = "";
|
|
index_t i, k;
|
|
bool rstrt = true;
|
|
bool pstrt = true;
|
|
doublereal nu;
|
|
for (i = 0; i < m_nsp; i++) {
|
|
nu = m_N(i, j);
|
|
k = m_species[m_order[i]];
|
|
if (nu < 0.0) {
|
|
sr += coeffString(rstrt, nu, m_mix->speciesName(k));
|
|
rstrt = false;
|
|
}
|
|
if (nu > 0.0) {
|
|
sp += coeffString(pstrt, nu, m_mix->speciesName(k));
|
|
pstrt = false;
|
|
}
|
|
}
|
|
return sr + " <=> " + sp;
|
|
}
|
|
|
|
void MultiPhaseEquil::step(doublereal omega, vector_fp& deltaN) {
|
|
index_t k, ik;
|
|
if (omega < 0.0)
|
|
throw CanteraError("step","negative omega");
|
|
|
|
for (ik = 0; ik < m_nel; ik++) {
|
|
k = m_order[ik];
|
|
m_lastmoles[k] = m_moles[k];
|
|
m_moles[k] += omega * deltaN[k];
|
|
}
|
|
|
|
for (ik = m_nel; ik < m_nsp; ik++) {
|
|
k = m_order[ik];
|
|
m_lastmoles[k] = m_moles[k];
|
|
if (m_majorsp[k]) {
|
|
m_moles[k] += omega * deltaN[k];
|
|
}
|
|
else {
|
|
m_moles[k] = fabs(m_moles[k])*fminn(10.0,
|
|
exp(-m_deltaG_RT[ik - m_nel]));
|
|
}
|
|
}
|
|
updateMixMoles();
|
|
}
|
|
|
|
|
|
/// Take one step in composition, given the gradient of G at the
|
|
/// starting point, and a vector of reaction steps dxi.
|
|
doublereal MultiPhaseEquil::
|
|
stepComposition() {
|
|
|
|
beginLogGroup("MultiPhaseEquil::stepComposition");
|
|
|
|
m_iter++;
|
|
index_t ik, k = 0;
|
|
doublereal grad0 = computeReactionSteps(m_dxi);
|
|
|
|
// compute the mole fraction changes.
|
|
multiply(m_N, DATA_PTR(m_dxi), DATA_PTR(m_work));
|
|
|
|
// change to sequential form
|
|
unsort(m_work);
|
|
|
|
// scale omega to keep the major species non-negative
|
|
doublereal FCTR = 0.99;
|
|
const doublereal MAJOR_THRESHOLD = 1.0e-12;
|
|
|
|
doublereal omega = 1.0, omax, omegamax = 1.0;
|
|
for (ik = 0; ik < m_nsp; ik++) {
|
|
k = m_order[ik];
|
|
if (ik < m_nel) {
|
|
FCTR = 0.99;
|
|
if (m_moles[k] < MAJOR_THRESHOLD) m_force = true;
|
|
}
|
|
else FCTR = 0.9;
|
|
// if species k is in a multi-species solution phase, then its
|
|
// mole number must remain positive, unless the entire phase
|
|
// goes away. First we'll determine an upper bound on omega,
|
|
// such that all
|
|
if (m_dsoln[k] == 1) {
|
|
|
|
if ((m_moles[k] > MAJOR_THRESHOLD) || (ik < m_nel)) {
|
|
if (m_moles[k] < MAJOR_THRESHOLD) m_force = true;
|
|
omax = m_moles[k]*FCTR/(fabs(m_work[k]) + TINY);
|
|
if (m_work[k] < 0.0 && omax < omegamax) {
|
|
omegamax = omax;
|
|
if (omegamax < 1.0e-5) {
|
|
m_force = true;
|
|
}
|
|
}
|
|
m_majorsp[k] = true;
|
|
}
|
|
else {
|
|
m_majorsp[k] = false;
|
|
}
|
|
}
|
|
else {
|
|
if (m_work[k] < 0.0 && m_moles[k] > 0.0) {
|
|
omax = -m_moles[k]/m_work[k];
|
|
if (omax < omegamax) {
|
|
omegamax = omax; //*1.000001;
|
|
if (omegamax < 1.0e-5) {
|
|
m_force = true;
|
|
}
|
|
}
|
|
}
|
|
if (m_moles[k] < -Tiny) {
|
|
addLogEntry("Negative moles for "
|
|
+m_mix->speciesName(m_species[k]), fp2str(m_moles[k]));
|
|
}
|
|
m_majorsp[k] = true;
|
|
}
|
|
}
|
|
|
|
// now take a step with this scaled omega
|
|
addLogEntry("Stepping by ", fp2str(omegamax));
|
|
step(omegamax, m_work);
|
|
// compute the gradient of G at this new position in the
|
|
// current direction. If it is positive, then we have overshot
|
|
// the minimum. In this case, interpolate back.
|
|
doublereal not_mu = 1.0e12;
|
|
m_mix->getValidChemPotentials(not_mu, DATA_PTR(m_mu));
|
|
doublereal grad1 = 0.0;
|
|
for (k = 0; k < m_nsp; k++) {
|
|
grad1 += m_work[k] * m_mu[m_species[k]];
|
|
}
|
|
|
|
omega = omegamax;
|
|
if (grad1 > 0.0) {
|
|
omega *= fabs(grad0) / (grad1 + fabs(grad0));
|
|
for (k = 0; k < m_nsp; k++) m_moles[k] = m_lastmoles[k];
|
|
addLogEntry("Stepped over minimum. Take smaller step ", fp2str(omega));
|
|
step(omega, m_work);
|
|
}
|
|
printInfo();
|
|
endLogGroup("MultiPhaseEquil::stepComposition");
|
|
return omega;
|
|
}
|
|
|
|
|
|
/// Compute the change in extent of reaction for each reaction.
|
|
|
|
doublereal MultiPhaseEquil::computeReactionSteps(vector_fp& dxi) {
|
|
|
|
index_t j, k, ik, kc, ip;
|
|
doublereal stoich, nmoles, csum, term1, fctr, rfctr;
|
|
vector_fp nu;
|
|
const doublereal TINY = 1.0e-20;
|
|
doublereal grad = 0.0;
|
|
|
|
dxi.resize(m_nsp - m_nel);
|
|
computeN();
|
|
doublereal not_mu = 1.0e12;
|
|
m_mix->getValidChemPotentials(not_mu, DATA_PTR(m_mu));
|
|
|
|
for (j = 0; j < m_nsp - m_nel; j++) {
|
|
|
|
// get stoichiometric vector
|
|
getStoichVector(j, nu);
|
|
|
|
// compute Delta G
|
|
doublereal dg_rt = 0.0;
|
|
for (k = 0; k < m_nsp; k++) {
|
|
dg_rt += m_mu[m_species[k]] * nu[k];
|
|
}
|
|
dg_rt /= (m_temp * GasConstant);
|
|
|
|
m_deltaG_RT[j] = dg_rt;
|
|
fctr = 1.0;
|
|
|
|
// if this is a formation reaction for a single-component phase,
|
|
// check whether reaction should be included
|
|
ik = j + m_nel;
|
|
k = m_order[ik];
|
|
if (!m_dsoln[k]) {
|
|
if (m_moles[k] <= 0.0 && dg_rt > 0.0) {
|
|
fctr = 0.0;
|
|
}
|
|
else {
|
|
fctr = 0.5;
|
|
}
|
|
}
|
|
else if (!m_solnrxn[j]) {
|
|
fctr = 1.0;
|
|
}
|
|
else {
|
|
|
|
// component sum
|
|
csum = 0.0;
|
|
for (k = 0; k < m_nel; k++) {
|
|
kc = m_order[k];
|
|
stoich = nu[kc];
|
|
nmoles = fabs(m_mix->speciesMoles(m_species[kc])) + TINY;
|
|
csum += stoich*stoich*m_dsoln[kc]/nmoles;
|
|
}
|
|
|
|
// noncomponent term
|
|
kc = m_order[j + m_nel];
|
|
nmoles = fabs(m_mix->speciesMoles(m_species[kc])) + TINY;
|
|
term1 = m_dsoln[kc]/nmoles;
|
|
|
|
// sum over solution phases
|
|
doublereal sum = 0.0, psum;
|
|
for (ip = 0; ip < m_np; ip++) {
|
|
phase_t& p = m_mix->phase(ip);
|
|
if (p.nSpecies() > 1) {
|
|
psum = 0.0;
|
|
for (k = 0; k < m_nsp; k++) {
|
|
kc = m_species[k];
|
|
if (m_mix->speciesPhaseIndex(kc) == ip) {
|
|
stoich = nu[kc];
|
|
psum += stoich * stoich;
|
|
}
|
|
}
|
|
sum -= psum / (fabs(m_mix->phaseMoles(ip)) + TINY);
|
|
}
|
|
}
|
|
rfctr = term1 + csum + sum;
|
|
if (fabs(rfctr) < TINY)
|
|
fctr = 1.0;
|
|
else
|
|
fctr = 1.0/(term1 + csum + sum);
|
|
}
|
|
dxi[j] = -fctr*dg_rt;
|
|
index_t m;
|
|
for (m = 0; m < m_nel; m++) {
|
|
if (m_moles[m_order[m]] <= 0.0 && (m_N(m, j)*dxi[j] < 0.0))
|
|
dxi[j] = 0.0;
|
|
}
|
|
grad += dxi[j]*dg_rt;
|
|
|
|
}
|
|
return grad*GasConstant*m_temp;
|
|
}
|
|
|
|
void MultiPhaseEquil::computeN() {
|
|
index_t m, k;
|
|
|
|
// get the species moles
|
|
|
|
// sort mole fractions
|
|
doublereal molesum = 0.0;
|
|
for (k = 0; k < m_nsp; k++) {
|
|
m_work[k] = m_mix->speciesMoles(m_species[k]);
|
|
m_sortindex[k] = k;
|
|
molesum += m_work[k];
|
|
}
|
|
heapsort(m_work, m_sortindex);
|
|
|
|
// reverse order in sort index
|
|
index_t itmp;
|
|
for (k = 0; k < m_nsp/2; k++) {
|
|
itmp = m_sortindex[m_nsp-k-1];
|
|
m_sortindex[m_nsp-k-1] = m_sortindex[k];
|
|
m_sortindex[k] = itmp;
|
|
}
|
|
index_t ik, ij;
|
|
bool ok;
|
|
for (m = 0; m < m_nel; m++) {
|
|
for (ik = 0; ik < m_nsp; ik++) {
|
|
k = m_sortindex[ik];
|
|
if (m_mix->nAtoms(m_species[k],m_element[m]) != 0) break;
|
|
}
|
|
ok = false;
|
|
for (ij = 0; ij < m_nel; ij++) {
|
|
if (int(k) == m_order[ij]) ok = true;
|
|
}
|
|
if (!ok || m_force) {
|
|
getComponents(m_sortindex);
|
|
m_force = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
doublereal MultiPhaseEquil::error() {
|
|
index_t j, ik, k;
|
|
doublereal err, maxerr = 0.0;
|
|
|
|
// examine every reaction
|
|
for (j = 0; j < m_nsp - m_nel; j++) {
|
|
ik = j + m_nel;
|
|
k = m_order[ik];
|
|
|
|
// don't require formation reactions for solution species
|
|
// present in trace amounts to be equilibrated
|
|
if (!isStoichPhase(ik) && fabs(moles(ik)) <= SmallNumber) {
|
|
err = 0.0;
|
|
}
|
|
|
|
// for stoichiometric phase species, no error if not present and
|
|
// delta G for the formation reaction is positive
|
|
if (isStoichPhase(ik) && moles(ik) <= 0.0 &&
|
|
m_deltaG_RT[j] >= 0.0) {
|
|
err = 0.0;
|
|
}
|
|
else {
|
|
err = fabs(m_deltaG_RT[j]);
|
|
}
|
|
if (err > maxerr) {
|
|
maxerr = err;
|
|
}
|
|
}
|
|
return maxerr;
|
|
}
|
|
}
|