Cantera  4.0.0a1
Loading...
Searching...
No Matches
MultiPhaseEquil.cpp
Go to the documentation of this file.
1/**
2 * @file MultiPhaseEquil.cpp
3 */
4
5// This file is part of Cantera. See License.txt in the top-level directory or
6// at https://cantera.org/license.txt for license and copyright information.
7
11
12#include <cstdio>
13#include <numeric>
14
15namespace Cantera
16{
17
18MultiPhaseEquil::MultiPhaseEquil(MultiPhase* mix, bool start, int loglevel) : m_mix(mix)
19{
20 // store some mixture parameters locally
21 m_nel_mix = mix->nElements();
22 m_nsp_mix = mix->nSpecies();
23 m_press = mix->pressure();
24 m_temp = mix->temperature();
25
26 m_incl_species.resize(m_nsp_mix,1);
27 m_incl_element.resize(m_nel_mix,1);
28 for (size_t m = 0; m < m_nel_mix; m++) {
29 string enm = mix->elementName(m);
30 // element 'E' or 'e' represents an electron; this requires special
31 // handling, so save its index for later use
32 if (enm == "E" || enm == "e") {
33 m_eloc = m;
34 }
35 // if an element other than electrons is not present in the mixture,
36 // then exclude it and all species containing it from the calculation.
37 // Electrons are a special case, since a species can have a negative
38 // number of 'atoms' of electrons (positive ions).
39 if (m_mix->elementMoles(m) <= 0.0 && m != m_eloc) {
40 m_incl_element[m] = 0;
41 for (size_t k = 0; k < m_nsp_mix; k++) {
42 if (m_mix->nAtoms(k,m) != 0.0) {
43 m_incl_species[k] = 0;
44 }
45 }
46 }
47 }
48
49 // Now build the list of elements to be included, starting with
50 // electrons, if they are present.
51 if (m_eloc < m_nel_mix) {
52 m_element.push_back(m_eloc);
53 m_nel++;
54 }
55 // add the included elements other than electrons
56 for (size_t m = 0; m < m_nel_mix; m++) {
57 if (m_incl_element[m] == 1 && m != m_eloc) {
58 m_nel++;
59 m_element.push_back(m);
60 }
61 }
62
63 // include pure single-constituent phases only if their thermo data are
64 // valid for this temperature. This is necessary, since some thermo
65 // polynomial fits are done only for a limited temperature range. For
66 // example, using the NASA polynomial fits for solid ice and liquid water,
67 // if this were not done the calculation would predict solid ice to be
68 // present far above its melting point, since the thermo polynomial fits
69 // only extend to 273.15 K, and give unphysical results above this
70 // temperature, leading (incorrectly) to Gibbs free energies at high
71 // temperature lower than for liquid water.
72 //
73 // When start=true (the default for TP equilibration), the solver computes
74 // its own initial composition from elemental totals. In that case, if an
75 // excluded species has non-zero initial moles, its elemental contribution
76 // is tracked in b_missing and redistributed to valid component species
77 // before calling setInitialMoles.
78 vector<double> b_missing(m_nel, 0.0);
79 bool has_excluded_moles = false;
80 for (size_t k = 0; k < m_nsp_mix; k++) {
81 size_t ip = m_mix->speciesPhaseIndex(k);
82 if (!m_mix->solutionSpecies(k) &&
83 !m_mix->tempOK(ip)) {
84 m_incl_species[k] = 0;
85 if (m_mix->speciesMoles(k) > 0.0) {
86 if (!start) {
87 throw CanteraError("MultiPhaseEquil::MultiPhaseEquil",
88 "condensed-phase species {} is excluded since its thermo "
89 "properties are\nnot valid at this temperature, but it has "
90 "non-zero moles in the initial state.", m_mix->speciesName(k));
91 }
92 for (size_t m = 0; m < m_nel; m++) {
93 b_missing[m] += m_mix->speciesMoles(k) *
94 m_mix->nAtoms(k, m_element[m]);
95 }
96 has_excluded_moles = true;
97 }
98 }
99 }
100
101 // Now build the list of all species to be included in the calculation.
102 for (size_t k = 0; k < m_nsp_mix; k++) {
103 if (m_incl_species[k] ==1) {
104 m_nsp++;
105 m_species.push_back(k);
106 }
107 }
108
109 // some work arrays for internal use
110 m_work.resize(m_nsp);
111 m_work2.resize(m_nsp);
112 m_work3.resize(m_nsp_mix);
113 m_mu.resize(m_nsp_mix);
114
115 // number of moles of each species
116 m_moles.resize(m_nsp);
117 m_lastmoles.resize(m_nsp);
118 m_dxi.resize(nFree());
119
120 // initialize the mole numbers to the mixture composition
121 for (size_t ik = 0; ik < m_nsp; ik++) {
122 m_moles[ik] = m_mix->speciesMoles(m_species[ik]);
123 }
124
125 // Delta G / RT for each reaction
126 m_deltaG_RT.resize(nFree(), 0.0);
127 m_majorsp.resize(m_nsp);
128 m_sortindex.resize(m_nsp,0);
129 m_lastsort.resize(m_nel);
130 m_solnrxn.resize(nFree());
131 m_A.resize(m_nel, m_nsp, 0.0);
132 m_N.resize(m_nsp, nFree());
133 m_order.resize(std::max(m_nsp, m_nel), 0);
134 iota(m_order.begin(), m_order.begin() + m_nsp, 0);
135
136 // if the 'start' flag is set, estimate the initial mole numbers by doing a
137 // linear Gibbs minimization. In this case, only the elemental composition
138 // of the initial mixture state matters.
139 if (start) {
140 if (has_excluded_moles) {
141 // Adjust m_moles to restore element contributions from excluded
142 // condensed-phase species. After Gaussian elimination, m_A has an identity
143 // matrix in the component-species columns, so adding b_missing[m] to
144 // m_moles[m_order[m]] changes only element m's total.
145 computeN();
146 for (size_t m = 0; m < m_nel; m++) {
147 m_moles[m_order[m]] += b_missing[m];
148 }
149 updateMixMoles();
150 }
151 setInitialMoles(loglevel-1);
152 }
153 computeN();
154
155 // Take a very small step in composition space, so that no
156 // species has precisely zero moles.
157 vector<double> dxi(nFree(), 1.0e-20);
158 if (!dxi.empty()) {
159 multiply(m_N, dxi, m_work);
160 unsort(m_work);
161 }
162
163 for (size_t k = 0; k < m_nsp; k++) {
164 m_moles[k] += m_work[k];
165 m_lastmoles[k] = m_moles[k];
166 if (m_mix->solutionSpecies(m_species[k])) {
167 m_dsoln.push_back(1);
168 } else {
169 m_dsoln.push_back(0);
170 }
171 }
172 m_force = false;
173 updateMixMoles();
174
175 // At this point, the instance has been created, the species to be included
176 // have been determined, and an initial composition has been selected that
177 // has all non-zero mole numbers for the included species.
178}
179
180double MultiPhaseEquil::equilibrate(int XY, double err, int maxsteps, int loglevel)
181{
182 int i;
183 m_iter = 0;
184 for (i = 0; i < maxsteps; i++) {
185 stepComposition(loglevel-1);
186 if (error() < err) {
187 break;
188 }
189 }
190 if (i >= maxsteps) {
191 throw CanteraError("MultiPhaseEquil::equilibrate",
192 "no convergence in {} iterations. Error = {}",
193 maxsteps, error());
194 }
195 finish();
196 return error();
197}
198
199void MultiPhaseEquil::updateMixMoles()
200{
201 fill(m_work3.begin(), m_work3.end(), 0.0);
202 for (size_t k = 0; k < m_nsp; k++) {
203 m_work3[m_species[k]] = m_moles[k];
204 }
205 m_mix->setMoles(m_work3);
206}
207
209{
210 fill(m_work3.begin(), m_work3.end(), 0.0);
211 for (size_t k = 0; k < m_nsp; k++) {
212 m_work3[m_species[k]] = (m_moles[k] > 0.0 ? m_moles[k] : 0.0);
213 }
214 m_mix->setMoles(m_work3);
215}
216
218{
219 double not_mu = 1.0e12;
220 m_mix->getValidChemPotentials(not_mu, m_mu, true);
221 double dxi_min = 1.0e10;
222 bool redo = true;
223 int iter = 0;
224
225 while (redo) {
226 // choose a set of components based on the current composition
227 computeN();
228 redo = false;
229 iter++;
230 if (iter > 4) {
231 break;
232 }
233
234 // loop over all reactions
235 for (size_t j = 0; j < nFree(); j++) {
236 double dg_rt = 0.0;
237 dxi_min = 1.0e10;
238 for (size_t ik = 0; ik < m_nsp; ik++) {
239 dg_rt += mu(ik) * m_N(ik,j);
240 }
241
242 // fwd or rev direction
243 int idir = (dg_rt < 0.0 ? 1 : -1);
244
245 for (size_t ik = 0; ik < m_nsp; ik++) {
246 double nu = m_N(ik, j);
247
248 // set max change in progress variable by
249 // non-negativity requirement
250 // -> Note, 0.99 factor is so that difference of 2 numbers
251 // isn't zero. This causes differences between
252 // optimized and debug versions of the code
253 if (nu*idir < 0) {
254 double delta_xi = fabs(0.99*moles(ik)/nu);
255 // if a component has nearly zero moles, redo
256 // with a new set of components
257 if (!redo && delta_xi < 1.0e-10 && ik < m_nel) {
258 redo = true;
259 }
260 dxi_min = std::min(dxi_min, delta_xi);
261 }
262 }
263 // step the composition by dxi_min
264 for (size_t ik = 0; ik < m_nsp; ik++) {
265 moles(ik) += m_N(ik, j) * idir*dxi_min;
266 }
267 }
268 // set the moles of the phase objects to match
269 updateMixMoles();
270 }
271 return 0;
272}
273
274void MultiPhaseEquil::getComponents(span<const size_t> order)
275{
276 // if the input species array has the wrong size, ignore it
277 // and consider the species for components in declaration order.
278 if (order.size() != m_nsp) {
279 for (size_t k = 0; k < m_nsp; k++) {
280 m_order[k] = k;
281 }
282 } else {
283 for (size_t k = 0; k < m_nsp; k++) {
284 m_order[k] = order[k];
285 }
286 }
287
288 size_t nRows = m_nel;
289 size_t nColumns = m_nsp;
290
291 // set up the atomic composition matrix
292 for (size_t m = 0; m < nRows; m++) {
293 for (size_t k = 0; k < nColumns; k++) {
294 m_A(m, k) = m_mix->nAtoms(m_species[m_order[k]], m_element[m]);
295 }
296 }
297
298 // Do Gaussian elimination
299 for (size_t m = 0; m < nRows; m++) {
300 // Check for rows that are zero
301 bool isZeroRow = true;
302 for (size_t k = m; k < nColumns; k++) {
303 if (fabs(m_A(m,k)) > sqrt(Tiny)) {
304 isZeroRow = false;
305 break;
306 }
307 }
308 if (isZeroRow) {
309 // Find the last non-zero row
310 size_t n = nRows - 1;
311 bool foundSwapCandidate = false;
312 for (; n > m; n--) {
313 for (size_t k = m; k < nColumns; k++) {
314 if (fabs(m_A(n,k)) > sqrt(Tiny)) {
315 foundSwapCandidate = true;
316 break;
317 }
318 }
319 if (foundSwapCandidate) {
320 break;
321 }
322 }
323 if (m != n) {
324 // Swap this row with the last non-zero row, and keep m_element
325 // in sync so that the element index matches its A matrix row.
326 for (size_t k = 0; k < nColumns; k++) {
327 std::swap(m_A(n,k), m_A(m,k));
328 }
329 std::swap(m_element[m], m_element[n]);
330 } else {
331 // All remaining rows are zero. Elimination is complete.
332 // The rank of the element matrix is m, which may be less than
333 // m_nel when some element constraints are linearly dependent
334 // (e.g., all species share a fixed H/C ratio). Update m_nel
335 // and resize arrays that depend on nFree().
336 if (m < m_nel) {
337 m_nel = m;
338 m_dxi.resize(nFree());
339 m_deltaG_RT.assign(nFree(), 0.0);
340 m_solnrxn.resize(nFree());
341 m_N.resize(m_nsp, nFree());
342 m_lastsort.resize(m_nel);
343 }
344 break;
345 }
346 }
347
348 // If a pivot is zero, exchange columns. This occurs when a species has
349 // an elemental composition that is not linearly independent of the
350 // component species that have already been assigned
351 if (m < nColumns && m_A(m,m) == 0.0) {
352 // First, we need to find a good candidate for a component species
353 // to swap in for the one that has zero pivot. It must contain
354 // element m, be linearly independent of the components processed so
355 // far (m_A(m,k) != 0), and should be a major species if possible.
356 // We'll choose the species with greatest mole fraction that
357 // satisfies these criteria.
358 double maxmoles = -999.0;
359 size_t kmax = 0;
360 for (size_t k = m+1; k < nColumns; k++) {
361 if (m_A(m,k) != 0.0 && fabs(m_moles[m_order[k]]) > maxmoles) {
362 kmax = k;
363 maxmoles = fabs(m_moles[m_order[k]]);
364 }
365 }
366
367 // Now exchange the column with zero pivot with the
368 // column for this major species
369 for (size_t n = 0; n < nRows; n++) {
370 std::swap(m_A(n, m), m_A(n, kmax));
371 }
372
373 // exchange the species labels on the columns
374 std::swap(m_order[m], m_order[kmax]);
375 }
376
377 // scale row m so that the diagonal element is unity
378 double fctr = 1.0/m_A(m,m);
379 for (size_t k = 0; k < nColumns; k++) {
380 m_A(m,k) *= fctr;
381 }
382
383 // For all rows below the diagonal, subtract A(n,m)/A(m,m)
384 // * (row m) from row n, so that A(n,m) = 0.
385 for (size_t n = m+1; n < m_nel; n++) {
386 fctr = m_A(n,m)/m_A(m,m);
387 for (size_t k = 0; k < m_nsp; k++) {
388 m_A(n,k) -= m_A(m,k)*fctr;
389 }
390 }
391 }
392
393 // The left m_nel columns of A are now upper-diagonal. Now
394 // reduce the m_nel columns to diagonal form by back-solving
395 for (size_t m = std::min(nRows,nColumns)-1; m > 0; m--) {
396 for (size_t n = m-1; n != npos; n--) {
397 if (m_A(n,m) != 0.0) {
398 double fctr = m_A(n,m);
399 for (size_t k = m; k < m_nsp; k++) {
400 m_A(n,k) -= fctr*m_A(m,k);
401 }
402 }
403 }
404 }
405
406 // create stoichiometric coefficient matrix.
407 for (size_t n = 0; n < m_nsp; n++) {
408 if (n < m_nel) {
409 for (size_t k = 0; k < nFree(); k++) {
410 m_N(n, k) = -m_A(n, k + m_nel);
411 }
412 } else {
413 for (size_t k = 0; k < nFree(); k++) {
414 m_N(n, k) = 0.0;
415 }
416 m_N(n, n - m_nel) = 1.0;
417 }
418 }
419
420 // find reactions involving solution phase species
421 for (size_t j = 0; j < nFree(); j++) {
422 m_solnrxn[j] = false;
423 for (size_t k = 0; k < m_nsp; k++) {
424 if (m_N(k, j) != 0 && m_mix->solutionSpecies(m_species[m_order[k]])) {
425 m_solnrxn[j] = true;
426 }
427 }
428 }
429}
430
431void MultiPhaseEquil::unsort(span<double> x)
432{
433 checkArraySize("MultiPhaseEquil::unsort", x.size(), m_nsp);
434 m_work2.assign(x.begin(), x.end());
435 for (size_t k = 0; k < m_nsp; k++) {
436 x[m_order[k]] = m_work2[k];
437 }
438}
439
440void MultiPhaseEquil::step(double omega, span<double> deltaN, int loglevel)
441{
442 if (omega < 0.0) {
443 throw CanteraError("MultiPhaseEquil::step","negative omega");
444 }
445 if (deltaN.size() != m_nsp) {
446 throw CanteraError("MultiPhaseEquil::step",
447 "Expected deltaN size {}, got {}", m_nsp, deltaN.size());
448 }
449
450 for (size_t ik = 0; ik < m_nel; ik++) {
451 size_t k = m_order[ik];
452 m_lastmoles[k] = m_moles[k];
453 m_moles[k] += omega * deltaN[k];
454 }
455
456 for (size_t ik = m_nel; ik < m_nsp; ik++) {
457 size_t k = m_order[ik];
458 m_lastmoles[k] = m_moles[k];
459 if (m_majorsp[k]) {
460 m_moles[k] += omega * deltaN[k];
461 } else {
462 // Minor non-component species use a non-linear update:
463 // moles_new = |m_moles[k]| * exp(-dG/RT), capped at 10x growth for
464 // stability. When m_moles[k] is exactly zero, this degenerates to 0
465 // regardless of dG; the species stays at 0 and the component
466 // correction below suppresses the would-be formation reaction.
467 size_t j = ik - m_nel;
468 double moles_new = fabs(m_moles[k])
469 * std::min(10.0, exp(-m_deltaG_RT[j]));
470 // The components were already updated above by omega*deltaN, which
471 // assumes each noncomponent species k changes by omega*deltaN[k].
472 // The actual change here is moles_new - m_moles[k], so element
473 // balance has an "excess" error that should be corrected on the
474 // components. We only apply the correction when the imbalance is
475 // catastrophic — i.e., the Newton step expects a large change in
476 // this species but the exponential formula caps it at a small
477 // fraction. For modestly-trace species the imbalance is O(m_moles[k])
478 // and applying the correction routinely would destabilize convergence
479 // by perturbing trace component species.
480 double excess = (moles_new - m_moles[k]) - omega * deltaN[k];
481 if (fabs(excess) > 10.0 * fabs(moles_new - m_moles[k]) &&
482 fabs(excess) > SmallNumber) {
483 for (size_t n = 0; n < m_nel; n++) {
484 m_moles[m_order[n]] += m_N(n, j) * excess;
485 }
486 }
487 m_moles[k] = moles_new;
488 }
489 }
490 updateMixMoles();
491}
492
494{
495 m_iter++;
496 double grad0 = computeReactionSteps(m_dxi);
497
498 // compute the mole fraction changes.
499 if (nFree()) {
500 multiply(m_N, m_dxi, m_work);
501 }
502
503 // change to sequential form
504 unsort(m_work);
505
506 // scale omega to keep the major species non-negative
507 double FCTR = 0.99;
508 const double MAJOR_THRESHOLD = 1.0e-12;
509 double omegamax = 1.0;
510 for (size_t ik = 0; ik < m_nsp; ik++) {
511 size_t k = m_order[ik];
512 if (ik < m_nel) {
513 FCTR = 0.99;
514 if (m_moles[k] < MAJOR_THRESHOLD) {
515 m_force = true;
516 }
517 } else {
518 FCTR = 0.9;
519 }
520 // if species k is in a multi-species solution phase, then its mole
521 // number must remain positive, unless the entire phase goes away. First
522 // we'll determine an upper bound on omega, such that all
523 if (m_dsoln[k] == 1) {
524 if ((m_moles[k] > MAJOR_THRESHOLD) || (ik < m_nel)) {
525 if (m_moles[k] < MAJOR_THRESHOLD) {
526 m_force = true;
527 }
528 double omax = m_moles[k]*FCTR/(fabs(m_work[k]) + Tiny);
529 if (m_work[k] < 0.0 && omax < omegamax) {
530 omegamax = omax;
531 if (omegamax < 1.0e-5) {
532 m_force = true;
533 }
534 }
535 m_majorsp[k] = true;
536 } else {
537 m_majorsp[k] = false;
538 }
539 } else {
540 if (m_work[k] < 0.0 && m_moles[k] > 0.0) {
541 double omax = -m_moles[k]/m_work[k];
542 if (omax < omegamax) {
543 omegamax = omax;
544 if (omegamax < 1.0e-5) {
545 m_force = true;
546 }
547 }
548 }
549 m_majorsp[k] = true;
550 }
551 }
552
553 // now take a step with this scaled omega
554 step(omegamax, m_work, loglevel);
555 // compute the gradient of G at this new position in the current direction.
556 // If it is positive, then we have overshot the minimum. In this case,
557 // interpolate back.
558 double not_mu = 1.0e12;
559 m_mix->getValidChemPotentials(not_mu, m_mu);
560 double grad1 = 0.0;
561 for (size_t k = 0; k < m_nsp; k++) {
562 grad1 += m_work[k] * m_mu[m_species[k]];
563 }
564
565 double omega = omegamax;
566 if (grad1 > 0.0) {
567 omega *= fabs(grad0) / (grad1 + fabs(grad0));
568 for (size_t k = 0; k < m_nsp; k++) {
569 m_moles[k] = m_lastmoles[k];
570 }
571 step(omega, m_work);
572 }
573 return omega;
574}
575
577{
578 checkArraySize("MultiPhaseEquil::computeReactionSteps", dxi.size(), nFree());
579 vector<double> nu(m_nsp, 0.0);
580 double grad = 0.0;
581 computeN();
582 double not_mu = 1.0e12;
583 m_mix->getValidChemPotentials(not_mu, m_mu);
584
585 for (size_t j = 0; j < nFree(); j++) {
586 // get stoichiometric vector
587 getStoichVector(j, nu);
588
589 // compute Delta G
590 double dg_rt = 0.0;
591 for (size_t k = 0; k < m_nsp; k++) {
592 dg_rt += m_mu[m_species[k]] * nu[k];
593 }
594 dg_rt /= (m_temp * GasConstant);
595
596 m_deltaG_RT[j] = dg_rt;
597 double fctr = 1.0;
598
599 // if this is a formation reaction for a single-component phase,
600 // check whether reaction should be included
601 size_t k = m_order[j + m_nel];
602 if (!m_dsoln[k]) {
603 if (m_moles[k] <= 0.0 && dg_rt > 0.0) {
604 fctr = 0.0;
605 } else {
606 fctr = 0.5;
607 }
608 } else if (!m_solnrxn[j]) {
609 fctr = 1.0;
610 } else {
611 // component sum
612 double csum = 0.0;
613 for (k = 0; k < m_nel; k++) {
614 size_t kc = m_order[k];
615 double nmoles = fabs(m_mix->speciesMoles(m_species[kc])) + Tiny;
616 csum += pow(nu[kc], 2)*m_dsoln[kc]/nmoles;
617 }
618
619 // noncomponent term
620 size_t kc = m_order[j + m_nel];
621 size_t ip_kc = m_mix->speciesPhaseIndex(m_species[kc]);
622 double nmoles_kc = fabs(m_mix->speciesMoles(m_species[kc])) + Tiny;
623 double pm_kc = fabs(m_mix->phaseMoles(ip_kc)) + Tiny;
624
625 // Phase correction: subtract (sum_{k in phase} nu_k)^2 / n_phase for
626 // each solution phase, which is the Hessian term for an ideal phase.
627 //
628 // For the phase containing kc, combine term1 = dsoln/nmoles_kc with
629 // the phase correction -nu_sum^2/pm_kc into a single fraction. This is
630 // algebraically equivalent but avoids catastrophic cancellation when
631 // pm_kc ≈ nmoles_kc (a trace single-species phase).
632 double nu_sum_kc = 0.0;
633 double sum = 0.0;
634 for (size_t ip = 0; ip < m_mix->nPhases(); ip++) {
635 double pm = fabs(m_mix->phaseMoles(ip));
636 if (m_mix->phase(ip).nSpecies() > 1 && pm > 0.0) {
637 double nu_sum = 0.0;
638 for (k = 0; k < m_nsp; k++) {
639 kc = m_species[k];
640 if (m_mix->speciesPhaseIndex(kc) == ip) {
641 nu_sum += nu[k];
642 }
643 }
644 if (ip == ip_kc) {
645 nu_sum_kc = nu_sum;
646 } else {
647 sum -= nu_sum * nu_sum / (pm + Tiny);
648 }
649 }
650 }
651 kc = m_order[j + m_nel];
652 double term1 = (m_dsoln[kc]*pm_kc - nu_sum_kc*nu_sum_kc*nmoles_kc)
653 / (nmoles_kc*pm_kc);
654 double rfctr = term1 + csum + sum;
655 if (fabs(rfctr) < Tiny) {
656 fctr = 1.0;
657 } else {
658 fctr = 1.0/rfctr;
659 }
660 }
661 dxi[j] = -fctr*dg_rt;
662
663 for (size_t m = 0; m < m_nel; m++) {
664 if (m_moles[m_order[m]] <= 0.0 && (m_N(m, j)*dxi[j] < 0.0)) {
665 dxi[j] = 0.0;
666 }
667 }
668 grad += dxi[j]*dg_rt;
669
670 }
671 return grad*GasConstant*m_temp;
672}
673
674void MultiPhaseEquil::computeN()
675{
676 // Sort the list of species by mole fraction (decreasing order)
677 vector<pair<double, size_t>> moleFractions(m_nsp);
678 for (size_t k = 0; k < m_nsp; k++) {
679 // use -Xk to generate reversed sort order
680 moleFractions[k] = {-m_mix->speciesMoles(m_species[k]), k};
681 }
682 std::sort(moleFractions.begin(), moleFractions.end());
683 for (size_t k = 0; k < m_nsp; k++) {
684 m_sortindex[k] = moleFractions[k].second;
685 }
686
687 for (size_t m = 0; m < m_nel; m++) {
688 size_t k = 0;
689 for (size_t ik = 0; ik < m_nsp; ik++) {
690 k = m_sortindex[ik];
691 if (m_mix->nAtoms(m_species[k],m_element[m]) != 0) {
692 break;
693 }
694 }
695 bool ok = false;
696 for (size_t ij = 0; ij < m_nel; ij++) {
697 if (k == m_order[ij]) {
698 ok = true;
699 }
700 }
701 if (!ok || m_force) {
702 getComponents(m_sortindex);
703 m_force = true;
704 break;
705 }
706 }
707}
708
709double MultiPhaseEquil::error()
710{
711 double err, maxerr = 0.0;
712
713 // Total moles in the mixture; used to scale the "negligible reaction" check
714 // below: a reaction whose maximum possible Gibbs reduction is far below the
715 // mixture scale cannot meaningfully change the composition and should not
716 // block convergence.
717 double total_moles = 0.0;
718 for (size_t k = 0; k < m_nsp; k++) {
719 total_moles += std::max(0.0, m_moles[k]);
720 }
721
722 // examine every reaction
723 for (size_t j = 0; j < nFree(); j++) {
724 size_t ik = j + m_nel;
725
726 // don't require formation reactions for solution species
727 // present in trace amounts to be equilibrated
728 if (!isStoichPhase(ik) && fabs(moles(ik)) <= Tiny) {
729 err = 0.0;
730 } else if (isStoichPhase(ik) && moles(ik) <= 0.0 &&
731 m_deltaG_RT[j] >= 0.0) {
732 // for stoichiometric phase species, no error if not present and
733 // delta G for the formation reaction is positive
734 err = 0.0;
735 } else {
736 err = fabs(m_deltaG_RT[j]);
737 // For an unfavorable (dG > 0) solution-phase formation reaction,
738 // the maximum extent achievable in a single step is bounded by
739 // the noncomponent's own moles (which the reaction is consuming)
740 // and by the components that the reaction would produce
741 // (m_N(n,j) > 0 in the same direction). If this maximum extent is
742 // far below the mixture scale, the reaction cannot meaningfully
743 // change the composition and its dG/RT — which can be set by
744 // floating-point noise in trace mole fractions — should not block
745 // convergence. We do not apply the same bound to favorable
746 // reactions because they can still grow a species from trace
747 // through repeated 10× steps of the exponential update formula.
748 if (!isStoichPhase(ik) && m_deltaG_RT[j] > 0.0) {
749 double max_extent = fabs(moles(ik));
750 for (size_t n = 0; n < m_nel; n++) {
751 double nu = m_N(n, j);
752 if (nu > 0.0) {
753 double mc = std::max(0.0, m_moles[m_order[n]]);
754 max_extent = std::min(max_extent, mc / nu);
755 }
756 }
757 if (max_extent * err < 1.0e-15 * total_moles) {
758 err = 0.0;
759 }
760 }
761 }
762 maxerr = std::max(maxerr, err);
763 }
764 return maxerr;
765}
766
767double MultiPhaseEquil::phaseMoles(size_t iph) const
768{
769 return m_mix->phaseMoles(iph);
770}
771
772void MultiPhaseEquil::reportCSV(const string& reportFile)
773{
774 FILE* FP = fopen(reportFile.c_str(), "w");
775 if (!FP) {
776 throw CanteraError("MultiPhaseEquil::reportCSV", "Failure to open file");
777 }
778 vector<double> mf(m_nsp_mix, 1.0);
779 vector<double> fe(m_nsp_mix, 0.0);
780 vector<double> VolPM;
781 vector<double> activity;
782 vector<double> ac;
783 vector<double> mu;
784 vector<double> mu0;
785 vector<double> molalities;
786
787 double vol = 0.0;
788 for (size_t iphase = 0; iphase < m_mix->nPhases(); iphase++) {
789 size_t istart = m_mix->speciesIndex(0, iphase);
790 ThermoPhase& tref = m_mix->phase(iphase);
791 size_t nSpecies = tref.nSpecies();
792 VolPM.resize(nSpecies, 0.0);
793 tref.getMoleFractions(span<double>(&mf[istart], tref.nSpecies()));
794 tref.getPartialMolarVolumes(VolPM);
795
796 double TMolesPhase = phaseMoles(iphase);
797 double VolPhaseVolumes = 0.0;
798 for (size_t k = 0; k < nSpecies; k++) {
799 VolPhaseVolumes += VolPM[k] * mf[istart + k];
800 }
801 VolPhaseVolumes *= TMolesPhase;
802 vol += VolPhaseVolumes;
803 }
804 fprintf(FP,"--------------------- VCS_MULTIPHASE_EQUIL FINAL REPORT"
805 " -----------------------------\n");
806 fprintf(FP,"Temperature = %11.5g kelvin\n", m_mix->temperature());
807 fprintf(FP,"Pressure = %11.5g Pascal\n", m_mix->pressure());
808 fprintf(FP,"Total Volume = %11.5g m**3\n", vol);
809
810 for (size_t iphase = 0; iphase < m_mix->nPhases(); iphase++) {
811 size_t istart = m_mix->speciesIndex(0, iphase);
812 ThermoPhase& tref = m_mix->phase(iphase);
813 ThermoPhase* tp = &tref;
814 tp->getMoleFractions(span<double>(&mf[istart], tp->nSpecies()));
815 string phaseName = tref.name();
816 double TMolesPhase = phaseMoles(iphase);
817 size_t nSpecies = tref.nSpecies();
818 activity.resize(nSpecies, 0.0);
819 ac.resize(nSpecies, 0.0);
820 mu0.resize(nSpecies, 0.0);
821 mu.resize(nSpecies, 0.0);
822 VolPM.resize(nSpecies, 0.0);
823 molalities.resize(nSpecies, 0.0);
824 int actConvention = tp->activityConvention();
825 tp->getActivities(activity);
826 tp->getActivityCoefficients(ac);
827 tp->getStandardChemPotentials(mu0);
828 tp->getPartialMolarVolumes(VolPM);
829 tp->getChemPotentials(mu);
830 double VolPhaseVolumes = 0.0;
831 for (size_t k = 0; k < nSpecies; k++) {
832 VolPhaseVolumes += VolPM[k] * mf[istart + k];
833 }
834 VolPhaseVolumes *= TMolesPhase;
835 vol += VolPhaseVolumes;
836 if (actConvention == 1) {
837 MolalityVPSSTP* mTP = static_cast<MolalityVPSSTP*>(tp);
838 mTP->getMolalities(molalities);
839 tp->getChemPotentials(mu);
840
841 if (iphase == 0) {
842 fprintf(FP," Name, Phase, PhaseMoles, Mole_Fract, "
843 "Molalities, ActCoeff, Activity,"
844 "ChemPot_SS0, ChemPot, mole_num, PMVol, Phase_Volume\n");
845
846 fprintf(FP," , , (kmol), , "
847 ", , ,"
848 " (kJ/gmol), (kJ/gmol), (kmol), (m**3/kmol), (m**3)\n");
849 }
850 for (size_t k = 0; k < nSpecies; k++) {
851 string sName = tp->speciesName(k);
852 fprintf(FP,"%12s, %11s, %11.3e, %11.3e, %11.3e, %11.3e, %11.3e,"
853 "%11.3e, %11.3e, %11.3e, %11.3e, %11.3e\n",
854 sName.c_str(),
855 phaseName.c_str(), TMolesPhase,
856 mf[istart + k], molalities[k], ac[k], activity[k],
857 mu0[k]*1.0E-6, mu[k]*1.0E-6,
858 mf[istart + k] * TMolesPhase,
859 VolPM[k], VolPhaseVolumes);
860 }
861 } else {
862 if (iphase == 0) {
863 fprintf(FP," Name, Phase, PhaseMoles, Mole_Fract, "
864 "Molalities, ActCoeff, Activity,"
865 " ChemPotSS0, ChemPot, mole_num, PMVol, Phase_Volume\n");
866
867 fprintf(FP," , , (kmol), , "
868 ", , ,"
869 " (kJ/gmol), (kJ/gmol), (kmol), (m**3/kmol), (m**3)\n");
870 }
871 for (size_t k = 0; k < nSpecies; k++) {
872 molalities[k] = 0.0;
873 }
874 for (size_t k = 0; k < nSpecies; k++) {
875 string sName = tp->speciesName(k);
876 fprintf(FP,"%12s, %11s, %11.3e, %11.3e, %11.3e, %11.3e, %11.3e, "
877 "%11.3e, %11.3e,% 11.3e, %11.3e, %11.3e\n",
878 sName.c_str(),
879 phaseName.c_str(), TMolesPhase,
880 mf[istart + k], molalities[k], ac[k],
881 activity[k], mu0[k]*1.0E-6, mu[k]*1.0E-6,
882 mf[istart + k] * TMolesPhase,
883 VolPM[k], VolPhaseVolumes);
884 }
885 }
886 }
887 fclose(FP);
888}
889
890}
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.
Definition MultiPhase.h:62
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...
Definition MultiPhase.h:206
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.
Definition MultiPhase.h:116
double pressure() const
Pressure [Pa].
Definition MultiPhase.h:352
size_t nPhases() const
Number of phases.
Definition MultiPhase.h:389
double temperature() const
Temperature [K].
Definition MultiPhase.h:308
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.
Definition MultiPhase.h:86
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.
Definition Phase.cpp:451
size_t nSpecies() const
Returns the number of species in the phase.
Definition Phase.h:247
const double GasConstant
Universal Gas Constant [J/kmol/K].
Definition ct_defs.h:123
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
const size_t npos
index returned by functions to indicate "no position"
Definition ct_defs.h:183
const double Tiny
Small number to compare differences of mole fractions against.
Definition ct_defs.h:176
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.
Definition ct_defs.h:161
void checkArraySize(const char *procedure, size_t available, size_t required)
Wrapper for throwing ArraySizeError.
Contains declarations for string manipulation functions within Cantera.