https://mooseframework.inl.gov
Loading...
Searching...
No Matches
PertinentGeochemicalSystem.C
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://mooseframework.inl.gov
3//*
4//* All rights reserved, see COPYRIGHT for full restrictions
5//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6//*
7//* Licensed under LGPL 2.1, please see LICENSE for details
8//* https://www.gnu.org/licenses/lgpl-2.1.html
9
11
14 const std::vector<std::string> & basis_species,
15 const std::vector<std::string> & minerals,
16 const std::vector<std::string> & gases,
17 const std::vector<std::string> & kinetic_minerals,
18 const std::vector<std::string> & kinetic_redox,
19 const std::vector<std::string> & kinetic_surface_species,
20 const std::string & redox_ox,
21 const std::string & redox_e)
22 : _db(db),
23 _basis_index(),
24 _basis_info(),
25 _mineral_index(),
26 _mineral_info(),
27 _gas_index(),
28 _gas_info(),
29 _kinetic_mineral_index(),
30 _kinetic_mineral_info(),
31 _kinetic_redox_index(),
32 _kinetic_redox_info(),
33 _kinetic_surface_index(),
34 _kinetic_surface_info(),
35 _secondary_index(),
36 _secondary_info(),
37 _redox_ox(redox_ox),
38 _redox_e(redox_e),
39 _model(_db)
40{
41 // Use the constructor info to build the "index" and "info" structures
42 buildBasis(basis_species);
43 buildMinerals(minerals);
44 buildGases(gases);
45 buildKineticMinerals(kinetic_minerals);
46 buildKineticRedox(kinetic_redox);
47 buildKineticSurface(kinetic_surface_species);
48
49 // Pull out all secondary equilibrium species: these are all relevant "redox couples" and
50 // "secondary species" and "surface species"
52
53 // Build minerals in the case that minerals = {"*"}
54 buildAllMinerals(minerals);
55
56 // Check that everything can be expressed in terms of the basis, possibly via redox and secondary
57 // species
59 checkGases();
63
64 // Populate _model
66}
67
68unsigned
70{
71 try
72 {
73 return _basis_index.at(name);
74 }
75 catch (const std::out_of_range &)
76 {
77 mooseError("species ", name, " is not in the original basis");
78 }
79 catch (...)
80 {
81 throw;
82 }
83}
84
85std::vector<std::string>
87{
88 std::vector<std::string> names(_basis_info.size());
89 for (const auto & name_ind : _basis_index)
90 names[name_ind.second] = name_ind.first;
91 return names;
92}
93
94void
95PertinentGeochemicalSystem::buildBasis(const std::vector<std::string> & basis_species)
96{
97 unsigned ind = 0;
98 for (const auto & name : basis_species)
99 {
100 if (ind == 0 and name != "H2O")
101 mooseError("First member of basis species list must be H2O");
102 if (_basis_index.count(name) == 1)
103 mooseError(name, " exists more than once in the basis species list");
104 _basis_index.emplace(name, ind);
105 ind += 1;
107 _basis_info.push_back(_db.getBasisSpecies({name})[name]);
108 else if (_db.isRedoxSpecies(name))
109 {
112 bs.name = rs.name;
113 bs.radius = rs.radius;
114 bs.charge = rs.charge;
116 _basis_info.push_back(bs);
117 }
118 else
119 mooseError(name + " does not exist in the basis species or redox species in " +
120 _db.filename());
121 }
122}
123
124void
125PertinentGeochemicalSystem::buildMinerals(const std::vector<std::string> & minerals)
126{
127 unsigned ind = 0;
128 if (minerals.size() == 1 && minerals[0] == "*")
129 return; // buildAllMinerals is called later
130 for (const auto & name : minerals)
131 {
132 if (_mineral_index.count(name) == 1)
133 mooseError(name + " exists more than once in the minerals list");
134 _mineral_index.emplace(name, ind);
135 ind += 1;
136 _mineral_info.push_back(_db.getMineralSpecies({name})[name]);
137 }
138}
139
140void
141PertinentGeochemicalSystem::buildGases(const std::vector<std::string> & gases)
142{
143 unsigned ind = 0;
144 for (const auto & name : gases)
145 {
146 if (_gas_index.count(name) == 1)
147 mooseError(name + " exists more than once in the gases list");
148 _gas_index.emplace(name, ind);
149 ind += 1;
151 _gas_info.push_back(gas);
152 }
153}
154
155void
156PertinentGeochemicalSystem::buildKineticMinerals(const std::vector<std::string> & kinetic_minerals)
157{
158 unsigned ind = 0;
159 for (const auto & name : kinetic_minerals)
160 {
161 if (_kinetic_mineral_index.count(name) == 1)
162 mooseError(name + " exists more than once in the kinetic_minerals list");
163 if (_mineral_index.count(name) == 1)
164 mooseError(name + " exists in both the minerals and kinetic_minerals lists");
165 _kinetic_mineral_index.emplace(name, ind);
166 ind += 1;
168 }
169}
170
171void
172PertinentGeochemicalSystem::buildKineticRedox(const std::vector<std::string> & kinetic_redox)
173{
174 unsigned ind = 0;
175 for (const auto & name : kinetic_redox)
176 {
177 if (_kinetic_redox_index.count(name) == 1)
178 mooseError(name + " exists more than once in the kinetic_redox list");
179 if (_basis_index.count(name) == 1)
180 mooseError(name + " exists in both the basis_species and kinetic_redox lists");
181 _kinetic_redox_index.emplace(name, ind);
182 ind += 1;
183 _kinetic_redox_info.push_back(_db.getRedoxSpecies({name})[name]);
184 }
185}
186
187void
189 const std::vector<std::string> & kinetic_surface_species)
190{
191 unsigned ind = 0;
192 for (const auto & name : kinetic_surface_species)
193 {
194 if (_kinetic_surface_index.count(name) == 1)
195 mooseError(name + " exists more than once in the kinetic_surface_species list");
196 _kinetic_surface_index.emplace(name, ind);
197 ind += 1;
199 }
200}
201
202void
204{
205 // run through all redox couples, including them if:
206 // - they are not part of the kinetic_redox list
207 // - they are not part of the basis_species list
208 // - their reaction involves only basis_species or secondary species already encountered
209 unsigned ind = 0;
210 for (const auto & name : _db.redoxCoupleNames())
211 {
212 if (_kinetic_redox_index.count(name) == 0 && _basis_index.count(name) == 0)
213 {
215 // check all reaction species are in the basis
216 bool all_species_in_basis_or_sec = true;
217 for (const auto & element : rs.basis_species)
218 if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
219 {
220 all_species_in_basis_or_sec = false;
221 break;
222 }
223 if (all_species_in_basis_or_sec)
224 {
225 _secondary_index.emplace(name, ind);
226 ind += 1;
228 ss.name = rs.name;
231 ss.radius = rs.radius;
232 ss.charge = rs.charge;
234 _secondary_info.push_back(ss);
235 }
236 }
237 }
238
239 // run through all secondary species, including them if:
240 // - their reaction involves only basis_species, or secondary species already encountered
241 // - the name is not _redox_e (which is usually "e-" the free electron)
242 for (const auto & name : _db.secondarySpeciesNames())
243 {
244 if (name == _redox_e)
245 continue;
247 // check all reaction species are in the basis
248 bool all_species_in_basis_or_sec = true;
249 for (const auto & element : ss.basis_species)
250 if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
251 {
252 all_species_in_basis_or_sec = false;
253 break;
254 }
255 if (all_species_in_basis_or_sec)
256 {
257 _secondary_index.emplace(name, ind);
258 ind += 1;
259 _secondary_info.push_back(ss);
260 }
261 }
262
263 // run through all surface species, including them if:
264 // - their reaction involves only basis_species or secondary species encountered so far
265 // - they are not in the kinetic_surface_species list
266 for (const auto & name : _db.surfaceSpeciesNames())
267 {
268 if (_kinetic_surface_index.count(name) == 0)
269 {
271 // check all reaction species are in the basis
272 bool all_species_in_basis_or_sec = true;
273 for (const auto & element : ss.basis_species)
274 if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
275 {
276 all_species_in_basis_or_sec = false;
277 break;
278 }
279 if (all_species_in_basis_or_sec)
280 {
282 to_sec.name = ss.name;
283 to_sec.basis_species = ss.basis_species;
284 to_sec.radius = -1.5; // flag to activity calculators that activity coefficient = 1
285 to_sec.charge = ss.charge;
287 const Real T0 = _db.getTemperatures()[0];
288 for (const auto & temp : _db.getTemperatures())
289 to_sec.equilibrium_const.push_back(ss.log10K + ss.dlog10KdT * (temp - T0));
290
291 _secondary_index.emplace(name, ind);
292 ind += 1;
293 _secondary_info.push_back(to_sec);
294 }
295 }
296 }
297}
298
299void
300PertinentGeochemicalSystem::buildAllMinerals(const std::vector<std::string> & minerals)
301{
302 if (!(minerals.size() == 1 && minerals[0] == "*"))
303 return; // buildMinerals has done its job of building _mineral_info and _mineral_index
304 unsigned ind = 0;
305 for (const auto & name_ms : _db.getMineralSpecies(_db.mineralSpeciesNames()))
306 {
307 if (_kinetic_mineral_index.count(name_ms.first) == 1)
308 continue;
309 bool known_basis_only = true;
310 for (const auto & basis_stoi : name_ms.second.basis_species)
311 {
312 if (_basis_index.count(basis_stoi.first) == 0 &&
313 _secondary_index.count(basis_stoi.first) == 0)
314 {
315 known_basis_only = false;
316 break;
317 }
318 }
319 if (known_basis_only)
320 {
321 _mineral_index.emplace(name_ms.first, ind);
322 ind += 1;
323 _mineral_info.push_back(name_ms.second);
324 }
325 }
326}
327
328bool
330{
331 bool found = false;
332 for (const auto & name : _db.secondarySpeciesNames())
333 if (name == _redox_e)
334 {
335 found = true;
337 for (const auto & element : ss.basis_species)
338 if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
339 return false;
340 }
341 return found;
342}
343
344void
345PertinentGeochemicalSystem::buildRedoxeInfo(std::vector<Real> & redox_e_stoichiometry,
346 std::vector<Real> & redox_e_log10K)
347{
348 const unsigned num_basis = _basis_info.size();
349 const unsigned numT = _db.getTemperatures().size();
350 redox_e_stoichiometry.assign(num_basis, 0.0);
351 redox_e_log10K.assign(numT, 0.0);
352 for (const auto & name : _db.secondarySpeciesNames())
353 if (name == _redox_e)
354 {
356 for (unsigned i = 0; i < numT; ++i)
357 redox_e_log10K[i] = ss.equilibrium_const[i];
358 for (const auto & react : ss.basis_species)
359 {
360 const Real stoi_coeff = react.second;
361 if (_model.basis_species_index.count(react.first) == 1)
362 {
363 const unsigned col = _model.basis_species_index[react.first];
364 redox_e_stoichiometry[col] += react.second;
365 }
366 else if (_secondary_index.count(react.first) == 1)
367 {
368 // reaction species is not a basis component, but a secondary component.
369 // So express stoichiometry in terms of the secondary component's reaction
370 const unsigned sec_row = _model.eqm_species_index[react.first];
371 for (unsigned i = 0; i < numT; ++i)
372 redox_e_log10K[i] += stoi_coeff * _model.eqm_log10K(sec_row, i);
373 for (unsigned col = 0; col < num_basis; ++col)
374 redox_e_stoichiometry[col] += stoi_coeff * _model.eqm_stoichiometry(sec_row, col);
375 }
376 }
377 }
378}
379
380void
382 const std::vector<GeochemistryMineralSpecies> & mineral_info) const
383{
384 for (const auto & mineral : mineral_info)
385 {
386 for (const auto & element : mineral.basis_species)
387 if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
388 mooseError("The reaction for " + mineral.name + " depends on " + element.first +
389 " which is not reducable to a set of basis species");
390 for (const auto & element : mineral.sorption_sites)
391 if (_basis_index.count(element.first) == 0)
392 mooseError("The sorbing sites for " + mineral.name + " include " + element.first +
393 " which is not in the basis_species list");
394 }
395}
396
397void
399{
400 for (const auto & gas : _gas_info)
401 for (const auto & element : gas.basis_species)
402 if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
403 mooseError("The reaction for " + gas.name + " depends on " + element.first +
404 " which is not reducable to a set of basis species");
405}
406
407void
409{
410 for (const auto & kr : _kinetic_redox_info)
411 for (const auto & element : kr.basis_species)
412 if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
413 mooseError("The reaction for " + kr.name + " depends on " + element.first +
414 " which is not reducable to a set of basis species");
415}
416
417void
419{
420 for (const auto & kr : _kinetic_surface_info)
421 for (const auto & element : kr.basis_species)
422 if (_basis_index.count(element.first) == 0 && _secondary_index.count(element.first) == 0)
423 mooseError("The reaction for " + kr.name + " depends on " + element.first +
424 " which is not reducable to a set of basis species");
425}
426
427void
429{
430 const unsigned num_rows = _secondary_info.size() + _mineral_info.size() + _gas_info.size();
431 const unsigned num_cols = _basis_info.size();
432 const unsigned num_temperatures = _db.getTemperatures().size();
433 unsigned ind = 0;
434
435 // create basis_species_index map
437
438 // extract the names
439 _model.basis_species_name = std::vector<std::string>(num_cols);
440 for (const auto & species : _basis_info)
441 _model.basis_species_name[_model.basis_species_index[species.name]] = species.name;
442
443 // initially no basis species are minerals
444 _model.basis_species_mineral = std::vector<bool>(num_cols, false);
445
446 // initially no basis species are gases
447 _model.basis_species_gas = std::vector<bool>(num_cols, false);
448
449 // initially all basis species are involved in transport (this gets modified for surface species
450 // below)
451 _model.basis_species_transported = std::vector<bool>(num_cols, true);
452
453 // record the charge
454 _model.basis_species_charge = std::vector<Real>(num_cols, 0.0);
455 for (const auto & species : _basis_info)
456 _model.basis_species_charge[_model.basis_species_index[species.name]] = species.charge;
457
458 // record the ionic radius
459 _model.basis_species_radius = std::vector<Real>(num_cols, 0.0);
460 for (const auto & species : _basis_info)
461 _model.basis_species_radius[_model.basis_species_index[species.name]] = species.radius;
462
463 // record the molecular weight
464 _model.basis_species_molecular_weight = std::vector<Real>(num_cols, 0.0);
465 for (const auto & species : _basis_info)
467 species.molecular_weight;
468
469 // record the molecular weight (zero for all species except minerals)
470 _model.basis_species_molecular_volume = std::vector<Real>(num_cols, 0.0);
471
472 // the "eqm_species" stuff is rather long-winded because of the different data structures used
473 // to hold secondary, mineral and gas info. There is a bit of an overlap, however, so let's
474 // create a new data structure that contains that overlapping info
475
476 std::vector<GeochemistryEquilibriumSpecies> overlap(_secondary_info);
477 for (const auto & species : _mineral_info)
478 {
480 es.name = species.name;
481 es.molecular_weight = species.molecular_weight;
482 es.equilibrium_const = species.equilibrium_const;
483 es.basis_species = species.basis_species;
484 overlap.push_back(es);
485 }
486 for (const auto & species : _gas_info)
487 {
489 es.name = species.name;
490 es.molecular_weight = species.molecular_weight;
491 es.equilibrium_const = species.equilibrium_const;
492 es.basis_species = species.basis_species;
493 overlap.push_back(es);
494 }
495
496 // create the eqm_species_index map
497 ind = 0;
498 for (const auto & species : overlap)
499 _model.eqm_species_index[species.name] = ind++;
500
501 // extract the names
502 _model.eqm_species_name = std::vector<std::string>(num_rows);
503 for (const auto & species : _model.eqm_species_index)
504 _model.eqm_species_name[species.second] = species.first;
505
506 // create the eqm_species_mineral vector
507 _model.eqm_species_mineral = std::vector<bool>(num_rows, false);
508 for (const auto & species : _mineral_info)
510 for (const auto & species : _gas_info)
511 _model.eqm_species_mineral[_model.eqm_species_index[species.name]] = false;
512
513 // create the eqm_species_gas vector
514 _model.eqm_species_gas = std::vector<bool>(num_rows, false);
515 for (const auto & species : _mineral_info)
516 _model.eqm_species_gas[_model.eqm_species_index[species.name]] = false;
517 for (const auto & species : _gas_info)
518 _model.eqm_species_gas[_model.eqm_species_index[species.name]] = true;
519
520 // create the eqm_species_transported vector (true for non-minerals) - gets modified below for
521 // surface species
522 _model.eqm_species_transported = std::vector<bool>(num_rows, true);
523 for (const auto & species : _mineral_info)
524 _model.eqm_species_transported[_model.eqm_species_index.at(species.name)] = false;
525
526 // record the charge
528 std::vector<Real>(num_rows, 0.0); // charge of gases and minerals is zero
529 for (const auto & species : _secondary_info)
530 _model.eqm_species_charge[_model.eqm_species_index[species.name]] = species.charge;
531
532 // record the radius
534 std::vector<Real>(num_rows, 0.0); // ionic radius of gases and minerals is zero
535 for (const auto & species : _secondary_info)
536 _model.eqm_species_radius[_model.eqm_species_index[species.name]] = species.radius;
537
538 // record the molecular weight
539 _model.eqm_species_molecular_weight = std::vector<Real>(num_rows, 0.0);
540 for (const auto & species : overlap)
542 species.molecular_weight;
543
544 // record the molecular volume (zero for all species except minerals)
545 _model.eqm_species_molecular_volume = std::vector<Real>(num_rows, 0.0);
546 for (const auto & species : _mineral_info)
548 species.molecular_volume;
549
550 // record surface-complexation info
551 for (const auto & species : _mineral_info)
552 if (species.surface_area != 0.0)
553 {
555 sci.surface_area = species.surface_area;
556 sci.sorption_sites = species.sorption_sites;
557 _model.surface_complexation_info[species.name] = sci;
558 }
559 for (const auto & species : _kinetic_mineral_info)
560 if (species.surface_area != 0.0)
561 {
563 sci.surface_area = species.surface_area;
564 sci.sorption_sites = species.sorption_sites;
565 _model.surface_complexation_info[species.name] = sci;
566 }
567
568 // record gas fugacity info
569 for (const auto & species : _gas_info)
570 _model.gas_chi[species.name] = species.chi;
571
572 // create the stoichiometry matrix
573 _model.eqm_stoichiometry.resize(num_rows, num_cols);
574 _model.eqm_log10K.resize(num_rows, num_temperatures);
575
576 // populate the stoichiometry
577 for (const auto & species : overlap)
578 {
579 const unsigned row = _model.eqm_species_index[species.name];
580 for (unsigned i = 0; i < num_temperatures; ++i)
581 _model.eqm_log10K(row, i) = species.equilibrium_const[i];
582 for (const auto & react : species.basis_species)
583 {
584 const Real stoi_coeff = react.second;
585 if (_model.basis_species_index.count(react.first) == 1)
586 {
587 const unsigned col = _model.basis_species_index[react.first];
588 _model.eqm_stoichiometry(row, col) += react.second;
589 }
590 else if (_secondary_index.count(react.first) == 1)
591 {
592 // reaction species is not a basis component, but a secondary component.
593 // So express stoichiometry in terms of the secondary component's reaction
594 const unsigned sec_row = _model.eqm_species_index[react.first];
595 for (unsigned i = 0; i < num_temperatures; ++i)
596 _model.eqm_log10K(row, i) += stoi_coeff * _model.eqm_log10K(sec_row, i);
597 for (unsigned col = 0; col < num_cols; ++col)
598 _model.eqm_stoichiometry(row, col) += stoi_coeff * _model.eqm_stoichiometry(sec_row, col);
599 }
600 else
601 mooseError("Species " + species.name + " includes " + react.first +
602 ", which cannot be expressed in terms of the basis. Previous checks must be "
603 "erroneous!");
604 }
605 }
606
607 // Build the redox information, if any. Here we express any O2(aq) in the redox equations in
608 // terms of redox_e (which is usually e-)
610 std::vector<Real> redox_e_stoichiometry(num_cols, 0.0);
611 std::vector<Real> redox_e_log10K(num_temperatures, 0.0);
612 std::vector<Real> redox_stoi;
613 std::vector<Real> redox_log10K;
614 if ((_model.basis_species_index.count(_redox_ox) == 1) && checkRedoxe())
615 {
616 // construct the stoichiometry and log10K for _redox_e and put it
617 buildRedoxeInfo(redox_e_stoichiometry, redox_e_log10K);
618 redox_stoi.insert(redox_stoi.end(), redox_e_stoichiometry.begin(), redox_e_stoichiometry.end());
619 redox_log10K.insert(redox_log10K.end(), redox_e_log10K.begin(), redox_e_log10K.end());
620 // the electron reaction is
621 // e- = nuw_i * basis_i + beta * O2(aq), where we've pulled out the O2(aq) because it's
622 // special
623 const unsigned o2_index = _model.basis_species_index.at(_redox_ox);
624 const Real beta = redox_e_stoichiometry[o2_index];
625 if (beta != 0.0)
626 {
627 for (const auto & bs : _model.basis_species_index)
628 if (_db.isRedoxSpecies(bs.first))
629 {
630 // this basis species is a redox couple in disequilibrium
631 const GeochemistryRedoxSpecies rs = _db.getRedoxSpecies({bs.first})[bs.first];
632 // check that its reaction involves only basis species, and record the stoichiometry in
633 // the current basis
634 std::vector<Real> stoi(num_cols, 0.0);
635 bool only_involves_basis_species = true;
636 for (const auto & name_stoi : rs.basis_species)
637 {
638 if (_model.basis_species_index.count(name_stoi.first) == 1)
639 stoi[_model.basis_species_index.at(name_stoi.first)] = name_stoi.second;
640 else
641 {
642 only_involves_basis_species = false;
643 break;
644 }
645 }
646 if (!only_involves_basis_species)
647 continue;
648 // Reaction is now
649 // redox = nu_i * basis_i + alpha * O2(aq), where we've pulled the O2(aq) out because
650 // it's special. Now pull the redox couple to the RHS of the reaction, so we have 0 =
651 // -redox + nu_i * basis_i + alpha * O2(aq)
652 stoi[bs.second] = -1.0;
653 // check that the stoichiometry involves O2(aq)
654 const Real alpha = stoi[o2_index];
655 if (alpha == 0.0)
656 continue;
657 // multiply equation -beta/alpha so it reads
658 // 0 = -beta/alpha * (-redox + nu_i * basis_i) - beta * O2(aq)
659 for (unsigned basis_i = 0; basis_i < num_cols; ++basis_i)
660 stoi[basis_i] *= -beta / alpha;
661 // add the equation to e- = nuw_i * basis_i + beta * O2(aq)
662 for (unsigned basis_i = 0; basis_i < num_cols; ++basis_i)
663 stoi[basis_i] += redox_e_stoichiometry[basis_i];
664 // now the reation is e- = nuw_i * basis_i - beta/alpha * (-redox + nu_i * basis_i)
665 redox_stoi.insert(redox_stoi.end(), stoi.begin(), stoi.end());
666
667 // record the equilibrium constants
668 for (unsigned temp = 0; temp < num_temperatures; ++temp)
669 redox_log10K.push_back((-beta / alpha) * rs.equilibrium_const[temp] +
670 redox_e_log10K[temp]);
671 }
672 }
673 }
674 // record the above in the model.redox_stoichiometry and model.redox_log10K DenseMatrices
675 const unsigned num_redox = redox_stoi.size() / num_cols;
676 _model.redox_stoichiometry.resize(num_redox, num_cols);
677 for (unsigned red = 0; red < num_redox; ++red)
678 for (unsigned basis_i = 0; basis_i < num_cols; ++basis_i)
679 _model.redox_stoichiometry(red, basis_i) = redox_stoi[red * num_cols + basis_i];
680 _model.redox_log10K.resize(num_redox, num_temperatures);
681 for (unsigned red = 0; red < num_redox; ++red)
682 for (unsigned temp = 0; temp < num_temperatures; ++temp)
683 _model.redox_log10K(red, temp) = redox_log10K[red * num_temperatures + temp];
684
685 // To build the kin_species_index, kin_species_name, etc, let's build an "overlap", similar to
686 // above
687 std::vector<GeochemistryMineralSpecies> overlap_kin(_kinetic_mineral_info);
688 for (const auto & species : _kinetic_redox_info)
689 {
691 ms.name = species.name;
692 ms.molecular_volume = 0.0;
693 ms.basis_species = species.basis_species;
694 ms.molecular_weight = species.molecular_weight;
695 ms.equilibrium_const = species.equilibrium_const;
696 overlap_kin.push_back(ms);
697 }
698 for (const auto & species : _kinetic_surface_info)
699 {
701 ms.name = species.name;
702 ms.molecular_volume = 0.0;
703 ms.basis_species = species.basis_species;
704 ms.molecular_weight = species.molecular_weight;
705 const Real T0 = _db.getTemperatures()[0];
706 for (const auto & temp : _db.getTemperatures())
707 ms.equilibrium_const.push_back(species.log10K + species.dlog10KdT * (temp - T0));
708 overlap_kin.push_back(ms);
709 }
710 const unsigned num_kin = overlap_kin.size();
711
712 // create the kin_species_index map
713 ind = 0;
714 for (const auto & species : overlap_kin)
715 _model.kin_species_index[species.name] = ind++;
716
717 // extract the names
718 _model.kin_species_name = std::vector<std::string>(num_kin);
719 for (const auto & species : _model.kin_species_index)
720 _model.kin_species_name[species.second] = species.first;
721
722 // build the kin_species_mineral info
723 _model.kin_species_mineral = std::vector<bool>(num_kin, true);
724 for (const auto & species : _kinetic_redox_info)
725 _model.kin_species_mineral[_model.kin_species_index[species.name]] = false;
726 for (const auto & species : _kinetic_surface_info)
727 _model.kin_species_mineral[_model.kin_species_index[species.name]] = false;
728
729 // create the kin_species_transported vector (false for minerals and surface species)
730 _model.kin_species_transported = std::vector<bool>(num_kin, true);
731 for (const auto & species : _kinetic_mineral_info)
732 _model.kin_species_transported[_model.kin_species_index.at(species.name)] = false;
733 for (const auto & species : _kinetic_surface_info)
734 _model.kin_species_transported[_model.kin_species_index.at(species.name)] = false;
735
736 // build the kin_species_charge info
737 _model.kin_species_charge = std::vector<Real>(num_kin, 0.0);
738 for (const auto & species : _kinetic_redox_info)
739 _model.kin_species_charge[_model.kin_species_index[species.name]] = species.charge;
740 for (const auto & species : _kinetic_surface_info)
741 _model.kin_species_charge[_model.kin_species_index[species.name]] = species.charge;
742
743 // extract the molecular weight
744 _model.kin_species_molecular_weight = std::vector<Real>(num_kin, 0.0);
745 for (const auto & species : overlap_kin)
747 species.molecular_weight;
748
749 // extract the molecular volume
750 _model.kin_species_molecular_volume = std::vector<Real>(num_kin, 0.0);
751 for (const auto & species : overlap_kin)
753 species.molecular_volume;
754
755 // extract the stoichiometry
756 _model.kin_stoichiometry.resize(num_kin, num_cols);
757 _model.kin_log10K.resize(num_kin, num_temperatures);
758
759 // populate the stoichiometry
760 for (const auto & species : overlap_kin)
761 {
762 const unsigned row = _model.kin_species_index[species.name];
763 for (unsigned i = 0; i < num_temperatures; ++i)
764 _model.kin_log10K(row, i) = species.equilibrium_const[i];
765 for (const auto & react : species.basis_species)
766 {
767 const Real stoi_coeff = react.second;
768 if (_model.basis_species_index.count(react.first) == 1)
769 {
770 const unsigned col = _model.basis_species_index[react.first];
771 _model.kin_stoichiometry(row, col) += react.second;
772 }
773 else if (_model.eqm_species_index.count(react.first) == 1)
774 {
775 // reaction species is not a basis component, but a secondary component.
776 // So express stoichiometry in terms of the secondary component's reaction
777 const unsigned sec_row = _model.eqm_species_index[react.first];
778 for (unsigned i = 0; i < num_temperatures; ++i)
779 _model.kin_log10K(row, i) += stoi_coeff * _model.eqm_log10K(sec_row, i);
780 for (unsigned col = 0; col < num_cols; ++col)
781 _model.kin_stoichiometry(row, col) += stoi_coeff * _model.eqm_stoichiometry(sec_row, col);
782 }
783 else
784 mooseError("Kinetic species " + species.name + " includes " + react.first +
785 ", which cannot be expressed in terms of the basis. Previous checks must be "
786 "erroneous!");
787 }
788 }
789
790 // check that there are no repeated sorbing sites in the SurfaceComplexationInfo
791 std::vector<std::string> all_sorbing_sites;
792 for (const auto & name_info : _model.surface_complexation_info)
793 for (const auto & name_frac : name_info.second.sorption_sites)
794 if (std::find(all_sorbing_sites.begin(), all_sorbing_sites.end(), name_frac.first) !=
795 all_sorbing_sites.end())
797 "The sorbing site ", name_frac.first, " appears in more than one sorbing mineral");
798 else
799 all_sorbing_sites.push_back(name_frac.first);
800
801 // build the information related to surface sorption, and modify the species_transported vectors
802 _model.surface_sorption_related.assign(num_rows, false);
803 _model.surface_sorption_number.assign(num_rows, 99);
804 for (const auto & name_info :
805 _model.surface_complexation_info) // all minerals involved in surface complexation
806 {
807 for (const auto & name_frac :
808 name_info.second.sorption_sites) // all sorption sites on the given mineral
809 {
810 const unsigned basis_index_of_sorption_site = _model.basis_species_index.at(name_frac.first);
811 _model.basis_species_transported[basis_index_of_sorption_site] = false;
812 }
813 bool mineral_involved_in_eqm = false;
814 for (const auto & name_frac :
815 name_info.second.sorption_sites) // all sorption sites on the given mineral
816 {
817 const unsigned basis_index_of_sorption_site = _model.basis_species_index.at(name_frac.first);
818 for (unsigned j = 0; j < num_rows; ++j) // all equilibrium species
819 if (_model.eqm_stoichiometry(j, basis_index_of_sorption_site) != 0.0)
820 {
821 mineral_involved_in_eqm = true;
822 break;
823 }
824 }
825 if (!mineral_involved_in_eqm)
826 continue;
827 const unsigned num_surface_sorption = _model.surface_sorption_name.size();
828 _model.surface_sorption_name.push_back(name_info.first);
829 _model.surface_sorption_area.push_back(name_info.second.surface_area);
830 for (const auto & name_frac :
831 name_info.second.sorption_sites) // all sorption sites on the given mineral
832 {
833 const unsigned basis_index_of_sorption_site = _model.basis_species_index.at(name_frac.first);
834 for (unsigned j = 0; j < num_rows; ++j) // all equilibrium species
835 if (_model.eqm_stoichiometry(j, basis_index_of_sorption_site) != 0.0)
836 {
838 mooseError("It is an error for any equilibrium species (such as ",
840 ") to have a reaction involving more than one sorbing site");
842 _model.surface_sorption_number[j] = num_surface_sorption;
844 }
845 }
846 }
847}
848
854
855void
857{
858 const std::string kinetic_species = description.kinetic_species_name;
859 if (_model.kin_species_index.count(kinetic_species) == 0)
860 mooseError("Cannot prescribe a kinetic rate to species ",
861 kinetic_species,
862 " since it is not a kinetic species");
863 const unsigned kinetic_species_index = _model.kin_species_index.at(kinetic_species);
864
865 // build the promoting index list
866 const unsigned num_pro = description.promoting_species.size();
867 const unsigned num_basis = _model.basis_species_name.size();
868 const unsigned num_eqm = _model.eqm_species_name.size();
869 std::vector<Real> promoting_ind(num_basis + num_eqm, 0.0);
870 std::vector<Real> promoting_m_ind(num_basis + num_eqm, 0.0);
871 std::vector<Real> promoting_k(num_basis + num_eqm, 0.0);
872 for (unsigned i = 0; i < num_pro; ++i)
873 {
874 unsigned index = 0;
875 const std::string promoting_species = description.promoting_species[i];
876 if (_model.basis_species_index.count(promoting_species) == 1)
877 index = _model.basis_species_index.at(promoting_species);
878 else if (_model.eqm_species_index.count(promoting_species) == 1)
879 index = num_basis + _model.eqm_species_index.at(promoting_species);
880 else
882 "Promoting species ", promoting_species, " must be a basis or a secondary species");
883 promoting_ind[index] = description.promoting_indices[i];
884 promoting_m_ind[index] = description.promoting_monod_indices[i];
885 promoting_k[index] = description.promoting_half_saturation[i];
886 }
887 unsigned progeny_num = 0;
888 if (_model.basis_species_index.count(description.progeny) == 1)
889 progeny_num = _model.basis_species_index.at(description.progeny);
890 else if (_model.eqm_species_index.count(description.progeny) == 1)
891 progeny_num = num_basis + _model.eqm_species_index.at(description.progeny);
892 else
893 mooseError("Progeny ", description.progeny, " must be a basis or a secondary species");
894
895 // append the result to kin_rate
896 _model.kin_rate.push_back(KineticRateDefinition(kinetic_species_index,
897 promoting_ind,
898 promoting_m_ind,
899 promoting_k,
900 progeny_num,
901 description));
902}
const GeochemicalDatabaseReader db("database/moose_testdb.json", true, true, false)
void mooseError(Args &&... args)
const std::string name
Definition Setup.h:21
Class for reading geochemical reactions from a MOOSE geochemical database.
bool isBasisSpecies(const std::string &name) const
Checks if species is of given type.
std::map< std::string, GeochemistryGasSpecies > getGasSpecies(const std::vector< std::string > &names)
Get the gas species information.
std::vector< std::string > secondarySpeciesNames() const
Returns a list of all the names of the "secondary species" and "free electron" in the database.
std::map< std::string, GeochemistryEquilibriumSpecies > getEquilibriumSpecies(const std::vector< std::string > &names)
Get the secondary equilibrium species information.
std::map< std::string, GeochemistryRedoxSpecies > getRedoxSpecies(const std::vector< std::string > &names)
Get the redox species (couples) information.
std::vector< std::string > mineralSpeciesNames() const
Returns a list of all the names of the "mineral species" in the database.
std::map< std::string, GeochemistryBasisSpecies > getBasisSpecies(const std::vector< std::string > &names)
Get the basis (primary) species information.
bool isRedoxSpecies(const std::string &name) const
const FileName & filename() const
Filename of database.
std::vector< std::string > surfaceSpeciesNames() const
Returns a list of all the names of the "surface species" in the database.
const std::vector< Real > & getTemperatures() const
Get the temperature points that the equilibrium constant is defined at.
std::map< std::string, GeochemistrySurfaceSpecies > getSurfaceSpecies(const std::vector< std::string > &names)
Get the surface sorbing species information.
std::vector< std::string > redoxCoupleNames() const
Returns a list of all the names of the "redox couples" in the database.
std::map< std::string, GeochemistryMineralSpecies > getMineralSpecies(const std::vector< std::string > &names)
Get the mineral species information.
std::vector< GeochemistryEquilibriumSpecies > _secondary_info
a vector of all relevant species
unsigned getIndexOfOriginalBasisSpecies(const std::string &name) const
void buildKineticRedox(const std::vector< std::string > &kinetic_redox)
using the kinetic_redox list, this method builds _kinetic_redox_index and _kinetic_redox_info
void buildGases(const std::vector< std::string > &gases)
using the gas list, this method builds _gas_index and _gas_info
const std::string _redox_ox
The name of the oxygen in all disequilibrium-redox equations, eg O2(aq), which must be a basis specie...
void buildMinerals(const std::vector< std::string > &minerals)
using the minerals list, this method builds _mineral_index and _mineral_info, unless minerals = {"*"}...
std::vector< GeochemistryMineralSpecies > _kinetic_mineral_info
a vector of all relevant species
const ModelGeochemicalDatabase & modelGeochemicalDatabase() const
Return a reference to the ModelGeochemicalDatabase structure.
void buildBasis(const std::vector< std::string > &basis_species)
using the basis_species list, this method builds _basis_index and _basis_info
std::vector< GeochemistryBasisSpecies > _basis_info
a vector of all relevant species
std::unordered_map< std::string, unsigned > _gas_index
given a species name, return its index in the corresponding "info" std::vector
std::unordered_map< std::string, unsigned > _basis_index
given a species name, return its index in the corresponding "info" std::vector
std::vector< GeochemistryRedoxSpecies > _kinetic_redox_info
a vector of all relevant species
ModelGeochemicalDatabase _model
The important datastructure built by this class.
void checkKineticSurfaceSpecies() const
Check that all kinetic surface species in the _kinetic_surface_species list have reactions that invol...
std::unordered_map< std::string, unsigned > _secondary_index
given a species name, return its index in the corresponding "info" std::vector
void buildRedoxeInfo(std::vector< Real > &redox_e_stoichiometry, std::vector< Real > &redox_e_log10K)
Extract the stoichiometry and log10K for the _redox_e species.
const std::string _redox_e
The name of the free electron involved in redox reactions.
PertinentGeochemicalSystem(const GeochemicalDatabaseReader &db, const std::vector< std::string > &basis_species, const std::vector< std::string > &minerals, const std::vector< std::string > &gases, const std::vector< std::string > &kinetic_minerals, const std::vector< std::string > &kinetic_redox, const std::vector< std::string > &kinetic_surface_species, const std::string &redox_ox, const std::string &redox_e)
std::vector< GeochemistryGasSpecies > _gas_info
a vector of all relevant species
void buildKineticMinerals(const std::vector< std::string > &kinetic_minerals)
using the kinetic_minerals list, this method builds _kinetic_mineral_index and _kinetic_mineral_info
void addKineticRate(const KineticRateUserDescription &description)
Adds a rate description for kinetic_species.
void checkGases() const
Check that all gases in the "gases" list have reactions that involve only the basis_species or second...
std::unordered_map< std::string, unsigned > _kinetic_surface_index
given a species name, return its index in the corresponding "info" std::vector
void createModel()
Fully populate the ModelGeochemicalDatabase.
void buildKineticSurface(const std::vector< std::string > &kinetic_surface)
using the kinetic_surface list, this method builds _kinetic_surface_index and _kinetic_surface_info
GeochemicalDatabaseReader _db
The database.
std::unordered_map< std::string, unsigned > _kinetic_redox_index
given a species name, return its index in the corresponding "info" std::vector
void checkMinerals(const std::vector< GeochemistryMineralSpecies > &mineral_info) const
Check that all minerals in mineral_info have reactions that involve only the basis_species or seconda...
void buildAllMinerals(const std::vector< std::string > &minerals)
If minerals = {"*"} then populate _mineral_index and _mineral_info with all relevant minerals This is...
std::unordered_map< std::string, unsigned > _kinetic_mineral_index
given a species name, return its index in the corresponding "info" std::vector
std::vector< GeochemistryMineralSpecies > _mineral_info
a vector of all relevant species
std::vector< std::string > originalBasisNames() const
std::vector< GeochemistrySurfaceSpecies > _kinetic_surface_info
a vector of all relevant species
void buildSecondarySpecies()
Extract all relevant "redox couples" and "secondary species" and "surface species" from the database.
std::unordered_map< std::string, unsigned > _mineral_index
given a species name, return its index in the corresponding "info" std::vector
void checkKineticRedox() const
Check that all kinetic redox species in the _kinetic_redox list have reactions that involve only the ...
void resize(const unsigned int new_m, const unsigned int new_n)
Data structure for basis (primary) species.
Data structure for secondary equilibrium species.
std::map< std::string, Real > basis_species
Data structure for mineral species.
Data structure for mineral species.
std::map< std::string, Real > basis_species
Data structure for redox species.
std::map< std::string, Real > basis_species
Data structure for sorbing surface species.
std::map< std::string, Real > basis_species
A single rate expression for the kinetic species with index kinetic_species_index.
Holds a user-specified description of a kinetic rate.
Data structure to hold all relevant information from the database file.
std::vector< bool > eqm_species_mineral
eqm_species_mineral[i] = true iff the i^th equilibrium species is a mineral
std::vector< Real > eqm_species_radius
all quantities have an ionic radius (Angstrom) for computing activity (mineral radius = 0,...
DenseMatrix< Real > eqm_stoichiometry
eqm_stoichiometry(i, j) = stoichiometric coefficient for equilibrium species "i" in terms of the basi...
std::vector< Real > basis_species_molecular_weight
all quantities have a molecular weight (g)
DenseMatrix< Real > kin_stoichiometry
kin_stoichiometry(i, j) = stoichiometric coefficient for kinetic species "i" in terms of the basis sp...
std::vector< Real > basis_species_radius
all quantities have an ionic radius (Angstrom) for computing activity (mineral radius = 0,...
std::vector< Real > basis_species_charge
all quantities have a charge (mineral charge = 0, gas charge = 0, oxide charge = 0)
std::vector< Real > eqm_species_molecular_volume
all quantities have a molecular volume (cm^3) (only nonzero for minerals, however)
std::string redox_lhs
the name of the species on the left-hand side of the redox equations.
std::vector< bool > kin_species_mineral
kin_species_mineral[j] = true iff the j^th kinetic species is a mineral
std::vector< Real > eqm_species_molecular_weight
all quantities have a molecular weight (g)
std::vector< unsigned > surface_sorption_number
surface_sorption_number[j] = the index of the surface potential that should be used to modify the equ...
std::vector< bool > basis_species_transported
basis_species_transported[j] = true iff the j^th basis species is transported in reactive-transport s...
std::vector< std::string > kin_species_name
kin_species_name[j] = name of the j^th kinetic species
DenseMatrix< Real > redox_stoichiometry
redox_stoichiometry(i, j) = stoichiometric coefficients for i^th redox species that is in disequilibr...
std::vector< Real > surface_sorption_area
surface_sorption_area[k] = specific surface area [m^2/g] for the k^th mineral involved in surface sor...
std::vector< bool > basis_species_mineral
basis_species_mineral[j] = true iff the j^th basis species is a mineral
std::vector< KineticRateDefinition > kin_rate
rates given to kinetic species.
std::vector< Real > kin_species_molecular_volume
all quantities have a molecular volume (cm^3/mol) (only nonzero for minerals, however)
DenseMatrix< Real > redox_log10K
redox_log10K(i, j) = log10(equilibrium constant) for i^th redox species at the j^th temperature point
DenseMatrix< Real > kin_log10K
kin_log10K(i, j) = log10(equilibrium constant for the i^th kinetic species at the j^th temperature po...
std::unordered_map< std::string, SurfaceComplexationInfo > surface_complexation_info
Holds info on surface complexation, if any, in the model.
std::vector< bool > surface_sorption_related
surface_sorption_related[j] = true iff the j^th equilibrium species is involved in surface sorption
std::vector< Real > basis_species_molecular_volume
all quantities have a molecular volume (cm^3) (only nonzero for minerals, however)
std::unordered_map< std::string, unsigned > eqm_species_index
eqm_species_index[name] = index of the equilibrium species (secondary aqueous species,...
std::vector< std::string > basis_species_name
basis_species_name[j] = name of the j^th basis species
std::vector< bool > kin_species_transported
kin_species_transported[j] = true iff the j^th kinetic species is transported in reactive-transport s...
std::unordered_map< std::string, std::vector< Real > > gas_chi
Holds info on gas fugacity "chi" parameters.
std::vector< Real > kin_species_charge
all kinetic quantities have a charge (mineral charge = 0)
std::unordered_map< std::string, unsigned > basis_species_index
basis_species_index[name] = index of the basis species, within all ModelGeochemicalDatabase internal ...
std::vector< std::string > surface_sorption_name
surface_sorption_name[k] = name of the mineral involved in surface sorption.
DenseMatrix< Real > eqm_log10K
eqm_log10K(i, j) = log10(equilibrium constant) for i^th equilibrium species at the j^th temperature p...
std::vector< bool > basis_species_gas
basis_species_gas[j] = true iff the j^th basis species is a gas
std::unordered_map< std::string, unsigned > kin_species_index
kin_species_index[name] = index of the kinetic species, within all ModelGeochemicalDatabase internal ...
std::vector< std::string > eqm_species_name
eqm_species_name[i] = name of the i^th eqm species
std::vector< Real > eqm_species_charge
all quantities have a charge (mineral charge = 0, gas charge = 0, oxide charge = 0)
std::vector< bool > eqm_species_gas
eqm_species_gas[i] = true iff the i^th equilibrium species is a gas
std::vector< Real > kin_species_molecular_weight
all quantities have a molecular weight (g/mol)
std::vector< bool > eqm_species_transported
eqm_species_transported[i] = true iff the i^th eqm species is transported in reactive-transport sims
Data structure designed to hold information related to sorption via surface complexation.
std::map< std::string, Real > sorption_sites