Cantera  4.0.0a1
Loading...
Searching...
No Matches
BasisOptimize.cpp
Go to the documentation of this file.
1/**
2 * @file BasisOptimize.cpp Functions which calculation optimized basis of the
3 * stoichiometric coefficient matrix (see /ref equil functions)
4 */
5
6// This file is part of Cantera. See License.txt in the top-level directory or
7// at https://cantera.org/license.txt for license and copyright information.
8
11#include "cantera/base/global.h"
12
13namespace Cantera
14{
16static const double USEDBEFORE = -1;
17
18size_t BasisOptimize(bool& usedZeroedSpecies, bool doFormRxn, MultiPhase* mphase,
19 span<size_t> orderVectorSpecies, span<size_t> orderVectorElements,
20 span<double> formRxnMatrix)
21{
22 // Get the total number of elements defined in the multiphase object
23 size_t ne = mphase->nElements();
24
25 // Get the total number of species in the multiphase object
26 size_t nspecies = mphase->nSpecies();
27
28 checkArraySize("BasisOptimize: orderVectorElements", orderVectorElements.size(), ne);
29 checkArraySize("BasisOptimize: orderVectorSpecies", orderVectorSpecies.size(), nspecies);
30 checkArraySize("BasisOptimize: formRxnMatrix", formRxnMatrix.size(), nspecies * ne);
31
32 if (BasisOptimize_print_lvl >= 1) {
33 writelog(" ");
34 writeline('-', 77);
35 writelog(" --- Subroutine BASOPT called to ");
36 writelog("calculate the number of components and ");
37 writelog("evaluate the formation matrix\n");
39 writelog(" ---\n");
40
41 writelog(" --- Formula Matrix used in BASOPT calculation\n");
42 writelog(" --- Species | Order | ");
43 for (size_t j = 0; j < ne; j++) {
44 size_t jj = orderVectorElements[j];
45 writelog(" {:>4.4s}({:1d})", mphase->elementName(jj), j);
46 }
47 writelog("\n");
48 for (size_t k = 0; k < nspecies; k++) {
49 size_t kk = orderVectorSpecies[k];
50 writelog(" --- {:>11.11s} | {:4d} |",
51 mphase->speciesName(kk), k);
52 for (size_t j = 0; j < ne; j++) {
53 size_t jj = orderVectorElements[j];
54 double num = mphase->nAtoms(kk,jj);
55 writelogf("%6.1g ", num);
56 }
57 writelog("\n");
58 }
59 writelog(" --- \n");
60 }
61 }
62
63 // Calculate the maximum value of the number of components possible. It's
64 // equal to the minimum of the number of elements and the number of total
65 // species.
66 size_t nComponents = std::min(ne, nspecies);
67 size_t nNonComponents = nspecies - nComponents;
68
69 // Set this return variable to false
70 usedZeroedSpecies = false;
71
72 // Create an array of mole numbers
73 vector<double> molNum(nspecies,0.0);
74 mphase->getMoles(molNum);
75
76 // Other workspace
77 DenseMatrix sm(ne, ne);
78 vector<double> ss(ne, 0.0);
79 vector<double> sa(ne, 0.0);
80 fill(formRxnMatrix.begin(), formRxnMatrix.end(), 0.0);
81
82 // For debugging purposes keep an unmodified copy of the array.
83 vector<double> molNumBase = molNum;
84 double molSave = 0.0;
85 size_t jr = 0;
86
87 // Top of a loop of some sort based on the index JR. JR is the current
88 // number of component species found.
89 while (jr < nComponents) {
90 // Top of another loop point based on finding a linearly independent
91 // species
92 size_t k = npos;
93 while (true) {
94 // Search the remaining part of the mole number vector, molNum for
95 // the largest remaining species. Return its identity. kk is the raw
96 // number. k is the orderVectorSpecies index.
97 size_t kk = max_element(molNum.begin(), molNum.end()) - molNum.begin();
98 size_t j;
99 for (j = 0; j < nspecies; j++) {
100 if (orderVectorSpecies[j] == kk) {
101 k = j;
102 break;
103 }
104 }
105 if (j == nspecies) {
106 throw CanteraError("BasisOptimize", "orderVectorSpecies contains an error");
107 }
108
109 if (molNum[kk] == 0.0) {
110 usedZeroedSpecies = true;
111 }
112 // If the largest molNum is negative, then we are done.
113 if (molNum[kk] == USEDBEFORE) {
114 nComponents = jr;
115 nNonComponents = nspecies - nComponents;
116 break;
117 }
118
119 // Assign a small negative number to the component that we have
120 // just found, in order to take it out of further consideration.
121 molSave = molNum[kk];
122 molNum[kk] = USEDBEFORE;
123
124 // CHECK LINEAR INDEPENDENCE WITH PREVIOUS SPECIES
125
126 // Modified Gram-Schmidt Method, p. 202 Dalquist
127 // QR factorization of a matrix without row pivoting.
128 size_t jl = jr;
129 for (j = 0; j < ne; ++j) {
130 size_t jj = orderVectorElements[j];
131 sm(j, jr) = mphase->nAtoms(kk,jj);
132 }
133 if (jl > 0) {
134 // Compute the coefficients of JA column of the the upper
135 // triangular R matrix, SS(J) = R_J_JR (this is slightly
136 // different than Dalquist) R_JA_JA = 1
137 for (j = 0; j < jl; ++j) {
138 ss[j] = 0.0;
139 for (size_t i = 0; i < ne; ++i) {
140 ss[j] += sm(i, jr) * sm(i, j);
141 }
142 ss[j] /= sa[j];
143 }
144
145 // Now make the new column, (*,JR), orthogonal to the previous
146 // columns
147 for (j = 0; j < jl; ++j) {
148 for (size_t i = 0; i < ne; ++i) {
149 sm(i, jr) -= ss[j] * sm(i, j);
150 }
151 }
152 }
153
154 // Find the new length of the new column in Q.
155 // It will be used in the denominator in future row calcs.
156 sa[jr] = 0.0;
157 for (size_t ml = 0; ml < ne; ++ml) {
158 sa[jr] += pow(sm(ml, jr), 2);
159 }
160
161 // IF NORM OF NEW ROW .LT. 1E-3 REJECT
162 if (sa[jr] > 1.0e-6) {
163 break;
164 }
165 }
166
167 // REARRANGE THE DATA
168 if (jr != k) {
169 if (BasisOptimize_print_lvl >= 1) {
170 size_t kk = orderVectorSpecies[k];
171 writelogf(" --- %-12.12s", mphase->speciesName(kk));
172 size_t jj = orderVectorSpecies[jr];
173 writelogf("(%9.2g) replaces %-12.12s",
174 molSave, mphase->speciesName(jj));
175 writelogf("(%9.2g) as component %3d\n", molNum[jj], jr);
176 }
177 std::swap(orderVectorSpecies[jr], orderVectorSpecies[k]);
178 }
179
180 // If we haven't found enough components, go back and find some more
181 jr++;
182 }
183
184 if (! doFormRxn) {
185 return nComponents;
186 }
187
188 // EVALUATE THE STOICHIOMETRY
189 //
190 // Formulate the matrix problem for the stoichiometric
191 // coefficients. CX + B = 0
192 //
193 // C will be an nc x nc matrix made up of the formula vectors for the
194 // components. Each component's formula vector is a column. The rows are the
195 // elements.
196 //
197 // n RHS's will be solved for. Thus, B is an nc x n matrix.
198 //
199 // BIG PROBLEM 1/21/99:
200 //
201 // This algorithm makes the assumption that the first nc rows of the formula
202 // matrix aren't rank deficient. However, this might not be the case. For
203 // example, assume that the first element in FormulaMatrix[] is argon.
204 // Assume that no species in the matrix problem actually includes argon.
205 // Then, the first row in sm[], below will be identically zero. bleh.
206 //
207 // What needs to be done is to perform a rearrangement of the ELEMENTS ->
208 // that is, rearrange, FormulaMatrix, sp, and gai, such that the first nc
209 // elements form in combination with the nc components create an invertible
210 // sm[]. not a small project, but very doable.
211 //
212 // An alternative would be to turn the matrix problem below into an ne x nc
213 // problem, and do QR elimination instead of Gauss-Jordan elimination.
214 //
215 // Note the rearrangement of elements need only be done once in the problem.
216 // It's actually very similar to the top of this program with ne being the
217 // species and nc being the elements!!
218
219 sm.resize(nComponents, nComponents);
220 for (size_t k = 0; k < nComponents; ++k) {
221 size_t kk = orderVectorSpecies[k];
222 for (size_t j = 0; j < nComponents; ++j) {
223 size_t jj = orderVectorElements[j];
224 sm(j, k) = mphase->nAtoms(kk, jj);
225 }
226 }
227
228 for (size_t i = 0; i < nNonComponents; ++i) {
229 size_t k = nComponents + i;
230 size_t kk = orderVectorSpecies[k];
231 for (size_t j = 0; j < nComponents; ++j) {
232 size_t jj = orderVectorElements[j];
233 formRxnMatrix[j + i * ne] = - mphase->nAtoms(kk, jj);
234 }
235 }
236
237 // // Use LU factorization to calculate the reaction matrix
238 solve(sm, formRxnMatrix, nNonComponents, ne);
239
240 if (BasisOptimize_print_lvl >= 1) {
241 writelog(" ---\n");
242 writelogf(" --- Number of Components = %d\n", nComponents);
243 writelog(" --- Formula Matrix:\n");
244 writelog(" --- Components: ");
245 for (size_t k = 0; k < nComponents; k++) {
246 size_t kk = orderVectorSpecies[k];
247 writelogf(" %3d (%3d) ", k, kk);
248 }
249 writelog("\n --- Components Moles: ");
250 for (size_t k = 0; k < nComponents; k++) {
251 size_t kk = orderVectorSpecies[k];
252 writelogf("%-11.3g", molNumBase[kk]);
253 }
254 writelog("\n --- NonComponent | Moles | ");
255 for (size_t i = 0; i < nComponents; i++) {
256 size_t kk = orderVectorSpecies[i];
257 writelogf("%-11.10s", mphase->speciesName(kk));
258 }
259 writelog("\n");
260
261 for (size_t i = 0; i < nNonComponents; i++) {
262 size_t k = i + nComponents;
263 size_t kk = orderVectorSpecies[k];
264 writelogf(" --- %3d (%3d) ", k, kk);
265 writelogf("%-10.10s", mphase->speciesName(kk));
266 writelogf("|%10.3g|", molNumBase[kk]);
267 // Print the negative of formRxnMatrix[]; it's easier to interpret.
268 for (size_t j = 0; j < nComponents; j++) {
269 writelogf(" %6.2f", - formRxnMatrix[j + i * ne]);
270 }
271 writelog("\n");
272 }
273 writelog(" ");
274 writeline('-', 77);
275 }
276
277 return nComponents;
278} // basopt()
279
280
281void ElemRearrange(size_t nComponents, span<const double> elementAbundances,
282 MultiPhase* mphase, span<size_t> orderVectorSpecies,
283 span<size_t> orderVectorElements)
284{
285 size_t nelements = mphase->nElements();
286 // Get the total number of species in the multiphase object
287 size_t nspecies = mphase->nSpecies();
288
289 if (BasisOptimize_print_lvl > 0) {
290 writelog(" ");
291 writeline('-', 77);
292 writelog(" --- Subroutine ElemRearrange() called to ");
293 writelog("check stoich. coefficient matrix\n");
294 writelog(" --- and to rearrange the element ordering once\n");
295 }
296
297 checkArraySize("ElemRearrange: orderVectorElements",
298 orderVectorElements.size(), nelements);
299 checkArraySize("ElemRearrange: orderVectorSpecies",
300 orderVectorSpecies.size(), nspecies);
301
302 // If the elementAbundances aren't input, just create a fake one based on
303 // summing the column of the stoich matrix. This will force elements with
304 // zero species to the end of the element ordering.
305 vector<double> eAbund(nelements,0.0);
306 if (elementAbundances.size() != nelements) {
307 for (size_t j = 0; j < nelements; j++) {
308 eAbund[j] = 0.0;
309 for (size_t k = 0; k < nspecies; k++) {
310 eAbund[j] += fabs(mphase->nAtoms(k, j));
311 }
312 }
313 } else {
314 copy(elementAbundances.begin(), elementAbundances.end(),
315 eAbund.begin());
316 }
317
318 vector<double> sa(nelements,0.0);
319 vector<double> ss(nelements,0.0);
320 vector<double> sm(nelements*nelements,0.0);
321
322 // Top of a loop of some sort based on the index JR. JR is the current
323 // number independent elements found.
324 size_t jr = 0;
325 while (jr < nComponents) {
326 // Top of another loop point based on finding a linearly independent
327 // element
328 size_t k = nelements;
329 while (true) {
330 // Search the element vector. We first locate elements that are
331 // present in any amount. Then, we locate elements that are not
332 // present in any amount. Return its identity in K.
333 k = nelements;
334 size_t kk;
335 for (size_t ielem = jr; ielem < nelements; ielem++) {
336 kk = orderVectorElements[ielem];
337 if (eAbund[kk] != USEDBEFORE && eAbund[kk] > 0.0) {
338 k = ielem;
339 break;
340 }
341 }
342 for (size_t ielem = jr; ielem < nelements; ielem++) {
343 kk = orderVectorElements[ielem];
344 if (eAbund[kk] != USEDBEFORE) {
345 k = ielem;
346 break;
347 }
348 }
349
350 if (k == nelements) {
351 // When we are here, there is an error usually.
352 // We haven't found the number of elements necessary.
353 if (BasisOptimize_print_lvl > 0) {
354 writelogf("Error exit: returning with nComponents = %d\n", jr);
355 }
356 throw CanteraError("ElemRearrange", "Required number of elements not found.");
357 }
358
359 // Assign a large negative number to the element that we have
360 // just found, in order to take it out of further consideration.
361 eAbund[kk] = USEDBEFORE;
362
363 // CHECK LINEAR INDEPENDENCE OF CURRENT FORMULA MATRIX
364 // LINE WITH PREVIOUS LINES OF THE FORMULA MATRIX
365
366 // Modified Gram-Schmidt Method, p. 202 Dalquist
367 // QR factorization of a matrix without row pivoting.
368 size_t jl = jr;
369
370 // Fill in the row for the current element, k, under consideration
371 // The row will contain the Formula matrix value for that element
372 // with respect to the vector of component species. (note j and k
373 // indices are flipped compared to the previous routine)
374 for (size_t j = 0; j < nComponents; ++j) {
375 size_t jj = orderVectorSpecies[j];
376 kk = orderVectorElements[k];
377 sm[j + jr*nComponents] = mphase->nAtoms(jj,kk);
378 }
379 if (jl > 0) {
380 // Compute the coefficients of JA column of the the upper
381 // triangular R matrix, SS(J) = R_J_JR (this is slightly
382 // different than Dalquist) R_JA_JA = 1
383 for (size_t j = 0; j < jl; ++j) {
384 ss[j] = 0.0;
385 for (size_t i = 0; i < nComponents; ++i) {
386 ss[j] += sm[i + jr*nComponents] * sm[i + j*nComponents];
387 }
388 ss[j] /= sa[j];
389 }
390
391 // Now make the new column, (*,JR), orthogonal to the
392 // previous columns
393 for (size_t j = 0; j < jl; ++j) {
394 for (size_t i = 0; i < nComponents; ++i) {
395 sm[i + jr*nComponents] -= ss[j] * sm[i + j*nComponents];
396 }
397 }
398 }
399
400 // Find the new length of the new column in Q.
401 // It will be used in the denominator in future row calcs.
402 sa[jr] = 0.0;
403 for (size_t ml = 0; ml < nComponents; ++ml) {
404 double tmp = sm[ml + jr*nComponents];
405 sa[jr] += tmp * tmp;
406 }
407 // IF NORM OF NEW ROW .LT. 1E-6 REJECT
408 if (sa[jr] > 1.0e-6) {
409 break;
410 }
411 }
412 // REARRANGE THE DATA
413 if (jr != k) {
414 if (BasisOptimize_print_lvl > 0) {
415 size_t kk = orderVectorElements[k];
416 writelog(" --- ");
417 writelogf("%-2.2s", mphase->elementName(kk));
418 writelog("replaces ");
419 kk = orderVectorElements[jr];
420 writelogf("%-2.2s", mphase->elementName(kk));
421 writelogf(" as element %3d\n", jr);
422 }
423 std::swap(orderVectorElements[jr], orderVectorElements[k]);
424 }
425
426 // If we haven't found enough components, go back and find some more
427 jr++;
428 };
429}
430
431}
Headers for the MultiPhase object that is used to set up multiphase equilibrium problems (see Chemica...
Base class for exceptions thrown by Cantera classes.
A class for full (non-sparse) matrices with Fortran-compatible data storage, which adds matrix operat...
Definition DenseMatrix.h:42
void resize(size_t n, size_t m, double v=0.0) override
Resize the matrix.
A class for multiphase mixtures.
Definition MultiPhase.h:62
void getMoles(span< double > molNum) const
Get the mole numbers of all species in the multiphase object.
double nAtoms(const size_t kGlob, const size_t mGlob) const
Returns the Number of atoms of global element mGlob in global species kGlob.
size_t nSpecies() const
Number of species, summed over all phases.
Definition MultiPhase.h:116
string speciesName(size_t kGlob) const
Name of species with global index kGlob.
size_t nElements() const
Number of elements.
Definition MultiPhase.h:86
string elementName(size_t m) const
Returns the name of the global element m.
This file contains definitions for utility functions and text for modules, inputfiles and logging,...
size_t BasisOptimize(bool &usedZeroedSpecies, bool doFormRxn, MultiPhase *mphase, span< size_t > orderVectorSpecies, span< size_t > orderVectorElements, span< double > formRxnMatrix)
Choose the optimum basis of species for the equilibrium calculations.
void ElemRearrange(size_t nComponents, span< const double > elementAbundances, MultiPhase *mphase, span< size_t > orderVectorSpecies, span< size_t > orderVectorElements)
Handles the potential rearrangement of the constraint equations represented by the Formula Matrix.
void writelogf(const char *fmt, const Args &... args)
Write a formatted message to the screen.
Definition global.h:191
void writelog(const string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition global.h:171
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
void solve(DenseMatrix &A, span< double > b, size_t nrhs, size_t ldb)
Solve Ax = b. Array b is overwritten on exit with x.
int BasisOptimize_print_lvl
External int that is used to turn on debug printing for the BasisOptimize program.
void checkArraySize(const char *procedure, size_t available, size_t required)
Wrapper for throwing ArraySizeError.
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...