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 for (size_t k = 0; k < m_nsp_mix; k++) {
73 size_t ip = m_mix->speciesPhaseIndex(k);
74 if (!m_mix->solutionSpecies(k) &&
75 !m_mix->tempOK(ip)) {
76 m_incl_species[k] = 0;
77 if (m_mix->speciesMoles(k) > 0.0) {
78 throw CanteraError("MultiPhaseEquil::MultiPhaseEquil",
79 "condensed-phase species"+ m_mix->speciesName(k)
80 + " is excluded since its thermo properties are \n"
81 "not valid at this temperature, but it has "
82 "non-zero moles in the initial state.");
83 }
84 }
85 }
86
87 // Now build the list of all species to be included in the calculation.
88 for (size_t k = 0; k < m_nsp_mix; k++) {
89 if (m_incl_species[k] ==1) {
90 m_nsp++;
91 m_species.push_back(k);
92 }
93 }
94
95 // some work arrays for internal use
96 m_work.resize(m_nsp);
97 m_work2.resize(m_nsp);
98 m_work3.resize(m_nsp_mix);
99 m_mu.resize(m_nsp_mix);
100
101 // number of moles of each species
102 m_moles.resize(m_nsp);
103 m_lastmoles.resize(m_nsp);
104 m_dxi.resize(nFree());
105
106 // initialize the mole numbers to the mixture composition
107 for (size_t ik = 0; ik < m_nsp; ik++) {
108 m_moles[ik] = m_mix->speciesMoles(m_species[ik]);
109 }
110
111 // Delta G / RT for each reaction
112 m_deltaG_RT.resize(nFree(), 0.0);
113 m_majorsp.resize(m_nsp);
114 m_sortindex.resize(m_nsp,0);
115 m_lastsort.resize(m_nel);
116 m_solnrxn.resize(nFree());
117 m_A.resize(m_nel, m_nsp, 0.0);
118 m_N.resize(m_nsp, nFree());
119 m_order.resize(std::max(m_nsp, m_nel), 0);
120 iota(m_order.begin(), m_order.begin() + m_nsp, 0);
121
122 // if the 'start' flag is set, estimate the initial mole numbers by doing a
123 // linear Gibbs minimization. In this case, only the elemental composition
124 // of the initial mixture state matters.
125 if (start) {
126 setInitialMoles(loglevel-1);
127 }
128 computeN();
129
130 // Take a very small step in composition space, so that no
131 // species has precisely zero moles.
132 vector<double> dxi(nFree(), 1.0e-20);
133 if (!dxi.empty()) {
134 multiply(m_N, dxi, m_work);
135 unsort(m_work);
136 }
137
138 for (size_t k = 0; k < m_nsp; k++) {
139 m_moles[k] += m_work[k];
140 m_lastmoles[k] = m_moles[k];
141 if (m_mix->solutionSpecies(m_species[k])) {
142 m_dsoln.push_back(1);
143 } else {
144 m_dsoln.push_back(0);
145 }
146 }
147 m_force = false;
148 updateMixMoles();
149
150 // At this point, the instance has been created, the species to be included
151 // have been determined, and an initial composition has been selected that
152 // has all non-zero mole numbers for the included species.
153}
154
155double MultiPhaseEquil::equilibrate(int XY, double err, int maxsteps, int loglevel)
156{
157 int i;
158 m_iter = 0;
159 for (i = 0; i < maxsteps; i++) {
160 stepComposition(loglevel-1);
161 if (error() < err) {
162 break;
163 }
164 }
165 if (i >= maxsteps) {
166 throw CanteraError("MultiPhaseEquil::equilibrate",
167 "no convergence in {} iterations. Error = {}",
168 maxsteps, error());
169 }
170 finish();
171 return error();
172}
173
174void MultiPhaseEquil::updateMixMoles()
175{
176 fill(m_work3.begin(), m_work3.end(), 0.0);
177 for (size_t k = 0; k < m_nsp; k++) {
178 m_work3[m_species[k]] = m_moles[k];
179 }
180 m_mix->setMoles(m_work3);
181}
182
184{
185 fill(m_work3.begin(), m_work3.end(), 0.0);
186 for (size_t k = 0; k < m_nsp; k++) {
187 m_work3[m_species[k]] = (m_moles[k] > 0.0 ? m_moles[k] : 0.0);
188 }
189 m_mix->setMoles(m_work3);
190}
191
193{
194 double not_mu = 1.0e12;
195 m_mix->getValidChemPotentials(not_mu, m_mu, true);
196 double dxi_min = 1.0e10;
197 bool redo = true;
198 int iter = 0;
199
200 while (redo) {
201 // choose a set of components based on the current composition
202 computeN();
203 redo = false;
204 iter++;
205 if (iter > 4) {
206 break;
207 }
208
209 // loop over all reactions
210 for (size_t j = 0; j < nFree(); j++) {
211 double dg_rt = 0.0;
212 dxi_min = 1.0e10;
213 for (size_t ik = 0; ik < m_nsp; ik++) {
214 dg_rt += mu(ik) * m_N(ik,j);
215 }
216
217 // fwd or rev direction
218 int idir = (dg_rt < 0.0 ? 1 : -1);
219
220 for (size_t ik = 0; ik < m_nsp; ik++) {
221 double nu = m_N(ik, j);
222
223 // set max change in progress variable by
224 // non-negativity requirement
225 // -> Note, 0.99 factor is so that difference of 2 numbers
226 // isn't zero. This causes differences between
227 // optimized and debug versions of the code
228 if (nu*idir < 0) {
229 double delta_xi = fabs(0.99*moles(ik)/nu);
230 // if a component has nearly zero moles, redo
231 // with a new set of components
232 if (!redo && delta_xi < 1.0e-10 && ik < m_nel) {
233 redo = true;
234 }
235 dxi_min = std::min(dxi_min, delta_xi);
236 }
237 }
238 // step the composition by dxi_min
239 for (size_t ik = 0; ik < m_nsp; ik++) {
240 moles(ik) += m_N(ik, j) * idir*dxi_min;
241 }
242 }
243 // set the moles of the phase objects to match
244 updateMixMoles();
245 }
246 return 0;
247}
248
249void MultiPhaseEquil::getComponents(span<const size_t> order)
250{
251 // if the input species array has the wrong size, ignore it
252 // and consider the species for components in declaration order.
253 if (order.size() != m_nsp) {
254 for (size_t k = 0; k < m_nsp; k++) {
255 m_order[k] = k;
256 }
257 } else {
258 for (size_t k = 0; k < m_nsp; k++) {
259 m_order[k] = order[k];
260 }
261 }
262
263 size_t nRows = m_nel;
264 size_t nColumns = m_nsp;
265
266 // set up the atomic composition matrix
267 for (size_t m = 0; m < nRows; m++) {
268 for (size_t k = 0; k < nColumns; k++) {
269 m_A(m, k) = m_mix->nAtoms(m_species[m_order[k]], m_element[m]);
270 }
271 }
272
273 // Do Gaussian elimination
274 for (size_t m = 0; m < nRows; m++) {
275 // Check for rows that are zero
276 bool isZeroRow = true;
277 for (size_t k = m; k < nColumns; k++) {
278 if (fabs(m_A(m,k)) > sqrt(Tiny)) {
279 isZeroRow = false;
280 break;
281 }
282 }
283 if (isZeroRow) {
284 // Find the last non-zero row
285 size_t n = nRows - 1;
286 bool foundSwapCandidate = false;
287 for (; n > m; n--) {
288 for (size_t k = m; k < nColumns; k++) {
289 if (fabs(m_A(n,k)) > sqrt(Tiny)) {
290 foundSwapCandidate = true;
291 break;
292 }
293 }
294 if (foundSwapCandidate) {
295 break;
296 }
297 }
298 if (m != n) {
299 // Swap this row with the last non-zero row
300 for (size_t k = 0; k < nColumns; k++) {
301 std::swap(m_A(n,k), m_A(m,k));
302 }
303 } else {
304 // All remaining rows are zero. Elimination is complete.
305 break;
306 }
307 }
308
309 // If a pivot is zero, exchange columns. This occurs when a species has
310 // an elemental composition that is not linearly independent of the
311 // component species that have already been assigned
312 if (m < nColumns && m_A(m,m) == 0.0) {
313 // First, we need to find a good candidate for a component species
314 // to swap in for the one that has zero pivot. It must contain
315 // element m, be linearly independent of the components processed so
316 // far (m_A(m,k) != 0), and should be a major species if possible.
317 // We'll choose the species with greatest mole fraction that
318 // satisfies these criteria.
319 double maxmoles = -999.0;
320 size_t kmax = 0;
321 for (size_t k = m+1; k < nColumns; k++) {
322 if (m_A(m,k) != 0.0 && fabs(m_moles[m_order[k]]) > maxmoles) {
323 kmax = k;
324 maxmoles = fabs(m_moles[m_order[k]]);
325 }
326 }
327
328 // Now exchange the column with zero pivot with the
329 // column for this major species
330 for (size_t n = 0; n < nRows; n++) {
331 std::swap(m_A(n, m), m_A(n, kmax));
332 }
333
334 // exchange the species labels on the columns
335 std::swap(m_order[m], m_order[kmax]);
336 }
337
338 // scale row m so that the diagonal element is unity
339 double fctr = 1.0/m_A(m,m);
340 for (size_t k = 0; k < nColumns; k++) {
341 m_A(m,k) *= fctr;
342 }
343
344 // For all rows below the diagonal, subtract A(n,m)/A(m,m)
345 // * (row m) from row n, so that A(n,m) = 0.
346 for (size_t n = m+1; n < m_nel; n++) {
347 fctr = m_A(n,m)/m_A(m,m);
348 for (size_t k = 0; k < m_nsp; k++) {
349 m_A(n,k) -= m_A(m,k)*fctr;
350 }
351 }
352 }
353
354 // The left m_nel columns of A are now upper-diagonal. Now
355 // reduce the m_nel columns to diagonal form by back-solving
356 for (size_t m = std::min(nRows,nColumns)-1; m > 0; m--) {
357 for (size_t n = m-1; n != npos; n--) {
358 if (m_A(n,m) != 0.0) {
359 double fctr = m_A(n,m);
360 for (size_t k = m; k < m_nsp; k++) {
361 m_A(n,k) -= fctr*m_A(m,k);
362 }
363 }
364 }
365 }
366
367 // create stoichiometric coefficient matrix.
368 for (size_t n = 0; n < m_nsp; n++) {
369 if (n < m_nel) {
370 for (size_t k = 0; k < nFree(); k++) {
371 m_N(n, k) = -m_A(n, k + m_nel);
372 }
373 } else {
374 for (size_t k = 0; k < nFree(); k++) {
375 m_N(n, k) = 0.0;
376 }
377 m_N(n, n - m_nel) = 1.0;
378 }
379 }
380
381 // find reactions involving solution phase species
382 for (size_t j = 0; j < nFree(); j++) {
383 m_solnrxn[j] = false;
384 for (size_t k = 0; k < m_nsp; k++) {
385 if (m_N(k, j) != 0 && m_mix->solutionSpecies(m_species[m_order[k]])) {
386 m_solnrxn[j] = true;
387 }
388 }
389 }
390}
391
392void MultiPhaseEquil::unsort(span<double> x)
393{
394 checkArraySize("MultiPhaseEquil::unsort", x.size(), m_nsp);
395 m_work2.assign(x.begin(), x.end());
396 for (size_t k = 0; k < m_nsp; k++) {
397 x[m_order[k]] = m_work2[k];
398 }
399}
400
401void MultiPhaseEquil::step(double omega, span<double> deltaN, int loglevel)
402{
403 if (omega < 0.0) {
404 throw CanteraError("MultiPhaseEquil::step","negative omega");
405 }
406 if (deltaN.size() != m_nsp) {
407 throw CanteraError("MultiPhaseEquil::step",
408 "Expected deltaN size {}, got {}", m_nsp, deltaN.size());
409 }
410
411 for (size_t ik = 0; ik < m_nel; ik++) {
412 size_t k = m_order[ik];
413 m_lastmoles[k] = m_moles[k];
414 m_moles[k] += omega * deltaN[k];
415 }
416
417 for (size_t ik = m_nel; ik < m_nsp; ik++) {
418 size_t k = m_order[ik];
419 m_lastmoles[k] = m_moles[k];
420 if (m_majorsp[k]) {
421 m_moles[k] += omega * deltaN[k];
422 } else {
423 m_moles[k] = fabs(m_moles[k])*std::min(10.0,
424 exp(-m_deltaG_RT[ik - m_nel]));
425 }
426 }
427 updateMixMoles();
428}
429
431{
432 m_iter++;
433 double grad0 = computeReactionSteps(m_dxi);
434
435 // compute the mole fraction changes.
436 if (nFree()) {
437 multiply(m_N, m_dxi, m_work);
438 }
439
440 // change to sequential form
441 unsort(m_work);
442
443 // scale omega to keep the major species non-negative
444 double FCTR = 0.99;
445 const double MAJOR_THRESHOLD = 1.0e-12;
446 double omegamax = 1.0;
447 for (size_t ik = 0; ik < m_nsp; ik++) {
448 size_t k = m_order[ik];
449 if (ik < m_nel) {
450 FCTR = 0.99;
451 if (m_moles[k] < MAJOR_THRESHOLD) {
452 m_force = true;
453 }
454 } else {
455 FCTR = 0.9;
456 }
457 // if species k is in a multi-species solution phase, then its mole
458 // number must remain positive, unless the entire phase goes away. First
459 // we'll determine an upper bound on omega, such that all
460 if (m_dsoln[k] == 1) {
461 if ((m_moles[k] > MAJOR_THRESHOLD) || (ik < m_nel)) {
462 if (m_moles[k] < MAJOR_THRESHOLD) {
463 m_force = true;
464 }
465 double omax = m_moles[k]*FCTR/(fabs(m_work[k]) + Tiny);
466 if (m_work[k] < 0.0 && omax < omegamax) {
467 omegamax = omax;
468 if (omegamax < 1.0e-5) {
469 m_force = true;
470 }
471 }
472 m_majorsp[k] = true;
473 } else {
474 m_majorsp[k] = false;
475 }
476 } else {
477 if (m_work[k] < 0.0 && m_moles[k] > 0.0) {
478 double omax = -m_moles[k]/m_work[k];
479 if (omax < omegamax) {
480 omegamax = omax;
481 if (omegamax < 1.0e-5) {
482 m_force = true;
483 }
484 }
485 }
486 m_majorsp[k] = true;
487 }
488 }
489
490 // now take a step with this scaled omega
491 step(omegamax, m_work);
492 // compute the gradient of G at this new position in the current direction.
493 // If it is positive, then we have overshot the minimum. In this case,
494 // interpolate back.
495 double not_mu = 1.0e12;
496 m_mix->getValidChemPotentials(not_mu, m_mu);
497 double grad1 = 0.0;
498 for (size_t k = 0; k < m_nsp; k++) {
499 grad1 += m_work[k] * m_mu[m_species[k]];
500 }
501
502 double omega = omegamax;
503 if (grad1 > 0.0) {
504 omega *= fabs(grad0) / (grad1 + fabs(grad0));
505 for (size_t k = 0; k < m_nsp; k++) {
506 m_moles[k] = m_lastmoles[k];
507 }
508 step(omega, m_work);
509 }
510 return omega;
511}
512
514{
515 checkArraySize("MultiPhaseEquil::computeReactionSteps", dxi.size(), nFree());
516 vector<double> nu(m_nsp, 0.0);
517 double grad = 0.0;
518 computeN();
519 double not_mu = 1.0e12;
520 m_mix->getValidChemPotentials(not_mu, m_mu);
521
522 for (size_t j = 0; j < nFree(); j++) {
523 // get stoichiometric vector
524 getStoichVector(j, nu);
525
526 // compute Delta G
527 double dg_rt = 0.0;
528 for (size_t k = 0; k < m_nsp; k++) {
529 dg_rt += m_mu[m_species[k]] * nu[k];
530 }
531 dg_rt /= (m_temp * GasConstant);
532
533 m_deltaG_RT[j] = dg_rt;
534 double fctr = 1.0;
535
536 // if this is a formation reaction for a single-component phase,
537 // check whether reaction should be included
538 size_t k = m_order[j + m_nel];
539 if (!m_dsoln[k]) {
540 if (m_moles[k] <= 0.0 && dg_rt > 0.0) {
541 fctr = 0.0;
542 } else {
543 fctr = 0.5;
544 }
545 } else if (!m_solnrxn[j]) {
546 fctr = 1.0;
547 } else {
548 // component sum
549 double csum = 0.0;
550 for (k = 0; k < m_nel; k++) {
551 size_t kc = m_order[k];
552 double nmoles = fabs(m_mix->speciesMoles(m_species[kc])) + Tiny;
553 csum += pow(nu[kc], 2)*m_dsoln[kc]/nmoles;
554 }
555
556 // noncomponent term
557 size_t kc = m_order[j + m_nel];
558 double nmoles = fabs(m_mix->speciesMoles(m_species[kc])) + Tiny;
559 double term1 = m_dsoln[kc]/nmoles;
560
561 // sum over solution phases
562 double sum = 0.0;
563 for (size_t ip = 0; ip < m_mix->nPhases(); ip++) {
564 ThermoPhase& p = m_mix->phase(ip);
565 if (p.nSpecies() > 1) {
566 double psum = 0.0;
567 for (k = 0; k < m_nsp; k++) {
568 kc = m_species[k];
569 if (m_mix->speciesPhaseIndex(kc) == ip) {
570 psum += pow(nu[k], 2);
571 }
572 }
573 sum -= psum / (fabs(m_mix->phaseMoles(ip)) + Tiny);
574 }
575 }
576 double rfctr = term1 + csum + sum;
577 if (fabs(rfctr) < Tiny) {
578 fctr = 1.0;
579 } else {
580 fctr = 1.0/(term1 + csum + sum);
581 }
582 }
583 dxi[j] = -fctr*dg_rt;
584
585 for (size_t m = 0; m < m_nel; m++) {
586 if (m_moles[m_order[m]] <= 0.0 && (m_N(m, j)*dxi[j] < 0.0)) {
587 dxi[j] = 0.0;
588 }
589 }
590 grad += dxi[j]*dg_rt;
591
592 }
593 return grad*GasConstant*m_temp;
594}
595
596void MultiPhaseEquil::computeN()
597{
598 // Sort the list of species by mole fraction (decreasing order)
599 vector<pair<double, size_t>> moleFractions(m_nsp);
600 for (size_t k = 0; k < m_nsp; k++) {
601 // use -Xk to generate reversed sort order
602 moleFractions[k] = {-m_mix->speciesMoles(m_species[k]), k};
603 }
604 std::sort(moleFractions.begin(), moleFractions.end());
605 for (size_t k = 0; k < m_nsp; k++) {
606 m_sortindex[k] = moleFractions[k].second;
607 }
608
609 for (size_t m = 0; m < m_nel; m++) {
610 size_t k = 0;
611 for (size_t ik = 0; ik < m_nsp; ik++) {
612 k = m_sortindex[ik];
613 if (m_mix->nAtoms(m_species[k],m_element[m]) != 0) {
614 break;
615 }
616 }
617 bool ok = false;
618 for (size_t ij = 0; ij < m_nel; ij++) {
619 if (k == m_order[ij]) {
620 ok = true;
621 }
622 }
623 if (!ok || m_force) {
624 getComponents(m_sortindex);
625 m_force = true;
626 break;
627 }
628 }
629}
630
631double MultiPhaseEquil::error()
632{
633 double err, maxerr = 0.0;
634
635 // examine every reaction
636 for (size_t j = 0; j < nFree(); j++) {
637 size_t ik = j + m_nel;
638
639 // don't require formation reactions for solution species
640 // present in trace amounts to be equilibrated
641 if (!isStoichPhase(ik) && fabs(moles(ik)) <= SmallNumber) {
642 err = 0.0;
643 }
644
645 // for stoichiometric phase species, no error if not present and
646 // delta G for the formation reaction is positive
647 if (isStoichPhase(ik) && moles(ik) <= 0.0 &&
648 m_deltaG_RT[j] >= 0.0) {
649 err = 0.0;
650 } else {
651 err = fabs(m_deltaG_RT[j]);
652 }
653 maxerr = std::max(maxerr, err);
654 }
655 return maxerr;
656}
657
658double MultiPhaseEquil::phaseMoles(size_t iph) const
659{
660 return m_mix->phaseMoles(iph);
661}
662
663void MultiPhaseEquil::reportCSV(const string& reportFile)
664{
665 FILE* FP = fopen(reportFile.c_str(), "w");
666 if (!FP) {
667 throw CanteraError("MultiPhaseEquil::reportCSV", "Failure to open file");
668 }
669 vector<double> mf(m_nsp_mix, 1.0);
670 vector<double> fe(m_nsp_mix, 0.0);
671 vector<double> VolPM;
672 vector<double> activity;
673 vector<double> ac;
674 vector<double> mu;
675 vector<double> mu0;
676 vector<double> molalities;
677
678 double vol = 0.0;
679 for (size_t iphase = 0; iphase < m_mix->nPhases(); iphase++) {
680 size_t istart = m_mix->speciesIndex(0, iphase);
681 ThermoPhase& tref = m_mix->phase(iphase);
682 size_t nSpecies = tref.nSpecies();
683 VolPM.resize(nSpecies, 0.0);
684 tref.getMoleFractions(span<double>(&mf[istart], tref.nSpecies()));
685 tref.getPartialMolarVolumes(VolPM);
686
687 double TMolesPhase = phaseMoles(iphase);
688 double VolPhaseVolumes = 0.0;
689 for (size_t k = 0; k < nSpecies; k++) {
690 VolPhaseVolumes += VolPM[k] * mf[istart + k];
691 }
692 VolPhaseVolumes *= TMolesPhase;
693 vol += VolPhaseVolumes;
694 }
695 fprintf(FP,"--------------------- VCS_MULTIPHASE_EQUIL FINAL REPORT"
696 " -----------------------------\n");
697 fprintf(FP,"Temperature = %11.5g kelvin\n", m_mix->temperature());
698 fprintf(FP,"Pressure = %11.5g Pascal\n", m_mix->pressure());
699 fprintf(FP,"Total Volume = %11.5g m**3\n", vol);
700
701 for (size_t iphase = 0; iphase < m_mix->nPhases(); iphase++) {
702 size_t istart = m_mix->speciesIndex(0, iphase);
703 ThermoPhase& tref = m_mix->phase(iphase);
704 ThermoPhase* tp = &tref;
705 tp->getMoleFractions(span<double>(&mf[istart], tp->nSpecies()));
706 string phaseName = tref.name();
707 double TMolesPhase = phaseMoles(iphase);
708 size_t nSpecies = tref.nSpecies();
709 activity.resize(nSpecies, 0.0);
710 ac.resize(nSpecies, 0.0);
711 mu0.resize(nSpecies, 0.0);
712 mu.resize(nSpecies, 0.0);
713 VolPM.resize(nSpecies, 0.0);
714 molalities.resize(nSpecies, 0.0);
715 int actConvention = tp->activityConvention();
716 tp->getActivities(activity);
717 tp->getActivityCoefficients(ac);
718 tp->getStandardChemPotentials(mu0);
719 tp->getPartialMolarVolumes(VolPM);
720 tp->getChemPotentials(mu);
721 double VolPhaseVolumes = 0.0;
722 for (size_t k = 0; k < nSpecies; k++) {
723 VolPhaseVolumes += VolPM[k] * mf[istart + k];
724 }
725 VolPhaseVolumes *= TMolesPhase;
726 vol += VolPhaseVolumes;
727 if (actConvention == 1) {
728 MolalityVPSSTP* mTP = static_cast<MolalityVPSSTP*>(tp);
729 mTP->getMolalities(molalities);
730 tp->getChemPotentials(mu);
731
732 if (iphase == 0) {
733 fprintf(FP," Name, Phase, PhaseMoles, Mole_Fract, "
734 "Molalities, ActCoeff, Activity,"
735 "ChemPot_SS0, ChemPot, mole_num, PMVol, Phase_Volume\n");
736
737 fprintf(FP," , , (kmol), , "
738 ", , ,"
739 " (kJ/gmol), (kJ/gmol), (kmol), (m**3/kmol), (m**3)\n");
740 }
741 for (size_t k = 0; k < nSpecies; k++) {
742 string sName = tp->speciesName(k);
743 fprintf(FP,"%12s, %11s, %11.3e, %11.3e, %11.3e, %11.3e, %11.3e,"
744 "%11.3e, %11.3e, %11.3e, %11.3e, %11.3e\n",
745 sName.c_str(),
746 phaseName.c_str(), TMolesPhase,
747 mf[istart + k], molalities[k], ac[k], activity[k],
748 mu0[k]*1.0E-6, mu[k]*1.0E-6,
749 mf[istart + k] * TMolesPhase,
750 VolPM[k], VolPhaseVolumes);
751 }
752 } else {
753 if (iphase == 0) {
754 fprintf(FP," Name, Phase, PhaseMoles, Mole_Fract, "
755 "Molalities, ActCoeff, Activity,"
756 " ChemPotSS0, ChemPot, mole_num, PMVol, Phase_Volume\n");
757
758 fprintf(FP," , , (kmol), , "
759 ", , ,"
760 " (kJ/gmol), (kJ/gmol), (kmol), (m**3/kmol), (m**3)\n");
761 }
762 for (size_t k = 0; k < nSpecies; k++) {
763 molalities[k] = 0.0;
764 }
765 for (size_t k = 0; k < nSpecies; k++) {
766 string sName = tp->speciesName(k);
767 fprintf(FP,"%12s, %11s, %11.3e, %11.3e, %11.3e, %11.3e, %11.3e, "
768 "%11.3e, %11.3e,% 11.3e, %11.3e, %11.3e\n",
769 sName.c_str(),
770 phaseName.c_str(), TMolesPhase,
771 mf[istart + k], molalities[k], ac[k],
772 activity[k], mu0[k]*1.0E-6, mu[k]*1.0E-6,
773 mf[istart + k] * TMolesPhase,
774 VolPM[k], VolPhaseVolumes);
775 }
776 }
777 }
778 fclose(FP);
779}
780
781}
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:441
size_t nSpecies() const
Returns the number of species in the phase.
Definition Phase.h:246
Base class for a phase with thermodynamic properties.
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.