10#include "cantera/numerics/eigen_dense.h"
27 m_incl_species.resize(m_nsp_mix,1);
28 m_incl_element.resize(m_nel_mix,1);
29 for (
size_t m = 0; m < m_nel_mix; m++) {
33 if (enm ==
"E" || enm ==
"e") {
41 m_incl_element[m] = 0;
42 for (
size_t k = 0; k < m_nsp_mix; k++) {
43 if (m_mix->
nAtoms(k,m) != 0.0) {
44 m_incl_species[k] = 0;
52 if (m_eloc < m_nel_mix) {
53 m_element.push_back(m_eloc);
57 for (
size_t m = 0; m < m_nel_mix; m++) {
58 if (m_incl_element[m] == 1 && m != m_eloc) {
60 m_element.push_back(m);
79 vector<size_t> excluded_with_nonzero_moles;
80 for (
size_t k = 0; k < m_nsp_mix; k++) {
84 m_incl_species[k] = 0;
88 "Species {} is excluded since its thermo properties are not "
89 "valid\nat this temperature, but it has non-zero moles in the "
92 excluded_with_nonzero_moles.push_back(k);
98 for (
size_t k = 0; k < m_nsp_mix; k++) {
99 if (m_incl_species[k] ==1) {
101 m_species.push_back(k);
106 m_work.resize(m_nsp);
107 m_work2.resize(m_nsp);
108 m_work3.resize(m_nsp_mix);
109 m_mu.resize(m_nsp_mix);
112 m_moles.resize(m_nsp);
113 m_lastmoles.resize(m_nsp);
114 m_dxi.resize(
nFree());
117 for (
size_t ik = 0; ik < m_nsp; ik++) {
122 m_deltaG_RT.resize(
nFree(), 0.0);
123 m_majorsp.resize(m_nsp);
124 m_sortindex.resize(m_nsp,0);
125 m_lastsort.resize(m_nel);
126 m_solnrxn.resize(
nFree());
127 m_A.
resize(m_nel, m_nsp, 0.0);
129 m_order.resize(std::max(m_nsp, m_nel), 0);
130 iota(m_order.begin(), m_order.begin() + m_nsp, 0);
136 if (!excluded_with_nonzero_moles.empty()) {
146 Eigen::VectorXd b_missing = Eigen::VectorXd::Zero(m_nel);
147 for (
size_t k : excluded_with_nonzero_moles) {
148 for (
size_t m = 0; m < m_nel; m++) {
150 m_mix->
nAtoms(k, m_element[m]);
153 Eigen::MatrixXd B(m_nel, m_nel);
154 for (
size_t i = 0; i < m_nel; i++) {
155 for (
size_t j = 0; j < m_nel; j++) {
156 B(i, j) = m_mix->
nAtoms(m_species[m_order[j]], m_element[i]);
159 Eigen::VectorXd delta = B.colPivHouseholderQr().solve(b_missing);
160 for (
size_t j = 0; j < m_nel; j++) {
161 m_moles[m_order[j]] += delta[j];
171 vector<double> dxi(
nFree(), 1.0e-20);
177 for (
size_t k = 0; k < m_nsp; k++) {
178 m_moles[k] += m_work[k];
179 m_lastmoles[k] = m_moles[k];
181 m_dsoln.push_back(1);
183 m_dsoln.push_back(0);
194double MultiPhaseEquil::equilibrate(
int XY,
double err,
int maxsteps,
int loglevel)
198 for (i = 0; i < maxsteps; i++) {
205 throw CanteraError(
"MultiPhaseEquil::equilibrate",
206 "no convergence in {} iterations. Error = {}",
213void MultiPhaseEquil::updateMixMoles()
215 fill(m_work3.begin(), m_work3.end(), 0.0);
216 for (
size_t k = 0; k < m_nsp; k++) {
217 m_work3[m_species[k]] = m_moles[k];
224 fill(m_work3.begin(), m_work3.end(), 0.0);
225 for (
size_t k = 0; k < m_nsp; k++) {
226 m_work3[m_species[k]] = (m_moles[k] > 0.0 ? m_moles[k] : 0.0);
233 double not_mu = 1.0e12;
235 double dxi_min = 1.0e10;
249 for (
size_t j = 0; j <
nFree(); j++) {
252 for (
size_t ik = 0; ik < m_nsp; ik++) {
253 dg_rt += mu(ik) * m_N(ik,j);
257 int idir = (dg_rt < 0.0 ? 1 : -1);
259 for (
size_t ik = 0; ik < m_nsp; ik++) {
260 double nu = m_N(ik, j);
268 double delta_xi = fabs(0.99*moles(ik)/nu);
271 if (!redo && delta_xi < 1.0e-10 && ik < m_nel) {
274 dxi_min = std::min(dxi_min, delta_xi);
278 for (
size_t ik = 0; ik < m_nsp; ik++) {
279 moles(ik) += m_N(ik, j) * idir*dxi_min;
292 if (order.size() != m_nsp) {
293 for (
size_t k = 0; k < m_nsp; k++) {
297 for (
size_t k = 0; k < m_nsp; k++) {
298 m_order[k] = order[k];
302 size_t nRows = m_nel;
303 size_t nColumns = m_nsp;
306 for (
size_t m = 0; m < nRows; m++) {
307 for (
size_t k = 0; k < nColumns; k++) {
308 m_A(m, k) = m_mix->
nAtoms(m_species[m_order[k]], m_element[m]);
313 for (
size_t m = 0; m < nRows; m++) {
315 bool isZeroRow =
true;
316 for (
size_t k = m; k < nColumns; k++) {
317 if (fabs(m_A(m,k)) > sqrt(
Tiny)) {
324 size_t n = nRows - 1;
325 bool foundSwapCandidate =
false;
327 for (
size_t k = m; k < nColumns; k++) {
328 if (fabs(m_A(n,k)) > sqrt(
Tiny)) {
329 foundSwapCandidate =
true;
333 if (foundSwapCandidate) {
340 for (
size_t k = 0; k < nColumns; k++) {
341 std::swap(m_A(n,k), m_A(m,k));
343 std::swap(m_element[m], m_element[n]);
352 m_dxi.resize(
nFree());
353 m_deltaG_RT.assign(
nFree(), 0.0);
354 m_solnrxn.resize(
nFree());
356 m_lastsort.resize(m_nel);
365 if (m < nColumns && m_A(m,m) == 0.0) {
372 double maxmoles = -999.0;
374 for (
size_t k = m+1; k < nColumns; k++) {
375 if (m_A(m,k) != 0.0 && fabs(m_moles[m_order[k]]) > maxmoles) {
377 maxmoles = fabs(m_moles[m_order[k]]);
383 for (
size_t n = 0; n < nRows; n++) {
384 std::swap(m_A(n, m), m_A(n, kmax));
388 std::swap(m_order[m], m_order[kmax]);
392 double fctr = 1.0/m_A(m,m);
393 for (
size_t k = 0; k < nColumns; k++) {
399 for (
size_t n = m+1; n < m_nel; n++) {
400 fctr = m_A(n,m)/m_A(m,m);
401 for (
size_t k = 0; k < m_nsp; k++) {
402 m_A(n,k) -= m_A(m,k)*fctr;
409 for (
size_t m = std::min(nRows,nColumns)-1; m > 0; m--) {
410 for (
size_t n = m-1; n !=
npos; n--) {
411 if (m_A(n,m) != 0.0) {
412 double fctr = m_A(n,m);
413 for (
size_t k = m; k < m_nsp; k++) {
414 m_A(n,k) -= fctr*m_A(m,k);
421 for (
size_t n = 0; n < m_nsp; n++) {
423 for (
size_t k = 0; k <
nFree(); k++) {
424 m_N(n, k) = -m_A(n, k + m_nel);
427 for (
size_t k = 0; k <
nFree(); k++) {
430 m_N(n, n - m_nel) = 1.0;
435 for (
size_t j = 0; j <
nFree(); j++) {
436 m_solnrxn[j] =
false;
437 for (
size_t k = 0; k < m_nsp; k++) {
448 m_work2.assign(x.begin(), x.end());
449 for (
size_t k = 0; k < m_nsp; k++) {
450 x[m_order[k]] = m_work2[k];
454void MultiPhaseEquil::step(
double omega, span<double> deltaN,
int loglevel)
457 throw CanteraError(
"MultiPhaseEquil::step",
"negative omega");
459 if (deltaN.size() != m_nsp) {
460 throw CanteraError(
"MultiPhaseEquil::step",
461 "Expected deltaN size {}, got {}", m_nsp, deltaN.size());
464 for (
size_t ik = 0; ik < m_nel; ik++) {
465 size_t k = m_order[ik];
466 m_lastmoles[k] = m_moles[k];
467 m_moles[k] += omega * deltaN[k];
470 for (
size_t ik = m_nel; ik < m_nsp; ik++) {
471 size_t k = m_order[ik];
472 m_lastmoles[k] = m_moles[k];
474 m_moles[k] += omega * deltaN[k];
481 size_t j = ik - m_nel;
482 double moles_new = fabs(m_moles[k])
483 * std::min(10.0, exp(-m_deltaG_RT[j]));
494 double excess = (moles_new - m_moles[k]) - omega * deltaN[k];
495 if (fabs(excess) > 10.0 * fabs(moles_new - m_moles[k]) &&
497 for (
size_t n = 0; n < m_nel; n++) {
498 m_moles[m_order[n]] += m_N(n, j) * excess;
501 m_moles[k] = moles_new;
519 double total_moles = 0.0;
520 for (
size_t k = 0; k < m_nsp; k++) {
521 total_moles += std::max(0.0, m_moles[k]);
523 double trace =
Tiny * total_moles;
524 bool dropped =
false;
525 for (
size_t j = 0; j <
nFree(); j++) {
526 size_t k = m_order[j + m_nel];
527 if (!m_dsoln[k] && m_moles[k] > 0.0 && m_moles[k] < trace && m_dxi[j] < 0.0) {
547 const double MAJOR_THRESHOLD = 1.0e-12;
548 double omegamax = 1.0;
549 for (
size_t ik = 0; ik < m_nsp; ik++) {
550 size_t k = m_order[ik];
553 if (m_moles[k] < MAJOR_THRESHOLD) {
562 if (m_dsoln[k] == 1) {
563 if ((m_moles[k] > MAJOR_THRESHOLD) || (ik < m_nel)) {
564 if (m_moles[k] < MAJOR_THRESHOLD) {
567 double omax = m_moles[k]*FCTR/(fabs(m_work[k]) +
Tiny);
568 if (m_work[k] < 0.0 && omax < omegamax) {
570 if (omegamax < 1.0e-5) {
576 m_majorsp[k] =
false;
579 if (m_work[k] < 0.0 && m_moles[k] > 0.0) {
580 double omax = -m_moles[k]/m_work[k];
581 if (omax < omegamax) {
583 if (omegamax < 1.0e-5) {
593 step(omegamax, m_work, loglevel);
597 double not_mu = 1.0e12;
600 for (
size_t k = 0; k < m_nsp; k++) {
601 grad1 += m_work[k] * m_mu[m_species[k]];
604 double omega = omegamax;
606 omega *= fabs(grad0) / (grad1 + fabs(grad0));
607 for (
size_t k = 0; k < m_nsp; k++) {
608 m_moles[k] = m_lastmoles[k];
618 vector<double> nu(m_nsp, 0.0);
621 double not_mu = 1.0e12;
624 for (
size_t j = 0; j <
nFree(); j++) {
626 getStoichVector(j, nu);
630 for (
size_t k = 0; k < m_nsp; k++) {
631 dg_rt += m_mu[m_species[k]] * nu[k];
635 m_deltaG_RT[j] = dg_rt;
640 size_t k = m_order[j + m_nel];
642 if (m_moles[k] <= 0.0 && dg_rt > 0.0) {
647 }
else if (!m_solnrxn[j]) {
652 for (k = 0; k < m_nel; k++) {
653 size_t kc = m_order[k];
655 csum += pow(nu[kc], 2)*m_dsoln[kc]/nmoles;
659 size_t kc = m_order[j + m_nel];
671 double nu_sum_kc = 0.0;
673 for (
size_t ip = 0; ip < m_mix->
nPhases(); ip++) {
677 for (k = 0; k < m_nsp; k++) {
686 sum -= nu_sum * nu_sum / (pm +
Tiny);
690 kc = m_order[j + m_nel];
691 double term1 = (m_dsoln[kc]*pm_kc - nu_sum_kc*nu_sum_kc*nmoles_kc)
693 double rfctr = term1 + csum + sum;
694 if (fabs(rfctr) <
Tiny) {
700 dxi[j] = -fctr*dg_rt;
702 for (
size_t m = 0; m < m_nel; m++) {
703 if (m_moles[m_order[m]] <= 0.0 && (m_N(m, j)*dxi[j] < 0.0)) {
707 grad += dxi[j]*dg_rt;
713void MultiPhaseEquil::computeN()
716 vector<pair<double, size_t>> moleFractions(m_nsp);
717 for (
size_t k = 0; k < m_nsp; k++) {
719 moleFractions[k] = {-m_mix->
speciesMoles(m_species[k]), k};
721 std::sort(moleFractions.begin(), moleFractions.end());
722 for (
size_t k = 0; k < m_nsp; k++) {
723 m_sortindex[k] = moleFractions[k].second;
726 bool reselect = m_force;
727 for (
size_t m = 0; m < m_nel && !reselect; m++) {
729 for (
size_t ik = 0; ik < m_nsp; ik++) {
731 if (m_mix->
nAtoms(m_species[k],m_element[m]) != 0) {
736 for (
size_t ij = 0; ij < m_nel; ij++) {
737 if (k == m_order[ij]) {
753 double total_moles = 0.0;
754 for (
size_t k = 0; k < m_nsp; k++) {
755 total_moles += std::max(0.0, m_moles[k]);
757 for (
size_t m = 0; m < m_nel; m++) {
758 if (m_moles[m_order[m]] < 1.0e-3 * total_moles) {
771double MultiPhaseEquil::error()
773 double err, maxerr = 0.0;
779 double total_moles = 0.0;
780 for (
size_t k = 0; k < m_nsp; k++) {
781 total_moles += std::max(0.0, m_moles[k]);
785 for (
size_t j = 0; j <
nFree(); j++) {
786 size_t ik = j + m_nel;
790 if (!isStoichPhase(ik) && fabs(moles(ik)) <=
Tiny) {
792 }
else if (isStoichPhase(ik) && moles(ik) <= 0.0 &&
793 m_deltaG_RT[j] >= 0.0) {
798 err = fabs(m_deltaG_RT[j]);
810 if (!isStoichPhase(ik) && m_deltaG_RT[j] > 0.0) {
811 double max_extent = fabs(moles(ik));
812 for (
size_t n = 0; n < m_nel; n++) {
813 double nu = m_N(n, j);
815 double mc = std::max(0.0, m_moles[m_order[n]]);
816 max_extent = std::min(max_extent, mc / nu);
819 if (max_extent * err < 1.0e-15 * total_moles) {
824 maxerr = std::max(maxerr, err);
829double MultiPhaseEquil::phaseMoles(
size_t iph)
const
834void MultiPhaseEquil::reportCSV(
const string& reportFile)
836 FILE* FP = fopen(reportFile.c_str(),
"w");
838 throw CanteraError(
"MultiPhaseEquil::reportCSV",
"Failure to open file");
840 vector<double> mf(m_nsp_mix, 1.0);
841 vector<double> fe(m_nsp_mix, 0.0);
842 vector<double> VolPM;
843 vector<double> activity;
847 vector<double> molalities;
850 for (
size_t iphase = 0; iphase < m_mix->
nPhases(); iphase++) {
852 ThermoPhase& tref = m_mix->
phase(iphase);
854 VolPM.resize(nSpecies, 0.0);
855 tref.getMoleFractions(span<double>(&mf[istart], tref.nSpecies()));
856 tref.getPartialMolarVolumes(VolPM);
858 double TMolesPhase = phaseMoles(iphase);
859 double VolPhaseVolumes = 0.0;
860 for (
size_t k = 0; k < nSpecies; k++) {
861 VolPhaseVolumes += VolPM[k] * mf[istart + k];
863 VolPhaseVolumes *= TMolesPhase;
864 vol += VolPhaseVolumes;
866 fprintf(FP,
"--------------------- VCS_MULTIPHASE_EQUIL FINAL REPORT"
867 " -----------------------------\n");
868 fprintf(FP,
"Temperature = %11.5g kelvin\n", m_mix->
temperature());
869 fprintf(FP,
"Pressure = %11.5g Pascal\n", m_mix->
pressure());
870 fprintf(FP,
"Total Volume = %11.5g m**3\n", vol);
872 for (
size_t iphase = 0; iphase < m_mix->
nPhases(); iphase++) {
874 ThermoPhase& tref = m_mix->
phase(iphase);
875 ThermoPhase* tp = &tref;
877 string phaseName = tref.name();
878 double TMolesPhase = phaseMoles(iphase);
879 size_t nSpecies = tref.nSpecies();
880 activity.resize(nSpecies, 0.0);
881 ac.resize(nSpecies, 0.0);
882 mu0.resize(nSpecies, 0.0);
883 mu.resize(nSpecies, 0.0);
884 VolPM.resize(nSpecies, 0.0);
885 molalities.resize(nSpecies, 0.0);
886 int actConvention = tp->activityConvention();
887 tp->getActivities(activity);
888 tp->getActivityCoefficients(ac);
889 tp->getStandardChemPotentials(mu0);
890 tp->getPartialMolarVolumes(VolPM);
891 tp->getChemPotentials(mu);
892 double VolPhaseVolumes = 0.0;
893 for (
size_t k = 0; k < nSpecies; k++) {
894 VolPhaseVolumes += VolPM[k] * mf[istart + k];
896 VolPhaseVolumes *= TMolesPhase;
897 vol += VolPhaseVolumes;
898 if (actConvention == 1) {
899 MolalityVPSSTP* mTP =
static_cast<MolalityVPSSTP*
>(tp);
900 mTP->getMolalities(molalities);
901 tp->getChemPotentials(mu);
904 fprintf(FP,
" Name, Phase, PhaseMoles, Mole_Fract, "
905 "Molalities, ActCoeff, Activity,"
906 "ChemPot_SS0, ChemPot, mole_num, PMVol, Phase_Volume\n");
908 fprintf(FP,
" , , (kmol), , "
910 " (kJ/gmol), (kJ/gmol), (kmol), (m**3/kmol), (m**3)\n");
912 for (
size_t k = 0; k < nSpecies; k++) {
913 string sName = tp->speciesName(k);
914 fprintf(FP,
"%12s, %11s, %11.3e, %11.3e, %11.3e, %11.3e, %11.3e,"
915 "%11.3e, %11.3e, %11.3e, %11.3e, %11.3e\n",
917 phaseName.c_str(), TMolesPhase,
918 mf[istart + k], molalities[k], ac[k], activity[k],
919 mu0[k]*1.0E-6, mu[k]*1.0E-6,
920 mf[istart + k] * TMolesPhase,
921 VolPM[k], VolPhaseVolumes);
925 fprintf(FP,
" Name, Phase, PhaseMoles, Mole_Fract, "
926 "Molalities, ActCoeff, Activity,"
927 " ChemPotSS0, ChemPot, mole_num, PMVol, Phase_Volume\n");
929 fprintf(FP,
" , , (kmol), , "
931 " (kJ/gmol), (kJ/gmol), (kmol), (m**3/kmol), (m**3)\n");
933 for (
size_t k = 0; k < nSpecies; k++) {
936 for (
size_t k = 0; k < nSpecies; k++) {
937 string sName = tp->speciesName(k);
938 fprintf(FP,
"%12s, %11s, %11.3e, %11.3e, %11.3e, %11.3e, %11.3e, "
939 "%11.3e, %11.3e,% 11.3e, %11.3e, %11.3e\n",
941 phaseName.c_str(), TMolesPhase,
942 mf[istart + k], molalities[k], ac[k],
943 activity[k], mu0[k]*1.0E-6, mu[k]*1.0E-6,
944 mf[istart + k] * TMolesPhase,
945 VolPM[k], VolPhaseVolumes);
Header for intermediate ThermoPhase object for phases which employ molality based activity coefficien...
Base class for exceptions thrown by Cantera classes.
void resize(size_t n, size_t m, double v=0.0) override
Resize the matrix.
void getComponents(span< const size_t > order)
This method finds a set of component species and a complete set of formation reactions for the non-co...
double stepComposition(int loglevel=0)
Take one step in composition, given the gradient of G at the starting point, and a vector of reaction...
void unsort(span< double > x)
Re-arrange a vector of species properties in sorted form (components first) into unsorted,...
void finish()
Clean up the composition.
size_t nFree() const
Number of degrees of freedom.
MultiPhaseEquil(MultiPhase *mix, bool start=true, int loglevel=0)
Construct a multiphase equilibrium manager for a multiphase mixture.
double computeReactionSteps(span< double > dxi)
Compute the change in extent of reaction for each reaction.
int setInitialMoles(int loglevel=0)
Estimate the initial mole numbers.
A class for multiphase mixtures.
size_t speciesIndex(size_t k, size_t p) const
Return the global index of the species belonging to phase number p with local index k within the phas...
bool solutionSpecies(size_t kGlob) const
Return true is species kGlob is a species in a multicomponent solution phase.
double nAtoms(const size_t kGlob, const size_t mGlob) const
Returns the Number of atoms of global element mGlob in global species kGlob.
void setMoles(span< const double > n)
Sets all of the global species mole numbers.
double speciesMoles(size_t kGlob) const
Returns the moles of global species k. units = kmol.
size_t nSpecies() const
Number of species, summed over all phases.
double pressure() const
Pressure [Pa].
size_t nPhases() const
Number of phases.
double temperature() const
Temperature [K].
string speciesName(size_t kGlob) const
Name of species with global index kGlob.
bool tempOK(size_t p) const
Return true if the phase p has valid thermo data for the current temperature.
size_t speciesPhaseIndex(const size_t kGlob) const
Returns the phase index of the Kth "global" species.
void getValidChemPotentials(double not_mu, span< double > mu, bool standard=false) const
Returns a vector of Valid chemical potentials.
double phaseMoles(const size_t n) const
Return the number of moles in phase n.
size_t nElements() const
Number of elements.
ThermoPhase & phase(size_t n)
Return a reference to phase n.
string elementName(size_t m) const
Returns the name of the global element m.
double elementMoles(size_t m) const
Total moles of global element m, summed over all phases.
void getMoleFractions(span< double > x) const
Get the species mole fraction vector.
size_t nSpecies() const
Returns the number of species in the phase.
const double GasConstant
Universal Gas Constant [J/kmol/K].
Namespace for the Cantera kernel.
const size_t npos
index returned by functions to indicate "no position"
const double Tiny
Small number to compare differences of mole fractions against.
void multiply(const DenseMatrix &A, span< const double > b, span< double > prod)
Multiply A*b and return the result in prod. Uses BLAS routine DGEMV.
const double SmallNumber
smallest number to compare to zero.
void checkArraySize(const char *procedure, size_t available, size_t required)
Wrapper for throwing ArraySizeError.
Contains declarations for string manipulation functions within Cantera.