https://mooseframework.inl.gov
GeochemicalSystem.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 
10 #include "GeochemicalSystem.h"
14 
19  const std::vector<std::string> & swap_out_of_basis,
20  const std::vector<std::string> & swap_into_basis,
21  const std::string & charge_balance_species,
22  const std::vector<std::string> & constrained_species,
23  const std::vector<Real> & constraint_value,
24  const MultiMooseEnum & constraint_unit,
25  const MultiMooseEnum & constraint_user_meaning,
26  Real initial_temperature,
27  unsigned iters_to_make_consistent,
28  Real min_initial_molality,
29  const std::vector<std::string> & kin_name,
30  const std::vector<Real> & kin_initial,
31  const MultiMooseEnum & kin_unit)
32  : _mgd(mgd),
33  _num_basis(mgd.basis_species_index.size()),
34  _num_eqm(mgd.eqm_species_index.size()),
35  _num_redox(mgd.redox_stoichiometry.m()),
36  _num_surface_pot(mgd.surface_sorption_name.size()),
37  _num_kin(mgd.kin_species_index.size()),
38  _swapper(swapper),
39  _swap_out(swap_out_of_basis),
40  _swap_in(swap_into_basis),
41  _gac(gac),
42  _is(is),
43  _charge_balance_species(charge_balance_species),
44  _original_charge_balance_species(charge_balance_species),
45  _charge_balance_basis_index(0),
46  _constrained_species(constrained_species),
47  _constraint_value(constraint_value),
48  _original_constraint_value(constraint_value),
49  _constraint_unit(constraint_unit.size()),
50  _constraint_user_meaning(constraint_user_meaning.size()),
51  _constraint_meaning(constraint_user_meaning.size()),
52  _eqm_log10K(_num_eqm),
53  _redox_log10K(_num_redox),
54  _kin_log10K(_num_kin),
55  _num_basis_in_algebraic_system(0),
56  _num_in_algebraic_system(0),
57  _in_algebraic_system(_num_basis),
58  _algebraic_index(_num_basis),
59  _basis_index(_num_basis),
60  _bulk_moles_old(_num_basis),
61  _basis_molality(_num_basis),
62  _basis_activity_known(_num_basis),
63  _basis_activity(_num_basis),
64  _eqm_molality(_num_eqm),
65  _basis_activity_coef(_num_basis),
66  _eqm_activity_coef(_num_eqm),
67  _eqm_activity(_num_eqm),
68  _surface_pot_expr(_num_surface_pot),
69  _sorbing_surface_area(_num_surface_pot),
70  _kin_moles(_num_kin),
71  _kin_moles_old(_num_kin),
72  _iters_to_make_consistent(iters_to_make_consistent),
73  _temperature(initial_temperature),
74  _min_initial_molality(min_initial_molality),
75  _original_redox_lhs()
76 {
77  for (unsigned i = 0; i < constraint_user_meaning.size(); ++i)
79  static_cast<ConstraintUserMeaningEnum>(constraint_user_meaning.get(i));
80  for (unsigned i = 0; i < constraint_unit.size(); ++i)
81  _constraint_unit[i] =
82  static_cast<GeochemistryUnitConverter::GeochemistryUnit>(constraint_unit.get(i));
83  const unsigned ku_size = kin_unit.size();
84  std::vector<GeochemistryUnitConverter::GeochemistryUnit> k_unit(ku_size);
85  for (unsigned i = 0; i < ku_size; ++i)
86  k_unit[i] = static_cast<GeochemistryUnitConverter::GeochemistryUnit>(kin_unit.get(i));
87  checkAndInitialize(kin_name, kin_initial, k_unit);
88 }
89 
95  const std::vector<std::string> & swap_out_of_basis,
96  const std::vector<std::string> & swap_into_basis,
97  const std::string & charge_balance_species,
98  const std::vector<std::string> & constrained_species,
99  const std::vector<Real> & constraint_value,
100  const std::vector<GeochemistryUnitConverter::GeochemistryUnit> & constraint_unit,
101  const std::vector<ConstraintUserMeaningEnum> & constraint_user_meaning,
102  Real initial_temperature,
103  unsigned iters_to_make_consistent,
104  Real min_initial_molality,
105  const std::vector<std::string> & kin_name,
106  const std::vector<Real> & kin_initial,
107  const std::vector<GeochemistryUnitConverter::GeochemistryUnit> & kin_unit)
108  : _mgd(mgd),
109  _num_basis(mgd.basis_species_index.size()),
110  _num_eqm(mgd.eqm_species_index.size()),
111  _num_redox(mgd.redox_stoichiometry.m()),
112  _num_surface_pot(mgd.surface_sorption_name.size()),
113  _num_kin(mgd.kin_species_index.size()),
114  _swapper(swapper),
115  _swap_out(swap_out_of_basis),
116  _swap_in(swap_into_basis),
117  _gac(gac),
118  _is(is),
119  _charge_balance_species(charge_balance_species),
120  _original_charge_balance_species(charge_balance_species),
121  _charge_balance_basis_index(0),
122  _constrained_species(constrained_species),
123  _constraint_value(constraint_value),
124  _original_constraint_value(constraint_value),
125  _constraint_unit(constraint_unit),
126  _constraint_user_meaning(constraint_user_meaning),
127  _constraint_meaning(constraint_user_meaning.size()),
128  _eqm_log10K(_num_eqm),
129  _redox_log10K(_num_redox),
130  _kin_log10K(_num_kin),
131  _num_basis_in_algebraic_system(0),
132  _num_in_algebraic_system(0),
133  _in_algebraic_system(_num_basis),
134  _algebraic_index(_num_basis),
135  _basis_index(_num_basis),
136  _bulk_moles_old(_num_basis),
137  _basis_molality(_num_basis),
138  _basis_activity_known(_num_basis),
139  _basis_activity(_num_basis),
140  _eqm_molality(_num_eqm),
141  _basis_activity_coef(_num_basis),
142  _eqm_activity_coef(_num_eqm),
143  _eqm_activity(_num_eqm),
144  _surface_pot_expr(_num_surface_pot),
145  _sorbing_surface_area(_num_surface_pot),
146  _kin_moles(_num_kin),
147  _kin_moles_old(_num_kin),
148  _iters_to_make_consistent(iters_to_make_consistent),
149  _temperature(initial_temperature),
150  _min_initial_molality(min_initial_molality),
151  _original_redox_lhs()
152 {
153  checkAndInitialize(kin_name, kin_initial, kin_unit);
154 }
155 
156 void
158  const std::vector<std::string> & kin_name,
159  const std::vector<Real> & kin_initial,
160  const std::vector<GeochemistryUnitConverter::GeochemistryUnit> & kin_unit)
161 {
162  // initialize every the kinetic species
163  const unsigned num_kin_name = kin_name.size();
164  if (!(_num_kin == num_kin_name && _num_kin == kin_initial.size() && _num_kin == kin_unit.size()))
165  mooseError("Initial mole number (or mass or volume) and a unit must be provided for each "
166  "kinetic species ",
167  _num_kin,
168  " ",
169  num_kin_name,
170  " ",
171  kin_initial.size(),
172  " ",
173  kin_unit.size());
174  for (const auto & name_index : _mgd.kin_species_index)
175  {
176  const unsigned ind = std::distance(
177  kin_name.begin(), std::find(kin_name.begin(), kin_name.end(), name_index.first));
178  if (ind < num_kin_name)
179  {
186  mooseError("Kinetic species ",
187  name_index.first,
188  ": units must be moles or mass, or volume in the case of minerals");
190  kin_initial[ind], kin_unit[ind], name_index.first, _mgd);
191  setKineticMoles(name_index.second, moles);
192  }
193  else
194  mooseError("Initial mole number or mass or volume for kinetic species ",
195  name_index.first,
196  " must be provided");
197  }
198 
199  // check sanity of swaps
200  if (_swap_out.size() != _swap_in.size())
201  mooseError("swap_out_of_basis must have same length as swap_into_basis");
202  for (unsigned i = 0; i < _swap_out.size(); ++i)
204  mooseError("Cannot swap out ",
206  " because it is the charge-balance species\n");
207 
208  // do swaps desired by user. any exception here is an error
209  for (unsigned i = 0; i < _swap_out.size(); ++i)
210  try
211  {
213  }
214  catch (const MooseException & e)
215  {
216  mooseError(e.what());
217  }
218 
219  // check charge-balance species is in the basis and has a charge
221  mooseError("Cannot enforce charge balance using ",
223  " because it is not in the basis");
226  mooseError("Cannot enforce charge balance using ",
228  " because it has zero charge");
229 
230  // check that constraint vectors have appropriate sizes
231  if (_constrained_species.size() != _constraint_value.size())
232  mooseError("Constrained species names must have same length as constraint values");
233  if (_constrained_species.size() != _constraint_unit.size())
234  mooseError("Constrained species names must have same length as constraint units");
235  if (_constrained_species.size() != _constraint_user_meaning.size())
236  mooseError("Constrained species names must have same length as constraint meanings");
237  if (_constrained_species.size() != _num_basis)
238  mooseError("Constrained species names must have same length as the number of species in the "
239  "basis (each component must be provided with a single constraint");
240 
241  // check that each _constrained_species name appears in the basis
242  for (const auto & name : _mgd.basis_species_name)
243  if (std::find(_constrained_species.begin(), _constrained_species.end(), name) ==
244  _constrained_species.end())
245  mooseError("The basis species ", name, " must appear in the constrained species list");
246 
247  // order the constraints in the same way as the basis species. This makes the remainder of the
248  // code much cleaner.
249  std::vector<std::string> c_s(_num_basis);
250  std::vector<Real> c_v(_num_basis);
251  std::vector<GeochemistryUnitConverter::GeochemistryUnit> c_u(_num_basis);
252  std::vector<ConstraintUserMeaningEnum> c_m(_num_basis);
253  for (unsigned i = 0; i < _num_basis; ++i)
254  {
255  const unsigned basis_ind = _mgd.basis_species_index.at(_constrained_species[i]);
256  c_s[basis_ind] = _constrained_species[i];
257  c_v[basis_ind] = _constraint_value[i];
258  c_u[basis_ind] = _constraint_unit[i];
259  c_m[basis_ind] = _constraint_user_meaning[i];
260  }
261  _constrained_species = c_s;
262  _constraint_value = c_v;
263  _constraint_unit = c_u;
266 
267  // run through the constraints, checking physical and chemical consistency, converting to mole
268  // units, and building constraint_meaning
269  for (unsigned i = 0; i < _constrained_species.size(); ++i)
270  {
271  const std::string name = _constrained_species[i];
272 
273  switch (_constraint_user_meaning[i])
274  {
276  {
277  // if the mass of solvent water is provided, check it is positive
278  if (_constraint_value[i] <= 0.0)
279  mooseError("Specified mass of solvent water must be positive: you entered ",
280  _constraint_value[i]);
282  mooseError("Units for kg_solvent_water must be kg");
284  break;
285  }
288  {
289  // convert to mole units and specify correct constraint_meaning
292  // add contributions from kinetic moles, if necessary
294  for (unsigned k = 0; k < _mgd.kin_species_name.size(); ++k)
301  mooseError("Species ", name, ": units for bulk composition must be moles or mass");
302  if (name == "H2O")
304  else
306  break;
307  }
309  {
310  // if free concentration, check it is positive and perform the translation to mole units
311  if (_constraint_value[i] <= 0.0)
312  mooseError("Specified free concentration values must be positive: you entered ",
313  _constraint_value[i]);
315  _constraint_unit[i] ==
317  _constraint_unit[i] ==
319  _constraint_unit[i] ==
321  _constraint_unit[i] ==
323  mooseError(
324  "Species ",
325  name,
326  ": units for free concentration quantities must be molal or mass_per_kg_solvent");
330  break;
331  }
333  {
334  // if free mineral, check it is positive and perform the translation to mole units
335  if (_constraint_value[i] <= 0.0)
336  mooseError("Specified free mineral values must be positive: you entered ",
337  _constraint_value[i]);
344  mooseError("Species ",
345  name,
346  ": units for free mineral quantities must be moles, mass or volume");
350  break;
351  }
353  {
354  if (_constraint_value[i] <= 0.0)
355  mooseError("Specified activity values must be positive: you entered ",
356  _constraint_value[i]);
358  mooseError(
359  "Species ", name, ": dimensionless units must be used when specifying activity");
361  break;
362  }
364  {
365  // if log10activity is provided, convert it to activity
367  mooseError("Species ",
368  name,
369  ": dimensionless units must be used when specifying log10activity\n");
372  break;
373  }
375  {
376  // if fugacity is provided, check it is positive
377  if (_constraint_value[i] <= 0.0)
378  mooseError("Specified fugacity values must be positive: you entered ",
379  _constraint_value[i]);
381  mooseError(
382  "Species ", name, ": dimensionless units must be used when specifying fugacity\n");
384  break;
385  }
387  {
388  // if log10fugacity is provided, convert it to fugacity
390  mooseError("Species ",
391  name,
392  ": dimensionless units must be used when specifying log10fugacity\n");
395  break;
396  }
397  }
398 
399  // check that water is provided with correct meaning
400  if (name == "H2O")
404  mooseError("H2O must be provided with either a mass of solvent water, a bulk composition "
405  "in moles or mass, or an activity");
406 
407  // check that gases are provided with the correct meaning
408  if (_mgd.basis_species_gas[i])
410  mooseError("The gas ", name, " must be provided with a fugacity");
411 
412  // check that minerals are provided with the correct meaning
416  mooseError("The mineral ",
417  name,
418  " must be provided with either: a free number of moles, a free mass or a free "
419  "volume; or a bulk composition of moles or mass");
420 
421  // check that non-water, non-minerals, non-gases are provided with the correct meaning
422  if (name != "H2O" && !_mgd.basis_species_gas[i] && !_mgd.basis_species_mineral[i])
426  mooseError("The basis species ",
427  name,
428  " must be provided with a free concentration, bulk composition or an activity");
429 
430  // check that the charge-balance species has been provided MOLES_BULK_SPECIES
433  mooseError("For code consistency, the species ",
434  name,
435  " must be provided with a bulk composition because it is the charge-balance "
436  "species. The value provided should be a reasonable estimate of the mole "
437  "number, but will be overridden as the solve progresses");
438  }
440 
441  initialize();
442 }
443 
444 void
446 {
453  _basis_index);
456 
457  _eqm_molality.assign(_num_eqm, 0.0);
458  _surface_pot_expr.assign(_num_surface_pot, 1.0);
459 
461 }
462 
463 void
465 {
466  // the steps 1 and 2 below could be iterated for a long time (or a Newton process could even be
467  // followed) to provide better estimates of activities and molalities, but this is not done in the
468  // conventional geochemistry approach: there are just too many unknowns and approximations
469  // employed during the algebraic-system solve to justify iterating towards the perfectly
470  // consistent initial condition
471  for (unsigned picard = 0; picard < _iters_to_make_consistent + 1; ++picard)
472  {
473  // Step 1: compute ionic strengths and activities using the eqm molalities
478 
479  // Step 2: compute equilibrium molality based on the activities just computed
481  }
482 
486 }
487 
488 unsigned
490 {
492 }
493 
494 Real
496 {
497  if (j >= _num_eqm)
498  mooseError("Cannot retrieve log10K for equilibrium species ",
499  j,
500  " since there are only ",
501  _num_eqm,
502  " equilibrium species");
503  return _eqm_log10K[j];
504 }
505 
506 unsigned
508 {
509  return _num_redox;
510 }
511 
512 Real
514 {
515  if (red >= _num_redox)
516  mooseError("Cannot retrieve log10K for redox species ",
517  red,
518  " since there are only ",
519  _num_redox,
520  " redox species");
521  return _redox_log10K[red];
522 }
523 
524 Real
526 {
527  if (red >= _num_redox)
528  mooseError("Cannot retrieve activity product for redox species ",
529  red,
530  " since there are only ",
531  _num_redox,
532  " redox species");
533  Real log10ap = 0.0;
534  for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
535  log10ap += _mgd.redox_stoichiometry(red, basis_i) * std::log10(_basis_activity[basis_i]);
536  return log10ap;
537 }
538 
539 unsigned
541 {
542  return _num_kin;
543 }
544 
545 Real
547 {
548  if (kin >= _num_kin)
549  mooseError("Cannot retrieve log10K for kinetic species ",
550  kin,
551  " since there are only ",
552  _num_kin,
553  " kinetic species");
554  return _kin_log10K[kin];
555 }
556 
557 const std::vector<Real> &
559 {
560  return _kin_log10K;
561 }
562 
563 Real
565 {
566  if (kin >= _num_kin)
567  mooseError("Cannot retrieve activity product for kinetic species ",
568  kin,
569  " since there are only ",
570  _num_kin,
571  " kinetic species");
572  Real log10ap = 0.0;
573  for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
574  log10ap += _mgd.kin_stoichiometry(kin, basis_i) * std::log10(_basis_activity[basis_i]);
575  return log10ap;
576 }
577 
578 void
580 {
581  const std::vector<Real> temps = _mgd.original_database->getTemperatures();
582  const unsigned numT = temps.size();
583  const std::string model_type = _mgd.original_database->getLogKModel();
584 
585  for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
586  {
588  temps, _mgd.eqm_log10K.sub_matrix(eqm_j, 1, 0, numT).get_values(), model_type);
589  interp.generate();
590  _eqm_log10K[eqm_j] = interp.sample(temperature);
591  }
592  for (unsigned red = 0; red < _num_redox; ++red)
593  {
595  temps, _mgd.redox_log10K.sub_matrix(red, 1, 0, numT).get_values(), model_type);
596  interp.generate();
597  _redox_log10K[red] = interp.sample(temperature);
598  }
599  for (unsigned kin = 0; kin < _num_kin; ++kin)
600  {
602  temps, _mgd.kin_log10K.sub_matrix(kin, 1, 0, numT).get_values(), model_type);
603  interp.generate();
604  _kin_log10K[kin] = interp.sample(temperature);
605  }
606 }
607 
608 void
609 GeochemicalSystem::buildAlgebraicInfo(std::vector<bool> & in_algebraic_system,
610  unsigned & num_basis_in_algebraic_system,
611  unsigned & num_in_algebraic_system,
612  std::vector<unsigned> & algebraic_index,
613  std::vector<unsigned> & basis_index) const
614 {
615  // build in_algebraic_system
616  for (const auto & name_index : _mgd.basis_species_index)
617  {
618  const std::string name = name_index.first;
619  const unsigned basis_ind = name_index.second;
620  const ConstraintMeaningEnum meaning = _constraint_meaning[basis_ind];
621  if (name == "H2O")
622  in_algebraic_system[basis_ind] = (meaning == ConstraintMeaningEnum::MOLES_BULK_WATER);
623  else if (_mgd.basis_species_gas[basis_ind])
624  in_algebraic_system[basis_ind] = false;
625  else if (_mgd.basis_species_mineral[basis_ind])
626  in_algebraic_system[basis_ind] = false;
627  else
628  in_algebraic_system[basis_ind] = (meaning == ConstraintMeaningEnum::MOLES_BULK_SPECIES);
629  }
630 
631  // build algebraic_index and basis_index
632  num_basis_in_algebraic_system = 0;
633  algebraic_index.resize(_num_basis, 0);
634  basis_index.resize(_num_basis, 0);
635  for (unsigned basis_ind = 0; basis_ind < _num_basis; ++basis_ind)
636  if (in_algebraic_system[basis_ind])
637  {
638  algebraic_index[basis_ind] = _num_basis_in_algebraic_system;
639  basis_index[_num_basis_in_algebraic_system] = basis_ind;
640  num_basis_in_algebraic_system += 1;
641  }
642 
643  num_in_algebraic_system = num_basis_in_algebraic_system + _num_surface_pot + _num_kin;
644 }
645 
646 unsigned
648 {
650 }
651 
652 unsigned
654 {
656 }
657 
658 unsigned
660 {
661  return _num_surface_pot;
662 }
663 
664 const std::vector<bool> &
666 {
667  return _in_algebraic_system;
668 }
669 
670 const std::vector<unsigned> &
672 {
673  return _basis_index;
674 }
675 
676 const std::vector<unsigned> &
678 {
679  return _algebraic_index;
680 }
681 
682 std::vector<Real>
684 {
685  std::vector<Real> var(_num_basis_in_algebraic_system + _num_surface_pot + _num_kin);
686  for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
687  var[a] = _basis_molality[_basis_index[a]];
688  for (unsigned s = 0; s < _num_surface_pot; ++s)
690  for (unsigned k = 0; k < _num_kin; ++k)
692  return var;
693 }
694 
695 std::vector<Real>
697 {
698  std::vector<Real> var(_num_basis_in_algebraic_system);
699  for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
700  var[a] = _basis_molality[_basis_index[a]];
701  return var;
702 }
703 
704 DenseVector<Real>
706 {
707  DenseVector<Real> var(_num_in_algebraic_system);
708  for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
709  var(a) = _basis_molality[_basis_index[a]];
710  for (unsigned s = 0; s < _num_surface_pot; ++s)
712  for (unsigned k = 0; k < _num_kin; ++k)
714  return var;
715 }
716 
717 void
718 GeochemicalSystem::initBulkAndFree(std::vector<Real> & bulk_moles_old,
719  std::vector<Real> & basis_molality) const
720 {
721  for (unsigned i = 0; i < _num_basis; ++i)
722  {
723  // water is done first, so dividing by basis_molality[0] is OK
724  const Real value = _constraint_value[i];
725  const ConstraintMeaningEnum meaning = _constraint_meaning[i];
726  switch (meaning)
727  {
729  {
730  bulk_moles_old[i] = value;
731  basis_molality[i] = std::max(
733  0.999 * value /
734  GeochemistryConstants::MOLES_PER_KG_WATER); // mass of solvent water (water is an
735  // algebraic variable). Guess used to
736  // initialize the Newton process
737  break;
738  }
740  {
741  bulk_moles_old[i] = value * GeochemistryConstants::MOLES_PER_KG_WATER /
742  0.999; // initial guess (water is not an algebraic variable). Will be
743  // determined exactly during the solve
744  basis_molality[i] = value; // mass of solvent water
745  break;
746  }
748  {
749  bulk_moles_old[i] = value;
750  basis_molality[i] = std::max(
752  0.9 * value / basis_molality[0]); // initial guess (i is an algebraic variable). This
753  // is what we solve for in the Newton process
754  break;
755  }
757  {
758  bulk_moles_old[i] =
759  value * basis_molality[0] / 0.9; // initial guess (i is not an algebraic variable).
760  // Will be determined exactly during the solve
761  basis_molality[i] = value;
762  break;
763  }
765  {
766  bulk_moles_old[i] = value / 0.9; // initial guess (i is not an algebraic variable). Will
767  // be determined exactly during the solve
768  basis_molality[i] = value; // note, this is *moles*, not molality
769  break;
770  }
772  {
773  bulk_moles_old[i] = 0.0; // initial guess (i is not an algebraic variable). will be
774  // determined exactly after the solve
775  basis_molality[i] =
776  0.0; // never used in any solve process, but since this is a gas should be zero, and
777  // setting this explicitly elimiates if(species=gas) checks in various loops
778  break;
779  }
781  {
782  bulk_moles_old[i] = value / 0.9; // initial guess (i is not an algebraic variable). Will
783  // be determined exactly during the solve
784  if (i == 0)
785  basis_molality[i] = 1.0; // assumption
786  else
787  basis_molality[i] =
788  value /
789  0.9; // assume activity_coefficient = 0.9. this will be updated during the solve
790  break;
791  }
792  }
793  }
794 }
795 
796 Real
798 {
799  return _basis_molality[0];
800 }
801 
802 const std::vector<Real> &
804 {
805  return _bulk_moles_old;
806 }
807 
808 DenseVector<Real>
810 {
811  DenseVector<Real> result(_bulk_moles_old);
812  if (_mgd.swap_to_original_basis.n() == 0)
813  return result; // no swaps have been performed
815  return result;
816 }
817 
818 DenseVector<Real>
820 {
821  std::vector<Real> trans_bulk;
823  DenseVector<Real> result(trans_bulk);
824  if (_mgd.swap_to_original_basis.n() == 0)
825  return result; // no swaps have been performed
827  return result;
828 }
829 
830 const std::vector<Real> &
832 {
833  return _basis_molality;
834 }
835 
836 void
837 GeochemicalSystem::buildKnownBasisActivities(std::vector<bool> & basis_activity_known,
838  std::vector<Real> & basis_activity) const
839 {
840  basis_activity_known = std::vector<bool>(_num_basis, false);
841  basis_activity.resize(_num_basis);
842 
843  // all aqueous species with provided activity, and all gases with fugacity have known activity
844  for (unsigned i = 0; i < _num_basis; ++i)
845  {
846  const ConstraintMeaningEnum meaning = _constraint_meaning[i];
848  {
849  basis_activity_known[i] = true;
850  basis_activity[i] = _constraint_value[i];
851  }
852  }
853 
854  // all minerals have activity = 1.0
855  for (unsigned basis_ind = 0; basis_ind < _num_basis; ++basis_ind)
856  if (_mgd.basis_species_mineral[basis_ind])
857  {
858  basis_activity_known[basis_ind] = true;
859  basis_activity[basis_ind] = 1.0;
860  }
861 }
862 
863 const std::vector<bool> &
865 {
866  return _basis_activity_known;
867 }
868 
869 Real
871 {
872  if (i >= _num_basis)
873  mooseError("Cannot retrieve basis activity for species ",
874  i,
875  " since there are only ",
876  _num_basis,
877  " basis species");
878  return _basis_activity[i];
879 }
880 
881 const std::vector<Real> &
883 {
884  return _basis_activity;
885 }
886 
887 Real
889 {
890  if (j >= _num_eqm)
891  mooseError("Cannot retrieve molality for equilibrium species ",
892  j,
893  " since there are only ",
894  _num_eqm,
895  " equilibrium species");
896  return _eqm_molality[j];
897 }
898 
899 const std::vector<Real> &
901 {
902  return _eqm_molality;
903 }
904 
905 Real
907 {
908  if (k >= _num_kin)
909  mooseError("Cannot retrieve moles for kinetic species ",
910  k,
911  " since there are only ",
912  _num_kin,
913  " kinetic species");
914  return _kin_moles[k];
915 }
916 
917 void
919 {
920  if (k >= _num_kin)
921  mooseError("Cannot set moles for kinetic species ",
922  k,
923  " since there are only ",
924  _num_kin,
925  " kinetic species");
926  if (moles <= 0.0)
927  mooseError("Mole number for kinetic species must be positive, not ", moles);
928  _kin_moles[k] = moles;
929  _kin_moles_old[k] = moles;
930 }
931 
932 const std::vector<Real> &
934 {
935  return _kin_moles;
936 }
937 
938 void
939 GeochemicalSystem::computeRemainingBasisActivities(std::vector<Real> & basis_activity) const
940 {
941  if (!_basis_activity_known[0])
942  basis_activity[0] = _gac.waterActivity();
943  for (unsigned basis_ind = 1; basis_ind < _num_basis; ++basis_ind) // don't loop over water
944  if (!_basis_activity_known[basis_ind]) // basis_activity_known = true for minerals, gases and
945  // species with activities provided by the user
946  basis_activity[basis_ind] = _basis_activity_coef[basis_ind] * _basis_molality[basis_ind];
947 }
948 
949 Real
951 {
952  if (j >= _num_eqm)
953  mooseError("Cannot retrieve activity coefficient for equilibrium species ",
954  j,
955  " since there are only ",
956  _num_eqm,
957  " equilibrium species");
958  return _eqm_activity_coef[j];
959 }
960 
961 const std::vector<Real> &
963 {
964  return _eqm_activity_coef;
965 }
966 
967 Real
969 {
970  if (i >= _num_basis)
971  mooseError("Cannot retrieve basis activity coefficient for species ",
972  i,
973  " since there are only ",
974  _num_basis,
975  " basis species");
976  return _basis_activity_coef[i];
977 }
978 
979 const std::vector<Real> &
981 {
982  return _basis_activity_coef;
983 }
984 
985 void
986 GeochemicalSystem::updateBasisMolalityForKnownActivity(std::vector<Real> & basis_molality) const
987 {
988  for (unsigned i = 1; i < _num_basis; ++i) // don't loop over water
989  {
991  basis_molality[i] =
992  _basis_activity[i] / _basis_activity_coef[i]; // just those for
993  // which activity is provided by the user
994  }
995 }
996 
997 Real
999 {
1000  Real log10ap = 0.0;
1001  for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
1002  log10ap += _mgd.eqm_stoichiometry(eqm_j, basis_i) * std::log10(_basis_activity[basis_i]);
1003  return log10ap;
1004 }
1005 
1006 void
1007 GeochemicalSystem::computeEqmMolalities(std::vector<Real> & eqm_molality) const
1008 {
1009  for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
1010  {
1011  if (_mgd.eqm_species_mineral[eqm_j] || _mgd.eqm_species_gas[eqm_j])
1012  eqm_molality[eqm_j] = 0.0;
1013  else
1014  {
1015  // compute log10 version first, in an attempt to eliminate overflow and underflow problems
1016  // such as 10^(1000)
1017  const Real log10m = log10ActivityProduct(eqm_j) - _eqm_log10K[eqm_j];
1018  eqm_molality[eqm_j] =
1019  std::pow(10.0, log10m) / _eqm_activity_coef[eqm_j] * surfaceSorptionModifier(eqm_j);
1020  }
1021  }
1022 }
1023 
1024 Real
1026 {
1027  if (eqm_j >= _num_eqm)
1028  return 1.0;
1029  if (!_mgd.surface_sorption_related[eqm_j])
1030  return 1.0;
1032  2.0 * _mgd.eqm_species_charge[eqm_j]);
1033 }
1034 
1035 void
1036 GeochemicalSystem::enforceChargeBalanceIfSimple(std::vector<Real> & constraint_value,
1037  std::vector<Real> & bulk_moles_old) const
1038 {
1039  Real tot_charge = 0.0;
1040  for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
1041  if (_mgd.basis_species_charge[basis_i] != 0.0)
1042  {
1044  tot_charge += _mgd.basis_species_charge[basis_i] * constraint_value[basis_i];
1045  else
1046  return;
1047  }
1048  // kinetic species are counted in the bulk
1049  // all charged basis species must have been provided with a MOLES_BULK_SPECIES value, so we can
1050  // easily enforce charge neutrality
1052  constraint_value[_charge_balance_basis_index];
1053  constraint_value[_charge_balance_basis_index] =
1055  bulk_moles_old[_charge_balance_basis_index] = constraint_value[_charge_balance_basis_index];
1056 }
1057 
1058 Real
1060 {
1061  Real tot_charge = 0.0;
1062  for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
1063  tot_charge += _mgd.basis_species_charge[basis_i] * _bulk_moles_old[basis_i];
1064  // kinetic species already counted in bulk_moles
1065  return tot_charge;
1066 }
1067 
1068 void
1070 {
1072 }
1073 
1074 void
1075 GeochemicalSystem::enforceChargeBalance(std::vector<Real> & constraint_value,
1076  std::vector<Real> & bulk_moles_old) const
1077 {
1078  const Real tot_charge = getTotalChargeOld();
1079  constraint_value[_charge_balance_basis_index] -=
1081  bulk_moles_old[_charge_balance_basis_index] = constraint_value[_charge_balance_basis_index];
1082 }
1083 
1084 void
1085 GeochemicalSystem::setAlgebraicVariables(const DenseVector<Real> & algebraic_var)
1086 {
1087  if (algebraic_var.size() != _num_in_algebraic_system)
1088  mooseError("Incorrect size in setAlgebraicVariables");
1089  for (unsigned a = 0; a < _num_in_algebraic_system; ++a)
1090  if (algebraic_var(a) <= 0.0)
1091  mooseError("Cannot set algebraic variables to non-positive values such as ",
1092  algebraic_var(a));
1093 
1094  for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
1095  _basis_molality[_basis_index[a]] = algebraic_var(a);
1096  for (unsigned s = 0; s < _num_surface_pot; ++s)
1097  _surface_pot_expr[s] = algebraic_var(s + _num_basis_in_algebraic_system);
1098  for (unsigned k = 0; k < _num_kin; ++k)
1100 
1102 }
1103 
1104 void
1105 GeochemicalSystem::computeBulk(std::vector<Real> & bulk_moles_old) const
1106 {
1107  for (unsigned i = 0; i < _num_basis; ++i)
1108  {
1109  const Real value = _constraint_value[i];
1110  const ConstraintMeaningEnum meaning = _constraint_meaning[i];
1111  switch (meaning)
1112  {
1115  {
1116  bulk_moles_old[i] = value;
1117  break;
1118  }
1124  {
1125  bulk_moles_old[i] = computeBulkFromMolalities(i);
1126  break;
1127  }
1128  }
1129  }
1130 }
1131 
1132 Real
1134 {
1135  const Real nw = _basis_molality[0];
1136  Real bulk = 0.0;
1137  if (basis_ind == 0)
1139  else if (_mgd.basis_species_mineral[basis_ind])
1140  bulk = _basis_molality[basis_ind] / nw; // because of multiplication by nw, below
1141  else
1142  bulk = _basis_molality[basis_ind];
1143  for (unsigned j = 0; j < _num_eqm; ++j)
1144  bulk += _mgd.eqm_stoichiometry(j, basis_ind) * _eqm_molality[j];
1145  bulk *= nw;
1146  for (unsigned kin = 0; kin < _num_kin; ++kin)
1147  bulk += _mgd.kin_stoichiometry(kin, basis_ind) * _kin_moles[kin];
1148  return bulk;
1149 }
1150 
1151 void
1152 GeochemicalSystem::computeTransportedBulkFromMolalities(std::vector<Real> & transported_bulk) const
1153 {
1154  transported_bulk.resize(_num_basis);
1155  const Real nw = _basis_molality[0];
1156  for (unsigned i = 0; i < _num_basis; ++i)
1157  {
1158  transported_bulk[i] = 0.0;
1159  if (i == 0)
1160  transported_bulk[i] = GeochemistryConstants::MOLES_PER_KG_WATER;
1161  else if (_mgd.basis_species_transported[i])
1162  {
1163  if (_mgd.basis_species_mineral[i])
1164  transported_bulk[i] = _basis_molality[i] / nw; // because of multiplication by nw, below
1165  else
1166  transported_bulk[i] = _basis_molality[i];
1167  }
1168  else
1169  transported_bulk[i] = 0.0;
1170  for (unsigned j = 0; j < _num_eqm; ++j)
1172  transported_bulk[i] += _mgd.eqm_stoichiometry(j, i) * _eqm_molality[j];
1173  transported_bulk[i] *= nw;
1174  }
1175 }
1176 
1177 Real
1179  const DenseVector<Real> & mole_additions) const
1180 {
1181  if (algebraic_ind >= _num_in_algebraic_system)
1182  mooseError("Cannot retrieve residual for algebraic index ",
1183  algebraic_ind,
1184  " because there are only ",
1186  " molalities in the algebraic system and ",
1188  " surface potentials and ",
1189  _num_kin,
1190  " kinetic species");
1191  if (mole_additions.size() != _num_basis + _num_kin)
1192  mooseError("The increment in mole numbers (mole_additions) needs to be of size ",
1193  _num_basis,
1194  " + ",
1195  _num_kin,
1196  " but it is of size ",
1197  mole_additions.size());
1198 
1199  if (algebraic_ind < _num_basis_in_algebraic_system) // residual for basis molality
1200  {
1201  /*
1202  * Without the special things for water or the charge-balance species, we're trying to solve
1203  * bulk_new = bulk_old + mole_addition
1204  * where bulk_old is known (from previous time-step or, for a steady-state problem, the
1205  * constraint)
1206  * and mole_addition is given to this function
1207  * and bulk_new = nw * (m + sum_eqm[stoi * mol_eqm]) + sum_kin[stoi * mole_kin]
1208  * Hence, the residual is
1209  * R = -(bulk_old + mole_addition) + nw*(m + sum_eqm[stoi*mol_eqm]) + sum_kin[stoi*mole_kin]
1210  * This is seemingly different from Bethke Eqns(16.7)-(16.9), because Bethke bulk ("M") do not
1211  contain the sum_kin[stoi * mol_kin] terms. Converting the above residual to Bethke's
1212  convention:
1213  * R = -((nw*(m + sum_eqm[stoi*mol_eqm]) + sum_kin[stoi*mole_kin])_old + mole_addition) + nw*(m
1214  + sum_eqm[stoi*mol_eqm]) + sum_kin[stoi*mole_kin]
1215  * = -(M_old + sum_kin[stoi*mole_kin_old] + mole_addition) + M + sum_kin[stoi*mole_kin]
1216  * = -(M_old + mole_addition) + M + sum_kin[stoi*kin_mole_addition]
1217  * which is exactly Eqns(16.7)-(16.9).
1218  * One problem with Bethke's approach is that if kin_mole_addition depends on the mole number
1219  of the kinetic species, there is a "hidden" variable (moles of kinetic species) which is kept
1220  constant during the solve. Here, including the moles of kinetic species as additional
1221  variables allows greater accuracy.
1222  */
1223  const unsigned basis_i = _basis_index[algebraic_ind];
1224  Real res = 0.0;
1225  if (basis_i == 0)
1226  res += -(_bulk_moles_old[basis_i] + mole_additions(basis_i)) +
1228  else if (basis_i == _charge_balance_basis_index)
1229  {
1230  res += _basis_molality[0] * _basis_molality[basis_i];
1231  for (unsigned i = 0; i < _num_basis; ++i)
1232  {
1233  if (i == _charge_balance_basis_index)
1234  continue;
1235  else if (_mgd.basis_species_charge[i] ==
1236  0.0) // certainly includes water, minerals and gases
1237  continue;
1239  {
1240  // the molalities might not yet have converged so that the bulk moles of this species
1241  // currently equals _bulk_moles_old + mole_additions, but we know
1242  // that when the solve has converged it'll have to, so:
1243  res += _mgd.basis_species_charge[i] * (_bulk_moles_old[i] + mole_additions(i)) /
1244  _mgd.basis_species_charge[basis_i];
1245  }
1246  else
1247  {
1248  // do not know the bulk moles of this species: use the current value for molality and
1249  // kin_moles. Note, there is no mole_additions here since physically any mole_additions
1250  // get instantly get soaked up by the fixed activity (or fixed molality, etc), and
1251  // mathematically when we eventually come to add mole_additions to the bulk_moles_old
1252  // (via addtoBulkMoles, for instance) we immediately return without adding stuff. End
1253  // result: charge-neutrality should not depend on mole_additions for fixed-activity
1254  // (molality, etc) species.
1256  _mgd.basis_species_charge[basis_i];
1257  }
1258  }
1259  }
1260  else
1261  res += -(_bulk_moles_old[basis_i] + mole_additions(basis_i)) +
1262  _basis_molality[0] * _basis_molality[basis_i];
1263  for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
1264  res += _basis_molality[0] * _mgd.eqm_stoichiometry(eqm_j, basis_i) * _eqm_molality[eqm_j];
1265  for (unsigned kin = 0; kin < _num_kin; ++kin)
1266  res += _mgd.kin_stoichiometry(kin, basis_i) * _kin_moles[kin];
1267  return res;
1268  }
1269  else if (algebraic_ind <
1270  _num_basis_in_algebraic_system + _num_surface_pot) // residual for surface potential
1271  {
1272  const unsigned sp = algebraic_ind - _num_basis_in_algebraic_system;
1273  Real res = surfacePotPrefactor(sp) * (_surface_pot_expr[sp] - 1.0 / _surface_pot_expr[sp]);
1274  for (unsigned j = 0; j < _num_eqm; ++j)
1277  return res;
1278  }
1279 
1280  // else: residual for kinetic
1281  const unsigned kin = algebraic_ind - _num_basis_in_algebraic_system - _num_surface_pot;
1282  Real res = _kin_moles[kin] - (_kin_moles_old[kin] + mole_additions(_num_basis + kin));
1283  return res;
1284 }
1285 
1286 void
1287 GeochemicalSystem::computeJacobian(const DenseVector<Real> & res,
1288  DenseMatrix<Real> & jac,
1289  const DenseVector<Real> & mole_additions,
1290  const DenseMatrix<Real> & dmole_additions) const
1291 {
1292  /* To the reader: yes, this is awfully complicated. The best way of understanding it is to very
1293  * slowly go through the residual calculation and compute the derivatives by hand. It is quite
1294  * well tested, but it'd be great if you could add more tests!
1295  */
1296  if (res.size() != _num_in_algebraic_system)
1297  mooseError(
1298  "Jacobian: residual size must be ", _num_in_algebraic_system, " but it is ", res.size());
1299  if (mole_additions.size() != _num_basis + _num_kin)
1300  mooseError("Jacobian: the increment in mole numbers (mole_additions) needs to be of size ",
1301  _num_basis,
1302  " + ",
1303  _num_kin,
1304  " but it is of size ",
1305  mole_additions.size());
1306  if (!(dmole_additions.n() == _num_basis + _num_kin &&
1307  dmole_additions.m() == _num_basis + _num_kin))
1308  mooseError("Jacobian: the derivative of mole additions (dmole_additions) needs to be of size ",
1309  _num_basis + _num_kin,
1310  "x",
1311  _num_basis + _num_kin,
1312  " but it is of size ",
1313  dmole_additions.m(),
1314  "x",
1315  dmole_additions.n());
1316  /*
1317 Note that in this function we make use of the fact that species can only be in the algebraic
1318 system if their molality is unknown (or in the case of water, the mass of solvent mass is
1319 unknown). This means that the molality of the equilibrium species depends on
1320 (activity_coefficient * basis molality)^stoi, so that the derivative is quite simple. Eg, we
1321 don't have to worry about derivatives with respect to fixed-activity things, or fixed fugacity.
1322 Also, note that the constructor and the various "set" methods of this class enforce molality > 0,
1323 so there are no division-by-zero problems. Also, note that we never compute derivatives of the
1324 activity coefficients, or the activity of water with respect to the molalities, as they are
1325 assumed to be quite small. Finally, the surface_pot_expr will also always be positive.
1326  */
1327  // correctly size and zero
1329  const Real nw = _basis_molality[0];
1330 
1331  // jac(a, b) = d(R_a) / d(m_b), where a corresponds to a molality
1332  for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
1333  {
1334  const unsigned basis_of_a = _basis_index[a];
1335  for (unsigned b = 0; b < _num_basis_in_algebraic_system; ++b)
1336  {
1337  const unsigned basis_of_b = _basis_index[b];
1338 
1339  // contribution from mole_additions
1340  if (basis_of_a != _charge_balance_basis_index)
1341  jac(a, b) -= dmole_additions(basis_of_a, basis_of_b);
1342  else
1343  {
1344  for (unsigned i = 0; i < _num_basis; ++i)
1345  {
1346  if (i == _charge_balance_basis_index)
1347  continue;
1348  else if (_mgd.basis_species_charge[i] ==
1349  0.0) // certainly includes water, minerals and gases
1350  continue;
1352  jac(a, b) += _mgd.basis_species_charge[i] * dmole_additions(i, basis_of_b) /
1353  _mgd.basis_species_charge[basis_of_a];
1354  }
1355  }
1356 
1357  // contribution from explicit nw, in case where mass of solvent water is an unknown
1358  if (basis_of_b == 0)
1359  {
1360  if (basis_of_a != _charge_balance_basis_index)
1361  {
1362  // use a short-cut here: dR/dnw = m + sum_eqm[stoi*mol] = (R + bulk + addition - kin)/nw
1363  Real numerator = res(a) + _bulk_moles_old[basis_of_a] + mole_additions(basis_of_a);
1364  for (unsigned kin = 0; kin < _num_kin; ++kin)
1365  numerator -= _mgd.kin_stoichiometry(kin, basis_of_a) * _kin_moles[kin];
1366  jac(a, 0) += numerator / nw;
1367  }
1368  else
1369  {
1370  for (unsigned i = 0; i < _num_basis; ++i)
1371  {
1372  if (i == _charge_balance_basis_index)
1373  continue;
1374  else if (_mgd.basis_species_charge[i] ==
1375  0.0) // certainly includes water, minerals and gases
1376  continue;
1378  continue;
1379  else
1380  {
1381  Real molal = _basis_molality[i];
1382  for (unsigned j = 0; j < _num_eqm; ++j)
1383  molal += _mgd.eqm_stoichiometry(j, i) * _eqm_molality[j];
1384  jac(a, 0) +=
1385  _mgd.basis_species_charge[i] * molal / _mgd.basis_species_charge[basis_of_a];
1386  }
1387  }
1388  jac(a, 0) += _basis_molality[basis_of_a];
1389  for (unsigned j = 0; j < _num_eqm; ++j)
1390  jac(a, 0) += _mgd.eqm_stoichiometry(j, basis_of_a) * _eqm_molality[j];
1391  }
1392  }
1393 
1394  // contribution from molality of basis_of_b
1395  if (basis_of_b != 0)
1396  {
1397  if (a == b)
1398  jac(a, b) += nw; // remember b != 0
1399  for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
1400  {
1401  jac(a, b) += nw * _mgd.eqm_stoichiometry(eqm_j, basis_of_a) * _eqm_molality[eqm_j] *
1402  _mgd.eqm_stoichiometry(eqm_j, basis_of_b) / _basis_molality[basis_of_b];
1403  }
1404  if (basis_of_a == _charge_balance_basis_index)
1405  {
1406  // additional terms from the charge-balance additions
1407  for (unsigned i = 0; i < _num_basis; ++i)
1408  {
1409  if (i == _charge_balance_basis_index)
1410  continue;
1411  else if (_mgd.basis_species_charge[i] ==
1412  0.0) // certainly includes water, minerals and gases
1413  continue;
1415  continue;
1416  else
1417  {
1418  const Real prefactor =
1419  _mgd.basis_species_charge[i] * nw / _mgd.basis_species_charge[basis_of_a];
1420  if (i == basis_of_b)
1421  jac(a, b) += prefactor;
1422  for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
1423  {
1424  jac(a, b) += prefactor * _mgd.eqm_stoichiometry(eqm_j, i) * _eqm_molality[eqm_j] *
1425  _mgd.eqm_stoichiometry(eqm_j, basis_of_b) /
1426  _basis_molality[basis_of_b];
1427  }
1428  }
1429  }
1430  }
1431  }
1432  }
1433  }
1434 
1435  // jac(a, b) = d(R_a) / d(surface_pot), where a corresponds to a molality
1436  for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
1437  {
1438  const unsigned basis_of_a = _basis_index[a];
1439  for (unsigned s = 0; s < _num_surface_pot; ++s)
1440  {
1441  const unsigned b = s + _num_basis_in_algebraic_system;
1442  // derivative of nw * _mgd.eqm_stoichiometry(eqm_j, basis_i) * _eqm_molality[eqm_j],
1443  // where _eqm_molality = (_surface_pot_expr)^(2 * charge) * stuff
1444  for (unsigned eqm_j = 0; eqm_j < _num_eqm; ++eqm_j)
1445  if (_mgd.surface_sorption_related[eqm_j] && _mgd.surface_sorption_number[eqm_j] == s)
1446  jac(a, b) += nw * _mgd.eqm_stoichiometry(eqm_j, basis_of_a) * 2.0 *
1447  _mgd.eqm_species_charge[eqm_j] * _eqm_molality[eqm_j] /
1449  }
1450  }
1451 
1452  // jac(a, b) = d(R_a) / d(_kin_moles) where a corresponds to a molality
1453  for (unsigned a = 0; a < _num_basis_in_algebraic_system; ++a)
1454  {
1455  const unsigned basis_of_a = _basis_index[a];
1456 
1457  // contribution from mole_additions
1458  for (unsigned kin = 0; kin < _num_kin; ++kin)
1459  {
1460  const unsigned ind = _num_basis + kin;
1461  const unsigned b = _num_basis_in_algebraic_system + _num_surface_pot + kin;
1462  if (basis_of_a != _charge_balance_basis_index)
1463  jac(a, b) -= dmole_additions(basis_of_a, ind);
1464  else
1465  {
1466  for (unsigned i = 0; i < _num_basis; ++i)
1467  {
1468  if (i == _charge_balance_basis_index)
1469  continue;
1470  else if (_mgd.basis_species_charge[i] ==
1471  0.0) // certainly includes water, minerals and gases
1472  continue;
1474  jac(a, b) += _mgd.basis_species_charge[i] * dmole_additions(i, ind) /
1475  _mgd.basis_species_charge[basis_of_a];
1476  }
1477  }
1478  }
1479 
1480  // contribution from sum_kin[stoi*kin_moles]
1481  for (unsigned kin = 0; kin < _num_kin; ++kin)
1482  {
1483  const unsigned b = _num_basis_in_algebraic_system + _num_surface_pot + kin;
1484  jac(a, b) += _mgd.kin_stoichiometry(kin, basis_of_a);
1485  }
1486 
1487  // special additional contribution for charge-balance from kinetic stuff
1488  if (basis_of_a == _charge_balance_basis_index)
1489  {
1490  for (unsigned i = 0; i < _num_basis; ++i)
1491  {
1492  if (i == _charge_balance_basis_index)
1493  continue;
1494  else if (_mgd.basis_species_charge[i] ==
1495  0.0) // certainly includes water, minerals and gases
1496  continue;
1498  continue;
1499  else
1500  {
1501  const Real prefactor =
1503  for (unsigned kin = 0; kin < _num_kin; ++kin)
1504  {
1505  const unsigned b = _num_basis_in_algebraic_system + _num_surface_pot + kin;
1506  jac(a, b) += prefactor * _mgd.kin_stoichiometry(kin, i);
1507  }
1508  }
1509  }
1510  }
1511  }
1512 
1513  // jac(a, b) = d(R_a) / d(variable_b) where a corresponds to a surface potential
1514  for (unsigned s = 0; s < _num_surface_pot; ++s)
1515  {
1516  const unsigned a = s + _num_basis_in_algebraic_system;
1517 
1518  for (unsigned b = 0; b < _num_basis_in_algebraic_system; ++b)
1519  {
1520  // derivative of nw * _mgd.eqm_species_charge[j] * _eqm_molality[j];
1521  const unsigned basis_of_b = _basis_index[b];
1522  if (basis_of_b == 0) // special case: mass of solvent water is an unknown
1523  {
1524  for (unsigned j = 0; j < _num_eqm; ++j)
1526  jac(a, b) += _mgd.eqm_species_charge[j] * _eqm_molality[j];
1527  }
1528  else
1529  {
1530  for (unsigned j = 0; j < _num_eqm; ++j)
1532  jac(a, b) += nw * _mgd.eqm_species_charge[j] * _eqm_molality[j] *
1533  _mgd.eqm_stoichiometry(j, basis_of_b) / _basis_molality[basis_of_b];
1534  }
1535  }
1536 
1537  const Real coef = surfacePotPrefactor(s);
1538  // derivative of coef * (x - 1/x) wrt x, where x = _surface_pot_expr
1539  jac(a, a) += coef * (1.0 + 1.0 / std::pow(_surface_pot_expr[s], 2.0));
1540  // derivative of nw * _mgd.eqm_species_charge[j] * _eqm_molality[j];
1541  // where _eqm_molality = (_surface_pot_expr)^(2 * charge) * stuff
1542  for (unsigned j = 0; j < _num_eqm; ++j)
1544  jac(a, a) += nw * std::pow(_mgd.eqm_species_charge[j], 2.0) * 2.0 * _eqm_molality[j] /
1545  _surface_pot_expr[s];
1546  }
1547 
1548  // jac(a, b) = d(R_a) / d(variable_b) where a corresponds to a kinetic species
1549  for (unsigned kin = 0; kin < _num_kin; ++kin)
1550  {
1551  const unsigned a = _num_basis_in_algebraic_system + _num_surface_pot + kin;
1552  jac(a, a) += 1.0; // deriv wrt _kin_moles[kin]
1553 
1554  // contribution from mole_additions
1555  const unsigned ind_of_addition = _num_basis + kin;
1556  for (unsigned b = 0; b < _num_basis_in_algebraic_system; ++b)
1557  {
1558  const unsigned basis_of_b = _basis_index[b];
1559  jac(a, b) -= dmole_additions(ind_of_addition, basis_of_b);
1560  }
1561  for (unsigned kinp = 0; kinp < _num_kin; ++kinp)
1562  {
1563  const unsigned b = _num_basis_in_algebraic_system + _num_surface_pot + kinp;
1564  const unsigned indp = _num_basis + kinp;
1565  jac(a, b) -= dmole_additions(ind_of_addition, indp);
1566  }
1567  }
1568 }
1569 
1572 {
1573  return _mgd;
1574 }
1575 
1576 void
1577 GeochemicalSystem::computeFreeMineralMoles(std::vector<Real> & basis_molality) const
1578 {
1579 
1580  const Real nw = _basis_molality[0];
1581  for (unsigned i = 0; i < _num_basis; ++i)
1582  if (_mgd.basis_species_mineral[i])
1583  {
1585  basis_molality[i] = _constraint_value[i];
1586  else
1587  {
1588  basis_molality[i] = _bulk_moles_old[i];
1589  for (unsigned j = 0; j < _num_eqm; ++j)
1590  basis_molality[i] -= nw * _mgd.eqm_stoichiometry(j, i) * _eqm_molality[j];
1591  for (unsigned kin = 0; kin < _num_kin; ++kin)
1592  basis_molality[i] -= _mgd.kin_stoichiometry(kin, i) * _kin_moles[kin];
1593  }
1594  }
1595 }
1596 
1597 std::vector<Real>
1599 {
1600  std::vector<Real> si(_num_eqm);
1601  for (unsigned j = 0; j < _num_eqm; ++j)
1602  if (_mgd.eqm_species_mineral[j])
1603  si[j] = log10ActivityProduct(j) - _eqm_log10K[j];
1604  else
1605  si[j] = 0.0;
1606  return si;
1607 }
1608 
1609 void
1610 GeochemicalSystem::performSwap(unsigned swap_out_of_basis, unsigned swap_into_basis)
1611 {
1612  if (swap_out_of_basis == 0)
1613  mooseException("GeochemicalSystem: attempting to swap out water and replace it by ",
1614  _mgd.eqm_species_name[swap_into_basis],
1615  ". This could be because the algorithm would like to "
1616  "swap out the charge-balance species, in which case you should choose a "
1617  "different charge-balance species");
1618  if (swap_out_of_basis == _charge_balance_basis_index)
1619  mooseException("GeochemicalSystem: attempting to swap the charge-balance species out of "
1620  "the basis");
1621  if (_mgd.basis_species_gas[swap_out_of_basis])
1622  mooseException("GeochemicalSystem: attempting to swap a gas out of the basis");
1623  if (_mgd.eqm_species_gas[swap_into_basis])
1624  mooseException("GeochemicalSystem: attempting to swap a gas into the basis");
1625  // perform the swap
1626  performSwapNoCheck(swap_out_of_basis, swap_into_basis);
1627 }
1628 
1629 void
1630 GeochemicalSystem::performSwapNoCheck(unsigned swap_out_of_basis, unsigned swap_into_basis)
1631 {
1632  DenseVector<Real> bm = _bulk_moles_old;
1633  _swapper.performSwap(_mgd, bm, swap_out_of_basis, swap_into_basis);
1634 
1635  // the swap_into_basis species now has fixed bulk moles irrespective of what the
1636  // swap_out_of_basis species had fixed
1638 
1639  // the bulk moles will have changed for many components. The _bulk_moles_old values will be
1640  // set in computeConsistentConfiguration, below
1641  for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
1644  {
1645  _constraint_value[basis_i] = bm(basis_i);
1646  _original_constraint_value[basis_i] = bm(basis_i);
1647  }
1648 
1649  // In the following, the molalities are carefully swapped so that the configuration stays
1650  // reasonably close to the configuration prior to the swap. This may be advantageous as it
1651  // initializes the Newton process with a better starting guess than what might have been used
1652  Real molality_of_species_just_swapped_in =
1653  _eqm_molality[swap_into_basis]; // is positive, or zero iff (mineral or gas)
1654  if (_mgd.basis_species_mineral[swap_out_of_basis] || _mgd.basis_species_gas[swap_out_of_basis] ||
1655  _eqm_molality[swap_into_basis] == 0.0)
1656  {
1657  // the species just swapped in is a mineral or a gas, so its equilibium molality is
1658  // undefined: make a guess for its molality
1659  molality_of_species_just_swapped_in =
1660  std::max(_min_initial_molality, 0.9 * bm(swap_out_of_basis));
1661  }
1662  Real molality_of_species_just_swapped_out =
1663  _basis_molality[swap_out_of_basis]; // can be negative if a consumed mineral
1664  _basis_molality[swap_out_of_basis] = molality_of_species_just_swapped_in;
1665  _eqm_molality[swap_into_basis] =
1666  std::max(0.0, molality_of_species_just_swapped_out); // this gets recalculated in
1667  // computeConsistentConfiguration, below
1668 
1669  // depending if minerals were swapped in or out of the basis, the known activity may have
1670  // changed
1672 
1673  // the equilibrium constants will have changed due to the swap
1675 
1676  // due to re-orderings in mgd, the charge-balance basis index might have changed
1678  // charge balance might be able to be performed easily
1680 
1681  // the algebraic system has probably changed
1686  _basis_index);
1687 
1688  // finally compute a consistent configuration, based on the basis molalities, etc, above
1690 }
1691 
1692 unsigned
1694 {
1695  return _num_basis;
1696 }
1697 
1698 unsigned
1700 {
1701  return _num_eqm;
1702 }
1703 
1704 void
1705 GeochemicalSystem::setChargeBalanceSpecies(unsigned new_charge_balance_index)
1706 {
1707  // because the original mole number of the charge balance species may have been changed due to
1708  // enforcing charge balance:
1712  // now change the charge balance info
1713  _charge_balance_basis_index = new_charge_balance_index;
1715  // enforce charge-balance if easily possible
1717 }
1718 
1719 bool
1721 {
1722  if (_basis_molality[_charge_balance_basis_index] > threshold_molality)
1723  return false;
1724  unsigned best_species_opp_sign = 0;
1725  unsigned best_species_same_sign = 0;
1726  Real best_molality_opp_sign = 0.0;
1727  Real best_molality_same_sign = 0.0;
1728  for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
1729  {
1730  if (basis_i == _charge_balance_basis_index)
1731  continue;
1733  continue;
1734  else if (_mgd.basis_species_charge[basis_i] == 0.0)
1735  continue;
1736  else if (_basis_molality[basis_i] <= threshold_molality)
1737  continue;
1738  // we know basis_i is a charged species with set bulk moles and high molality
1739  if (_mgd.basis_species_charge[basis_i] *
1741  0.0)
1742  {
1743  // charge of opposite sign
1744  if (_basis_molality[basis_i] > best_molality_opp_sign)
1745  {
1746  best_species_opp_sign = basis_i;
1747  best_molality_opp_sign = _basis_molality[basis_i];
1748  }
1749  }
1750  else
1751  {
1752  // charge of same sign
1753  if (_basis_molality[basis_i] > best_molality_same_sign)
1754  {
1755  best_species_same_sign = basis_i;
1756  best_molality_same_sign = _basis_molality[basis_i];
1757  }
1758  }
1759  }
1760  if (best_species_opp_sign != 0)
1761  {
1762  // this is preferred over the same-sign version
1763  setChargeBalanceSpecies(best_species_opp_sign);
1764  return true;
1765  }
1766  else if (best_species_same_sign != 0)
1767  {
1768  // this is not preferred, but is better than no charge-balance species change
1769  setChargeBalanceSpecies(best_species_same_sign);
1770  return true;
1771  }
1772  return false;
1773 }
1774 
1775 bool
1777 {
1779  _mgd.basis_species_index.end())
1780  return false; // original charge-balance species no longer in basis
1781  const unsigned original_index = _mgd.basis_species_index.at(_original_charge_balance_species);
1782  if (original_index == _charge_balance_basis_index)
1783  return false; // current charge-balance species is the original
1784  setChargeBalanceSpecies(original_index);
1785  return true;
1786 }
1787 
1788 Real
1790 {
1792 }
1793 
1794 Real
1796 {
1798 }
1799 
1800 Real
1802 {
1804  std::sqrt(8.0 * GeochemistryConstants::GAS_CONSTANT *
1810 }
1811 
1812 Real
1814 {
1815  if (sp >= _num_surface_pot)
1816  mooseError("Cannot retrieve the surface potential for surface ",
1817  sp,
1818  " since there are only ",
1820  " surfaces involved in surface complexation");
1821  return -2.0 * GeochemistryConstants::GAS_CONSTANT *
1824 }
1825 
1826 Real
1828 {
1829  if (sp >= _num_surface_pot)
1830  mooseError("Cannot retrieve the surface charge for surface ",
1831  sp,
1832  " since there are only ",
1834  " surfaces involved in surface complexation");
1835  // pre = mol of charge per square metre. To convert to Coulombs/m^2:
1836  const Real pre = surfacePotPrefactor(sp) / _sorbing_surface_area[sp] *
1837  (-_surface_pot_expr[sp] + 1.0 / _surface_pot_expr[sp]);
1838  return pre * GeochemistryConstants::FARADAY;
1839 }
1840 
1841 void
1842 GeochemicalSystem::computeSorbingSurfaceArea(std::vector<Real> & sorbing_surface_area) const
1843 {
1844  for (unsigned sp = 0; sp < _num_surface_pot; ++sp)
1845  {
1846  sorbing_surface_area[sp] = _mgd.surface_sorption_area[sp];
1847  if (_mgd.basis_species_index.count(_mgd.surface_sorption_name[sp]) == 1)
1848  {
1849  const unsigned basis_ind = _mgd.basis_species_index.at(_mgd.surface_sorption_name[sp]);
1850  const Real grams =
1851  _mgd.basis_species_molecular_weight[basis_ind] * _basis_molality[basis_ind];
1852  sorbing_surface_area[sp] *= grams;
1853  }
1854  }
1855 }
1856 
1857 const std::vector<Real> &
1859 {
1860  return _sorbing_surface_area;
1861 }
1862 
1863 Real
1865 {
1866  return _temperature;
1867 }
1868 
1869 void
1871 {
1875 }
1876 
1877 void
1879  const std::vector<std::string> & names,
1880  const std::vector<Real> & values,
1881  const std::vector<bool> & constraints_from_molalities)
1882 {
1883  // assume temperature-dependent quantities have been built during instantiation
1884  // assume algebraic info has been built during instantiation
1885 
1886  /*
1887  * STEP 0: Check sizes are correct
1888  */
1889  const unsigned num_names = names.size();
1890  if (num_names != values.size())
1891  mooseError("When setting all molalities, names and values must have same size");
1892  if (num_names != _num_basis + _num_eqm + _num_surface_pot + _num_kin)
1893  mooseError("When setting all molalities, values must be provided for every species and surface "
1894  "potentials");
1895  if (constraints_from_molalities.size() != _num_basis)
1896  mooseError("constraints_from_molalities has size ",
1897  constraints_from_molalities.size(),
1898  " while number of basis species is ",
1899  _num_basis);
1900 
1901  /*
1902  * STEP 1: Read values into _basis_molality, _eqm_molality and _surface_pot_expr and
1903  * _kin_moles
1904  */
1905  // The is no guarantee is made that the user-supplied molalities are "good", except they must
1906  // be non-negative. There are lots of "finds" in the loops below, which is slow, but it
1907  // ensures all species are given molalities. This method is designed to be called only every
1908  // once-in-a-while (eg, at the start of a simulation)
1909  for (const auto & name_index : _mgd.basis_species_index)
1910  {
1911  const unsigned ind =
1912  std::distance(names.begin(), std::find(names.begin(), names.end(), name_index.first));
1913  if (ind < num_names)
1914  {
1915  if (_mgd.basis_species_gas[name_index.second])
1916  {
1917  if (values[ind] != 0.0)
1918  mooseError("Molality for gas ",
1919  name_index.first,
1920  " cannot be ",
1921  values[ind],
1922  ": it must be zero");
1923  else
1924  _basis_molality[name_index.second] = values[ind];
1925  }
1926  else if (_mgd.basis_species_mineral[name_index.second])
1927  {
1928  if (values[ind] < 0.0)
1929  mooseError("Molality for mineral ",
1930  name_index.first,
1931  " cannot be ",
1932  values[ind],
1933  ": it must be non-negative");
1934  else
1935  _basis_molality[name_index.second] = values[ind];
1936  }
1937  else if (values[ind] <= 0.0)
1938  mooseError("Molality for species ",
1939  name_index.first,
1940  " cannot be ",
1941  values[ind],
1942  ": it must be positive");
1943  else
1944  _basis_molality[name_index.second] = values[ind];
1945  }
1946  else
1947  mooseError("Molality (or free mineral moles, etc - whatever is appropriate) for species ",
1948  name_index.first,
1949  " needs to be provided when setting all molalities");
1950  }
1951  for (const auto & name_index : _mgd.eqm_species_index)
1952  {
1953  const unsigned ind =
1954  std::distance(names.begin(), std::find(names.begin(), names.end(), name_index.first));
1955  if (ind < num_names)
1956  {
1957  if (_mgd.eqm_species_gas[name_index.second] || _mgd.eqm_species_mineral[name_index.second])
1958  _eqm_molality[name_index.second] =
1959  0.0; // Note: explicitly setting to zero, irrespective of user input. The reason
1960  // for doing this is that during a restore, a basis species with positive
1961  // molality could become a secondary species, which should have zero molality
1962  else if (values[ind] < 0.0)
1963  mooseError("Molality for species ",
1964  name_index.first,
1965  " cannot be ",
1966  values[ind],
1967  ": it must be non-negative");
1968  else
1969  _eqm_molality[name_index.second] = values[ind];
1970  }
1971  else
1972  mooseError("Molality for species ",
1973  name_index.first,
1974  " needs to be provided when setting all molalities");
1975  }
1976  for (unsigned sp = 0; sp < _num_surface_pot; ++sp)
1977  {
1978  const unsigned ind =
1979  std::distance(names.begin(),
1980  std::find(names.begin(),
1981  names.end(),
1982  _mgd.surface_sorption_name[sp] + "_surface_potential_expr"));
1983  if (ind < num_names)
1984  {
1985  if (values[ind] <= 0.0)
1986  mooseError("Surface-potential expression for mineral ",
1988  " cannot be ",
1989  values[ind],
1990  ": it must be positive");
1991  _surface_pot_expr[sp] = values[ind];
1992  }
1993  else
1994  mooseError("Surface potential for mineral ",
1996  " needs to be provided when setting all molalities");
1997  }
1998  for (const auto & name_index : _mgd.kin_species_index)
1999  {
2000  const unsigned ind =
2001  std::distance(names.begin(), std::find(names.begin(), names.end(), name_index.first));
2002  if (ind < num_names)
2003  setKineticMoles(name_index.second, values[ind]);
2004  else
2005  mooseError("Moles for species ",
2006  name_index.first,
2007  " needs to be provided when setting all molalities");
2008  }
2009 
2010  /*
2011  * STEP 2: Alter _constraint_values if necessary
2012  */
2013  // If some of the constraints_from_molalities are false, then the molalities provided to this
2014  // method may have to be modified to satisfy the contraints: this alters _basis_molality so
2015  // must occur first
2016  for (unsigned i = 0; i < _num_basis; ++i)
2017  {
2018  const ConstraintMeaningEnum meaning = _constraint_meaning[i];
2019  if (meaning == ConstraintMeaningEnum::KG_SOLVENT_WATER ||
2022  {
2023  if (constraints_from_molalities[i])
2024  {
2025  // molalities provided to this method dictate the contraints:
2028  }
2029  else
2030  // contraints take precedence over the molalities provided to this method:
2032  }
2033  }
2034 
2035  // Potentially alter _constraint_value for the BULK contraints:
2036  for (unsigned i = 0; i < _num_basis; ++i)
2037  {
2038  const ConstraintMeaningEnum meaning = _constraint_meaning[i];
2039  if (meaning == ConstraintMeaningEnum::MOLES_BULK_WATER ||
2041  if (constraints_from_molalities[i])
2042  {
2043  // the constraint value should be overridden by the molality-computed bulk mole number
2046  }
2047  }
2048 
2049  // Potentially alter _constraint_value for ACTIVITY contraints:
2050  for (unsigned i = 0; i < _num_basis; ++i)
2051  if (constraints_from_molalities[i])
2052  {
2053  const ConstraintMeaningEnum meaning = _constraint_meaning[i];
2054  if (meaning == ConstraintMeaningEnum::FUGACITY)
2055  mooseError("Gas fugacity cannot be determined from molality: constraints_from_molalities "
2056  "must be set false for all gases");
2057  else if (meaning == ConstraintMeaningEnum::ACTIVITY)
2058  {
2059  if (i == 0)
2060  mooseError("Water activity cannot be determined from molalities: "
2061  "constraints_from_molalities "
2062  "must be set to false for water if activity of water is fixed");
2063  // the constraint value should be overidden by the molality provided to this method
2066  }
2067  }
2068 
2069  /*
2070  * STEP 3: Follow the initialize() and computeConsistentConfiguration() methods
2071  */
2072  // assume done already: buildTemperatureDependentQuantities
2074  // assume done already: buildAlgebraicInfo
2075  // should not be done, as basis_molality is set by this method instead: initBulkAndFree
2077  // should not be done, as these are set by this method: _eqm_molality.assign
2078  // should not be done, as these are set by this method: _surface_pot_expr.assign
2081  // for constraints_from_molalities = false then the following: (1) overrides the basis
2082  // molality provided to this method; (2) produces a slightly inconsistent equilibrium
2083  // geochemical system because basis_activity_coef was computed on the basis of the basis
2084  // molalities provided to this method
2087  // should not be done, as these are set by this method: computeEqmMolalities
2089  // should not be done, as these are set by this method: computeFreeMineralMoles
2091 }
2092 
2093 const std::vector<GeochemicalSystem::ConstraintMeaningEnum> &
2095 {
2096  return _constraint_meaning;
2097 }
2098 
2099 void
2101 {
2102  for (unsigned basis_ind = 0; basis_ind < _num_basis; ++basis_ind)
2106  changeConstraintToBulk(basis_ind);
2107 }
2108 
2109 void
2111 {
2112  if (basis_ind >= _num_basis)
2113  mooseError("Cannot changeConstraintToBulk for species ",
2114  basis_ind,
2115  " because there are only ",
2116  _num_basis,
2117  " basis species");
2118  if (_mgd.basis_species_gas[basis_ind])
2119  {
2120  // this is a special case where we have to swap out the gas in favour of an equilibrium
2121  // species
2122  unsigned swap_into_basis = 0;
2123  bool legitimate_swap_found = false;
2124  Real best_stoi = 0.0;
2125  for (unsigned j = 0; j < _num_eqm; ++j)
2126  {
2127  if (_mgd.eqm_species_gas[j] || _mgd.eqm_stoichiometry(j, basis_ind) == 0.0 ||
2129  continue;
2130  const Real stoi = std::abs(_mgd.eqm_stoichiometry(j, basis_ind));
2131  if (stoi > best_stoi)
2132  {
2133  best_stoi = stoi;
2134  swap_into_basis = j;
2135  legitimate_swap_found = true;
2136  }
2137  }
2138  if (legitimate_swap_found)
2139  performSwapNoCheck(basis_ind, swap_into_basis);
2140  else
2141  mooseError("Attempting to change constraint of gas ",
2142  _mgd.basis_species_name[basis_ind],
2143  " to MOLES_BULK_SPECIES, which involves a search for a suitable non-gas species "
2144  "to swap with. No such species was found");
2145  }
2146  else
2147  changeConstraintToBulk(basis_ind, computeBulkFromMolalities(basis_ind));
2148 }
2149 
2150 void
2151 GeochemicalSystem::changeConstraintToBulk(unsigned basis_ind, Real value)
2152 {
2153  if (basis_ind >= _num_basis)
2154  mooseError("Cannot changeConstraintToBulk for species ",
2155  basis_ind,
2156  " because there are only ",
2157  _num_basis,
2158  " basis species");
2159  if (_mgd.basis_species_gas[basis_ind])
2160  mooseError("Attempting to changeConstraintToBulk for a gas species. Since a swap is involved, "
2161  "you cannot specify a value for the bulk number of moles. You must use "
2162  "changeConstraintToBulk(basis_ind) method instead of "
2163  "changeConstraintToBulk(basis_ind, value)");
2164  if (basis_ind == 0)
2166  else
2168  setConstraintValue(basis_ind, value);
2169 
2170  // it is possible that FIXED_ACTIVITY just became MOLES_BULK_SPECIES
2172 
2173  // it is likely the algebraic system has changed
2178  _basis_index);
2179 }
2180 
2181 void
2182 GeochemicalSystem::addToBulkMoles(unsigned basis_ind, Real value)
2183 {
2184  if (basis_ind >= _num_basis)
2185  mooseError("Cannot addToBulkMoles for species ",
2186  basis_ind,
2187  " because there are only ",
2188  _num_basis,
2189  " basis species");
2190  if (!(_constraint_meaning[basis_ind] ==
2192  _constraint_meaning[basis_ind] ==
2194  return;
2195  setConstraintValue(basis_ind, _constraint_value[basis_ind] + value);
2196 }
2197 
2198 void
2199 GeochemicalSystem::setConstraintValue(unsigned basis_ind, Real value)
2200 {
2201  if (basis_ind >= _num_basis)
2202  mooseError("Cannot setConstraintValue for species ",
2203  basis_ind,
2204  " because there are only ",
2205  _num_basis,
2206  " basis species");
2207  _constraint_value[basis_ind] = value;
2208  _original_constraint_value[basis_ind] = value;
2209  switch (_constraint_meaning[basis_ind])
2210  {
2213  {
2215  break;
2216  }
2220  {
2221  // the changes resulting from this change are very similar to setAlgebraicVariables
2222  _basis_molality[basis_ind] = value;
2224  break;
2225  }
2227  {
2228  // the changes resulting from this change are very similar to setAlgebraicVariables
2229  _basis_activity[basis_ind] = value;
2230  _basis_molality[basis_ind] = 0.0;
2232  break;
2233  }
2235  {
2236  // the changes resulting from this change are very similar to setAlgebraicVariables
2237  _basis_activity[basis_ind] = value;
2238  _basis_molality[basis_ind] = _basis_activity[basis_ind] / _basis_activity_coef[basis_ind];
2240  break;
2241  }
2242  }
2243 }
2244 
2245 void
2247 {
2248  // Altering the constraints on bulk number of moles impacts various other things.
2249  // Here, we follow the initialize() and computeConsistentConfiguration() methods, picking out
2250  // what might have changed.
2251  // Because the constraint meanings have changed, enforcing charge-neutrality might be easy
2253  // Because the constraint values have changed, either through charge-neutrality or directly
2254  // through changing the constraint values, the bulk moles must be updated:
2255  for (unsigned i = 0; i < _num_basis; ++i)
2259  // Because the bulk mineral moles might have changed, the free mineral moles might have
2260  // changed
2261  computeFreeMineralMoles(_basis_molality); // free mineral moles might be changed
2262 }
2263 
2264 const std::string &
2266 {
2267  return _original_redox_lhs;
2268 }
2269 
2270 void
2272 {
2273  _mgd = mgd;
2274 }
2275 
2278 {
2279  return _swapper;
2280 }
2281 
2282 void
2284 {
2285  // mole numbers of basis minerals
2286  for (unsigned i = 0; i < _num_basis; ++i)
2287  if (_mgd.basis_species_mineral[i])
2288  _basis_molality[i] = value;
2289 
2290  // mole numbers of sorption sites
2291  for (const auto & name_info :
2292  _mgd.surface_complexation_info) // all minerals involved in surface complexation
2293  for (const auto & name_frac :
2294  name_info.second.sorption_sites) // all sorption sites on the given mineral
2295  {
2296  if (_mgd.basis_species_index.count(name_frac.first))
2297  _basis_molality[_mgd.basis_species_index.at(name_frac.first)] = value;
2298  else
2299  _eqm_molality[_mgd.eqm_species_index.at(name_frac.first)] = value;
2300  }
2301 
2302  // mole numbers of sorbed equilibrium species
2303  for (unsigned j = 0; j < _num_eqm; ++j)
2304  {
2305  if (_mgd.eqm_species_mineral[j])
2306  _eqm_molality[j] = 0.0; // Note: explicitly setting to zero, irrespective of user input,
2307  // to ensure consistency with the rest of the code
2309  _eqm_molality[j] = value;
2310  }
2311 
2312  // mole numbers of kinetic species
2313  for (unsigned k = 0; k < _num_kin; ++k)
2314  if (_mgd.kin_species_mineral[k])
2315  _kin_moles[k] = value;
2316 }
2317 
2318 Real
2320 {
2321  if (eqm_ind >= _num_eqm)
2322  mooseError("Cannot retrieve activity for equilibrium species ",
2323  eqm_ind,
2324  " since there are only ",
2325  _num_eqm,
2326  " equilibrium species");
2327  if (_mgd.eqm_species_mineral[eqm_ind])
2328  return 1.0;
2329  else if (_mgd.eqm_species_gas[eqm_ind])
2330  {
2331  Real log10f = 0.0;
2332  for (unsigned basis_i = 0; basis_i < _num_basis; ++basis_i)
2333  log10f += _mgd.eqm_stoichiometry(eqm_ind, basis_i) * std::log10(_basis_activity[basis_i]);
2334  log10f -= getLog10K(eqm_ind);
2335  return std::pow(10.0, log10f);
2336  }
2337  else
2338  return _eqm_activity_coef[eqm_ind] * _eqm_molality[eqm_ind];
2339 }
2340 
2341 const std::vector<Real> &
2343 {
2344  for (unsigned j = 0; j < _num_eqm; ++j)
2346  return _eqm_activity;
2347 }
2348 
2349 void
2350 GeochemicalSystem::updateOldWithCurrent(const DenseVector<Real> & mole_additions)
2351 {
2352  if (mole_additions.size() != _num_basis + _num_kin)
2353  mooseError("The increment in mole numbers (mole_additions) needs to be of size ",
2354  _num_basis,
2355  " + ",
2356  _num_kin,
2357  " but it is of size ",
2358  mole_additions.size());
2359 
2360  // Copy the current kin_moles to the old values
2361  for (unsigned k = 0; k < _num_kin; ++k)
2363 
2364  // The following:
2365  // - just returns if constraint is not BULK type.
2366  // - ensures charge balance if simple
2367  // - sets the constraint value and bulk_moles_old (for BULK-type species)
2368  // - computes basis_molality for the BULK-type mineral species
2369  for (unsigned i = 0; i < _num_basis; ++i)
2370  addToBulkMoles(i, mole_additions(i));
2371 
2372  // The following:
2373  // - sets bulk_moles_old to the Constraint value for BULK-type species (duplicating the above
2374  // function)
2375  // - for the other species, computes bulk_moles_old from the molalities
2377 
2378  // It is possible that the user would also like to enforceChargeBalance() now, and that is fine
2379  // - there will be no negative consequences
2380 }
2381 
2382 void
2384  DenseVector<Real> & mole_additions,
2385  DenseMatrix<Real> & dmole_additions)
2386 {
2387  if (_num_kin == 0)
2388  return;
2389 
2390  // check sizes
2391  const unsigned tot = mole_additions.size();
2392  if (!(tot == _num_kin + _num_basis && dmole_additions.m() == tot && dmole_additions.n() == tot))
2393  mooseError("addKineticRates: incorrectly sized additions: ",
2394  tot,
2395  " ",
2396  dmole_additions.m(),
2397  " ",
2398  dmole_additions.n());
2399 
2400  // construct eqm_activity for species that we need
2401  for (unsigned j = 0; j < _num_eqm; ++j)
2402  if (_mgd.eqm_species_gas[j] || _mgd.eqm_species_name[j] == "H+" ||
2403  _mgd.eqm_species_name[j] == "OH-")
2405 
2406  // calculate the rates and put into appropriate slots
2407  Real rate;
2408  Real drate_dkin;
2409  std::vector<Real> drate_dmol(_num_basis);
2410  for (const auto & krd : _mgd.kin_rate)
2411  {
2412  const unsigned kin = krd.kinetic_species_index;
2414  krd.promoting_monod_indices,
2415  krd.promoting_half_saturation,
2416  krd.description,
2424  _eqm_molality,
2425  _eqm_activity,
2427  _kin_moles[kin],
2429  _kin_log10K[kin],
2432  kin,
2433  _temperature,
2434  rate,
2435  drate_dkin,
2436  drate_dmol);
2437 
2438  /* the following block of code may be confusing at first sight.
2439  * (1) The usual case is that the kinetic reaction is written
2440  * kinetic -> basis_species, and direction = BOTH, and kinetic_bio_efficiency = -1
2441  * In this case, the following does mole_additions(kinetic_species) += -1 * rate * dt
2442  * If rate > 0 then the kinetic species decreases in mass.
2443  * The solver will then detect that the kinetic_species has changed, and adjust basis_species
2444  * accordingly, viz it will add rate * dt of basis species to the aqueous solution.
2445  * (2) The common biologically-catalysed case is the reaction is written
2446  * reactants -> products, catalysed by microbe. In the database file this is written as
2447  * microbe -> -reactants + products
2448  * The input file will set direction = BOTH, and kinetic_bio_efficiency != -1
2449  * Suppose that rate > 0, corresponding to reactants being converted to products.
2450  * With kinetic_bio_efficiency = -1) this would correspond to microbe decreasing in mass,
2451  * however with kinetic_bio_efficiency > 0, this is not true.
2452  * The following block of code sets
2453  * mole_additions(microbe) = kinetic_bio_efficiency * rate * dt > 0
2454  * that is, the microbe mass is increasing.
2455  * However, this means the solver will decrease products and increase reactants,
2456  * by kinetic_bio_efficiency * rate * dt because it sees the reaction microbe -> -reactants +
2457  * products in the database file.
2458  * This is clearly wrong, because we actually want the end result to be that the products have
2459  * increased by rate * dt, and the reactants to have decreased by rate * dt.
2460  * So, to counter the solver, we add (-1 - kinetic_bio_efficiency) * rate * dt of reactants and
2461  * add (1 + kinetic_bio_efficienty) * rate * dt of products.
2462  * (3) The other situation is the biological death, where direction = DEATH.
2463  * In this case the database file contains microbe -> -reactants + products
2464  * However, independent of rate, we don't want to change the molality of reactants or products.
2465  * Following the logic of (2), to counter the solver, we add -kinetic_bio_efficiency * rate * dt
2466  * of reactants and kinetic_bio_efficiency * rate * dt of products
2467  */
2468  const unsigned ind = kin + _num_basis;
2469  mole_additions(ind) += krd.description.kinetic_bio_efficiency * rate * dt;
2470  dmole_additions(ind, ind) += krd.description.kinetic_bio_efficiency * drate_dkin * dt;
2471  const Real extra_additions = (krd.description.direction == DirectionChoiceEnum::DEATH)
2472  ? krd.description.kinetic_bio_efficiency
2473  : krd.description.kinetic_bio_efficiency + 1.0;
2474  for (unsigned i = 0; i < _num_basis; ++i)
2475  {
2476  dmole_additions(ind, i) += krd.description.kinetic_bio_efficiency * drate_dmol[i] * dt;
2477  const Real stoi_fac = _mgd.kin_stoichiometry(kin, i) * extra_additions * dt;
2478  mole_additions(i) += stoi_fac * rate;
2479  dmole_additions(i, ind) += stoi_fac * drate_dkin;
2480  for (unsigned j = 0; j < _num_basis; ++j)
2481  dmole_additions(i, j) += stoi_fac * drate_dmol[j];
2482  }
2483 
2484  const Real eff = krd.description.progeny_efficiency;
2485  if (eff != 0.0)
2486  {
2487  const unsigned bio_i = krd.progeny_index;
2488  if (bio_i < _num_basis)
2489  {
2490  mole_additions(bio_i) += eff * rate * dt;
2491  dmole_additions(bio_i, ind) += eff * drate_dkin * dt;
2492  for (unsigned i = 0; i < _num_basis; ++i)
2493  dmole_additions(bio_i, i) += eff * drate_dmol[i] * dt;
2494  }
2495  else
2496  {
2497  for (unsigned i = 0; i < _num_basis; ++i)
2498  {
2499  mole_additions(i) += _mgd.eqm_stoichiometry(bio_i - _num_basis, i) * eff * rate * dt;
2500  dmole_additions(i, ind) +=
2501  _mgd.eqm_stoichiometry(bio_i - _num_basis, i) * eff * drate_dkin * dt;
2502  for (unsigned j = 0; j < _num_basis; ++j)
2503  dmole_additions(i, j) +=
2504  _mgd.eqm_stoichiometry(bio_i - _num_basis, i) * eff * drate_dmol[j] * dt;
2505  }
2506  }
2507  }
2508  }
2509 }
std::vector< bool > surface_sorption_related
surface_sorption_related[j] = true iff the j^th equilibrium species is involved in surface sorption ...
void initialize()
initialize all variables, ready for a Newton solve of the algebraic system
void computeConsistentConfiguration()
Compute a reasonably consistent configuration that can be used as an initial condition in a Newton pr...
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< Real > getAlgebraicBasisValues() const
DenseMatrix< Real > redox_stoichiometry
redox_stoichiometry(i, j) = stoichiometric coefficients for i^th redox species that is in disequilibr...
Real getTemperature() const
const unsigned _num_kin
number of kinetic species
DenseVector< Real > getBulkOldInOriginalBasis() const
std::vector< bool > _basis_activity_known
whether basis_activity[i] is fixed by the user
Real surfaceSorptionModifier(unsigned eqm_j) const
virtual Real waterActivity() const =0
Computes and returns the activity of water.
constexpr Real MOLES_PER_KG_WATER
std::string redox_lhs
the name of the species on the left-hand side of the redox equations.
const std::vector< Real > & getKineticMoles() const
std::vector< bool > kin_species_mineral
kin_species_mineral[j] = true iff the j^th kinetic species is a mineral
Real getIonicStrength() const
Get the ionic strength.
std::vector< bool > basis_species_transported
basis_species_transported[j] = true iff the j^th basis species is transported in reactive-transport s...
void initBulkAndFree(std::vector< Real > &bulk_moles_old, std::vector< Real > &basis_molality) const
based on _constrained_value and _constraint_meaning, populate nw, bulk_moles_old and basis_molality w...
const std::vector< Real > & getBasisActivity() const
virtual const char * what() const
std::vector< std::string > surface_sorption_name
surface_sorption_name[k] = name of the mineral involved in surface sorption.
const std::vector< std::string > _swap_in
Species to immediately add to the basis in favor of _swap_out.
void changeConstraintToBulk(unsigned basis_ind)
Changes the constraint to MOLES_BULK_SPECIES (or MOLES_BULK_WATER if basis_ind = 0) for the basis ind...
const unsigned _num_basis
number of basis species
std::vector< Real > _basis_molality
IMPORTANT: this is.
std::vector< Real > _kin_log10K
equilibrium constant of the kinetic species
const std::vector< Real > & getBulkMolesOld() const
std::string _original_redox_lhs
The left-hand-side of the redox equations in _mgd after initial swaps.
std::vector< unsigned > _algebraic_index
_algebraic_index[i] = index in the algebraic system of the basis species i. _basis_index[_algebraic_i...
void setChargeBalanceSpecies(unsigned new_charge_balance_index)
Set the charge-balance species to the basis index provided.
GeochemistryActivityCoefficients & _gac
Object to compute the activity coefficients and activity of water.
constexpr Real CELSIUS_TO_KELVIN
void mooseError(Args &&... args)
Real _min_initial_molality
Minimum molality ever used in an initial guess.
std::vector< bool > eqm_species_gas
eqm_species_gas[i] = true iff the i^th equilibrium species is a gas
std::vector< Real > _basis_activity_coef
basis activity coefficients
constexpr Real DIELECTRIC_CONSTANT_WATER
std::vector< Real > _eqm_activity
equilibrium activities.
Real log10ActivityProduct(unsigned eqm_j) const
std::unordered_map< std::string, unsigned > basis_species_index
basis_species_index[name] = index of the basis species, within all ModelGeochemicalDatabase internal ...
const ModelGeochemicalDatabase mgd
Fit the equilibrium constant values read from the thermodynamic databse at specified temperature valu...
void alterSystemBecauseBulkChanged()
Alter the GeochemicalSystem to reflect changes in bulk composition constraints that occur through...
void computeTransportedBulkFromMolalities(std::vector< Real > &transported_bulk) const
Computes the value of transported bulk moles for all basis species using the existing molalities...
std::vector< Real > _kin_moles_old
old mole number of kinetic species
std::vector< ConstraintUserMeaningEnum > _constraint_user_meaning
The user-defined meaning of the values in _constraint_value. In the constructor, this is ordered to h...
Real _temperature
The temperature in degC.
const unsigned _num_redox
number of redox species
DenseMatrix< Real > kin_stoichiometry
kin_stoichiometry(i, j) = stoichiometric coefficient for kinetic species "i" in terms of the basis sp...
std::vector< Real > kin_species_molecular_weight
all quantities have a molecular weight (g/mol)
std::vector< bool > _in_algebraic_system
if _in_algebraic_system[i] == true then we need to solve for the i^th basis-species molality ...
Real surfacePotPrefactor(unsigned sp) const
unsigned int size() const
std::string getLogKModel() const
Get the equilibrium constant model type.
unsigned int m() const
std::unordered_map< std::string, unsigned > eqm_species_index
eqm_species_index[name] = index of the equilibrium species (secondary aqueous species, redox couples in equilibrium with the aqueous solution, minerals in equilibrium with the aqueous solution, gases in equilibrium with the aqueous solution) within all ModelGeochemicalDatabase internal datastrcutres, with given name
Real log10KineticActivityProduct(unsigned kin) const
DenseMatrix< Real > redox_log10K
redox_log10K(i, j) = log10(equilibrium constant) for i^th redox species at the j^th temperature point...
void computeBulk(std::vector< Real > &bulk_moles_old) const
Computes the value of bulk_moles_old for all basis species.
unsigned getChargeBalanceBasisIndex() const
return the index of the charge-balance species in the basis list
std::vector< Real > _basis_activity
values of activity (for water, minerals and aqueous basis species) or fugacity (for gases) ...
static const std::string temperature
Definition: NS.h:59
void updateOldWithCurrent(const DenseVector< Real > &mole_additions)
Usually used at the end of a solve, to provide correct initial conditions to the next time-step...
void buildTemperatureDependentQuantities(Real temperature)
Using the provided value of temperature, build _eqm_log10K for each eqm species and redox species...
unsigned getNumKinetic() const
returns the number of kinetic species
unsigned _charge_balance_basis_index
The index in the list of basis species corresponding to the charge-balance species. This gets altered as the simulation progresses, due to swaps, and alterChargeBalanceSpecies.
void computeSorbingSurfaceArea(std::vector< Real > &sorbing_surface_area) const
Compute sorbing_surface_area depending on the current molality of the sorbing minerals.
Real ionicStrength(const ModelGeochemicalDatabase &mgd, const std::vector< Real > &basis_species_molality, const std::vector< Real > &eqm_species_molality, const std::vector< Real > &kin_species_molality) const
Compute ionic strength.
void calculateRate(const std::vector< Real > &promoting_indices, const std::vector< Real > &promoting_monod_indices, const std::vector< Real > &promoting_half_saturation, const KineticRateUserDescription &description, const std::vector< std::string > &basis_species_name, const std::vector< bool > &basis_species_gas, const std::vector< Real > &basis_molality, const std::vector< Real > &basis_activity, const std::vector< bool > &basis_activity_known, const std::vector< std::string > &eqm_species_name, const std::vector< bool > &eqm_species_gas, const std::vector< Real > &eqm_molality, const std::vector< Real > &eqm_activity, const DenseMatrix< Real > &eqm_stoichiometry, Real kin_moles, Real kin_species_molecular_weight, Real log10K, Real log10_activity_product, const DenseMatrix< Real > &kin_stoichiometry, unsigned kin, Real temp_degC, Real &rate, Real &drate_dkin, std::vector< Real > &drate_dmol)
Calclates a kinetic rate and its derivative.
DenseMatrix< Real > swap_to_original_basis
Swap matrix that allows expression in terms of the original basis.
std::vector< std::string > _constrained_species
Names of the species in that have their values fixed to _constraint_value with meanings _constraint_m...
const std::vector< Real > & computeAndGetEquilibriumActivity()
Computes activity for all equilibrium species (_eqm_activity) and returns a reference to the vector...
Real toMoles(Real quantity, const GeochemistryUnit unit, const std::string &species_name, const ModelGeochemicalDatabase &mgd)
Calculates the amount of moles corresponding to "quantity" "unit"s of species name, OR calculates the molality corresponding to "quantity" "unit"s of species name, whichever is appropriate.
std::vector< Real > _redox_log10K
equilibrium constant of the redox species
unsigned getNumInBasis() const
returns the number of species in the basis
std::unordered_map< std::string, unsigned > kin_species_index
kin_species_index[name] = index of the kinetic species, within all ModelGeochemicalDatabase internal ...
const std::vector< Real > & getKineticLog10K() const
std::vector< Real > _sorbing_surface_area
surface areas of the sorbing minerals
GeochemistryIonicStrength & _is
Object that provides the ionic strengths.
std::vector< Real > _constraint_value
Numerical values of the constraints on _constraint_species. In the constructor, this is ordered to ha...
void computeFreeMineralMoles(std::vector< Real > &basis_molality) const
Compute the free mineral moles (ie, basis_molality for basis species that are minerals), using the bulk_moles_old.
DenseMatrix< Real > eqm_stoichiometry
eqm_stoichiometry(i, j) = stoichiometric coefficient for equilibrium species "i" in terms of the basi...
void vector_mult_transpose(DenseVector< Real > &dest, const DenseVector< Real > &arg) const
unsigned _iters_to_make_consistent
Iterations to make the initial configuration consistent.
ConstraintMeaningEnum
Each basis species has one of the following constraints.
virtual void setInternalParameters(Real temperature, const ModelGeochemicalDatabase &mgd, const std::vector< Real > &basis_species_molality, const std::vector< Real > &eqm_species_molality, const std::vector< Real > &kin_species_molality)=0
Sets internal parameters, such as the ionic strength and Debye-Huckel parameters, prior to computing ...
const std::vector< Real > & getTemperatures() const
Get the temperature points that the equilibrium constant is defined at.
std::vector< Real > _bulk_moles_old
Number of bulk moles of basis species (this includes contributions from kinetic species, in contrast to Bethke)
Real getTotalChargeOld() const
void setKineticMoles(unsigned kin, Real moles)
Sets the current AND old mole number for a kinetic species.
Class to swap basis species with equilibrium species.
std::vector< Real > _kin_moles
mole number of kinetic species
constexpr Real PERMITTIVITY_FREE_SPACE
std::vector< unsigned > surface_sorption_number
surface_sorption_number[j] = the index of the surface potential that should be used to modify the equ...
const std::vector< Real > & getEquilibriumActivityCoefficient() const
GeochemistrySpeciesSwapper & _swapper
swapper that can swap species into and out of the basis
Real getEquilibriumActivity(unsigned eqm_ind) const
Returns the value of activity for the equilibrium species with index eqm_index.
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
std::string _charge_balance_species
The species used to enforce charge balance.
Real getStoichiometricIonicStrength() const
Get the stoichiometric ionic strength.
unsigned getNumInAlgebraicSystem() const
std::vector< Real > _original_constraint_value
Numerical values of the constraints on _constraint_species. In the constructor, this is ordered to ha...
const std::string name
Definition: Setup.h:20
std::vector< Real > _surface_pot_expr
surface potential expressions.
const std::string _original_charge_balance_species
The species used to enforce charge balance, as provided in the constructor.
const std::vector< Real > & getSorbingSurfaceArea() const
virtual void generate()
const std::vector< Real > & getBasisActivityCoefficient() const
Calculators to compute ionic strength and stoichiometric ionic strength.
Base class to compute activity coefficients for non-minerals and non-gases (since these species do no...
const ModelGeochemicalDatabase & getModelGeochemicalDatabase() const
void addKineticRates(Real dt, DenseVector< Real > &mole_additions, DenseMatrix< Real > &dmole_additions)
Computes the kinetic rates and their derivatives based on the current values of molality, mole number, etc, and then, using dt, calculates the appropriate mole_additions and dmole_additions.
PetscErrorCode PetscInt const PetscInt IS * is
std::vector< GeochemistryUnitConverter::GeochemistryUnit > _constraint_unit
Units of the _constraint_value when the GeochemicalSystem is constructed. This is used during the con...
Real computeBulkFromMolalities(unsigned basis_ind) const
DenseMatrix< Real > kin_log10K
kin_log10K(i, j) = log10(equilibrium constant for the i^th kinetic species at the j^th temperature po...
std::vector< ConstraintMeaningEnum > _constraint_meaning
The meaning of the values in _constraint_value. In the constructor, this is ordered to have the same ...
std::vector< T > & get_values()
void enforceChargeBalance()
Enforces charge balance by altering the constraint_value and bulk_moles_old of the charge-balance spe...
const std::vector< unsigned > & getAlgebraicIndexOfBasisSystem() const
void enforceChargeBalanceIfSimple(std::vector< Real > &constraint_value, std::vector< Real > &bulk_moles_old) const
If all non-charged basis species are provided with a bulk number of moles, alter the bulk number of m...
void buildKnownBasisActivities(std::vector< bool > &basis_activity_known, std::vector< Real > &basis_activity) const
Fully populates basis_activity_known, which is true if activity has been set by the user...
DenseMatrix< Real > eqm_log10K
eqm_log10K(i, j) = log10(equilibrium constant) for i^th equilibrium species at the j^th temperature p...
Real getRedoxLog10K(unsigned red) const
std::vector< Real > basis_species_molecular_weight
all quantities have a molecular weight (g)
bool revertToOriginalChargeBalanceSpecies()
Changes the charge-balance species to the original that is specified in the constructor (this might n...
std::vector< unsigned > _basis_index
_basis_index[i] = index in the basis of the algebraic system species i, for i<num_basis_in_algebraic_...
bool alterChargeBalanceSpecies(Real threshold_molality)
If the molality of the charge-balance basis species is less than threshold molality, then attempt to change the charge-balance species, preferably to another component with opposite charge and big molality.
void setModelGeochemicalDatabase(const ModelGeochemicalDatabase &mgd)
Copies a ModelGeochemicalDatabase into our _mgd structure.
unsigned getNumRedox() const
void computeEqmMolalities(std::vector< Real > &eqm_molality) const
compute the equilibrium molalities (not for minerals or gases)
unsigned _num_in_algebraic_system
number of unknowns in the algebraic system (includes molalities and surface potentials) ...
unsigned int get(unsigned int i) const
const GeochemicalDatabaseReader * original_database
a pointer to the original database used to build this ModelGeochemicalDatabase
Real getLog10K(unsigned j) const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const unsigned _num_surface_pot
number of surface potentials
Real getSurfacePotential(unsigned sp) const
std::vector< bool > eqm_species_transported
eqm_species_transported[i] = true iff the i^th eqm species is transported in reactive-transport sims ...
std::vector< Real > getAlgebraicVariableValues() const
void checkAndInitialize(const std::vector< std::string > &kin_name, const std::vector< Real > &kin_initial, const std::vector< GeochemistryUnitConverter::GeochemistryUnit > &kin_unit)
Used during construction: checks for sane inputs and initializes molalities, etc, using the initializ...
void updateBasisMolalityForKnownActivity(std::vector< Real > &basis_molality) const
For basis aqueous species (not water, minerals or gases) with activity set by the user...
void performSwap(ModelGeochemicalDatabase &mgd, const std::string &replace_this, const std::string &with_this)
Check that replacing the named basis species with the named equilibrium species is valid...
void buildAlgebraicInfo(std::vector< bool > &in_algebraic_system, unsigned &num_basis_in_algebraic_system, unsigned &num_in_algebraic_system, std::vector< unsigned > &algebraic_index, std::vector< unsigned > &basis_index) const
Builds in_algebraic_system, algebraic_index and basis_index, and sets num_basis_in_algebraic_system a...
unsigned getNumSurfacePotentials() const
return the number of surface potentials
void setTemperature(Real temperature)
Sets the temperature to the given quantity.
const unsigned _num_eqm
number of equilibrium species
std::vector< Real > _eqm_molality
molality of equilibrium species
Real getResidualComponent(unsigned algebraic_ind, const DenseVector< Real > &mole_additions) const
Return the residual of the algebraic system for the given algebraic index.
std::vector< Real > basis_species_charge
all quantities have a charge (mineral charge = 0, gas charge = 0, oxide charge = 0) ...
Real stoichiometricIonicStrength(const ModelGeochemicalDatabase &mgd, const std::vector< Real > &basis_species_molality, const std::vector< Real > &eqm_species_molality, const std::vector< Real > &kin_species_molality) const
Compute stoichiometric ionic strength.
Real log10RedoxActivityProduct(unsigned red) const
void resize(const unsigned int new_m, const unsigned int new_n)
std::vector< bool > basis_species_gas
basis_species_gas[j] = true iff the j^th basis species is a gas
std::vector< bool > basis_species_mineral
basis_species_mineral[j] = true iff the j^th basis species is a mineral
std::vector< Real > _eqm_log10K
equilibrium constant of the equilibrium species
ModelGeochemicalDatabase & _mgd
The minimal geochemical database.
void setSolventMassAndFreeMolalityAndMineralMolesAndSurfacePotsAndKineticMoles(const std::vector< std::string > &names, const std::vector< Real > &values, const std::vector< bool > &constraints_from_molalities)
Sets:
void setAlgebraicVariables(const DenseVector< Real > &algebraic_var)
Set the variables in the algebraic system (molalities and potentially the mass of solvent water...
std::vector< Real > eqm_species_charge
all quantities have a charge (mineral charge = 0, gas charge = 0, oxide charge = 0) ...
std::vector< std::string > kin_species_name
kin_species_name[j] = name of the j^th kinetic species
Data structure to hold all relevant information from the database file.
std::vector< Real > _eqm_activity_coef
equilibrium activity coefficients
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
std::vector< bool > eqm_species_mineral
eqm_species_mineral[i] = true iff the i^th equilibrium species is a mineral
void performSwapNoCheck(unsigned swap_out_of_basis, unsigned swap_into_basis)
Perform the basis swap, and ensure that the resulting system is consistent.
const std::vector< Real > & getSolventMassAndFreeMolalityAndMineralMoles() const
Real getSolventWaterMass() const
Returns the mass of solvent water.
DenseVector< Real > getTransportedBulkInOriginalBasis() const
void computeJacobian(const DenseVector< Real > &res, DenseMatrix< Real > &jac, const DenseVector< Real > &mole_additions, const DenseMatrix< Real > &dmole_additions) const
Compute the Jacobian for the algebraic system and put it in jac.
const std::vector< bool > & getInAlgebraicSystem() const
const GeochemistrySpeciesSwapper & getSwapper() const
unsigned int n() const
void addToBulkMoles(unsigned basis_ind, Real value)
Add to the MOLES_BULK_SPECIES (or MOLES_BULK_WATER if basis_ind = 0) for the basis species...
const std::vector< bool > & getBasisActivityKnown() const
DenseVector< Real > getAlgebraicVariableDenseValues() const
void performSwap(unsigned swap_out_of_basis, unsigned swap_into_basis)
Perform the basis swap, and ensure that the resulting system is consistent.
void setConstraintValue(unsigned basis_ind, Real value)
Set the constraint value for the basis species.
unsigned getNumInEquilibrium() const
returns the number of species in equilibrium with the basis components
std::unordered_map< std::string, SurfaceComplexationInfo > surface_complexation_info
Holds info on surface complexation, if any, in the model.
const std::vector< std::string > _swap_out
Species to immediately remove from the basis in favor of _swap_in.
unsigned getNumBasisInAlgebraicSystem() const
return the number of basis species in the algebraic system
DenseMatrix sub_matrix(unsigned int row_id, unsigned int row_size, unsigned int col_id, unsigned int col_size) const
void closeSystem()
Changes a KG_SOLVENT_WATER constraint to MOLES_BULK_WATER (if there is such a constraint) and all FRE...
const std::vector< unsigned > & getBasisIndexOfAlgebraicSystem() const
MooseUnits pow(const MooseUnits &, int)
void setMineralRelatedFreeMoles(Real value)
Set the free mole number of mineral-related species to the value provided.
std::vector< std::string > eqm_species_name
eqm_species_name[i] = name of the i^th eqm species
static const std::string k
Definition: NS.h:130
const std::vector< Real > & getEquilibriumMolality() const
for(PetscInt i=0;i< nvars;++i)
Real getSurfaceCharge(unsigned sp) const
std::vector< std::string > basis_species_name
basis_species_name[j] = name of the j^th basis species
const std::vector< GeochemicalSystem::ConstraintMeaningEnum > & getConstraintMeaning() const
const std::string & getOriginalRedoxLHS() const
virtual void buildActivityCoefficients(const ModelGeochemicalDatabase &mgd, std::vector< Real > &basis_activity_coef, std::vector< Real > &eqm_activity_coef) const =0
Compute the activity coefficients and store them in basis_activity_coef and eqm_activity_coef Note: ...
GeochemicalSystem(ModelGeochemicalDatabase &mgd, GeochemistryActivityCoefficients &gac, GeochemistryIonicStrength &is, GeochemistrySpeciesSwapper &swapper, const std::vector< std::string > &swap_out_of_basis, const std::vector< std::string > &swap_into_basis, const std::string &charge_balance_species, const std::vector< std::string > &constrained_species, const std::vector< Real > &constraint_value, const MultiMooseEnum &constraint_unit, const MultiMooseEnum &constraint_user_meaning, Real initial_temperature, unsigned iters_to_make_consistent, Real min_initial_molality, const std::vector< std::string > &kin_name, const std::vector< Real > &kin_initial_moles, const MultiMooseEnum &kin_unit)
Construct the geochemical system, check consistency of the constructor&#39;s arguments, and initialize all internal variables (molalities, bulk compositions, equilibrium constants, activities, activity coefficients, surface potentials and kinetic mole numbers) and set up the algebraic system
unsigned _num_basis_in_algebraic_system
number of unknown molalities in the algebraic system. Note: surface potentials (if any) are extra qua...
void computeRemainingBasisActivities(std::vector< Real > &basis_activity) const
Compute the activity of water and put in basis_activity[0], and use the _basis_activity_coef and _bas...
std::vector< Real > getSaturationIndices() const
std::vector< KineticRateDefinition > kin_rate
rates given to kinetic species.