Reactive transport with biogeochemistry: aquifer zoning

This follows the example in Section 33.2 of Bethke (2007). Groundwater flows through an aquifer from west to east (towards the positive "" direction). The aquifer is 200km long, has a porosity of 0.3, and the flow rate is 10m(water).m(rock).yr. The groundwater has pH 7.5, and contains 1mmolal Ca, 2mmolal HCO, 40molal SO, and small amounts of acetate, sulfide and methane. Living in the aquifer are two species of microbe: sulfate reducers and methanogens, which are discussed in detail below. The model definition is:

[UserObjects]
  [definition]
    type = GeochemicalModelDefinition
    database_file = "../../../database/moose_geochemdb.json"
    basis_species = "H2O H+ CH3COO- CH4(aq) HS- Ca++ HCO3- SO4-- Fe++"
    kinetic_minerals = "sulfate_reducer methanogen"
    equilibrium_minerals = "*"
    kinetic_rate_descriptions = "rate_sulfate_reducer death_sulfate_reducer rate_methanogen death_methanogen"
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_conc.i)

Description of biogeochemistry

The two species of microbe are: sulfate reducers that catalyse the reaction (1) and methanogens that catalyse the reaction (2) Acetate (CHCOO) is added to the aquifer from surrounding rocks at a rate of mol.m.yr (the m in the denominator is a volume of rock, not a volume of groundwater) which is a rate consistent with observations. This feeds the microbes. The initial microbial population is small.

The standard MOOSE geochemistry database contains the necessary entries (see the biogeochemistry page for more discussion):


    "methanogen": {
      "species": {
        "CH3COO-": -1.0,
        "H2O": -1.0,
        "CH4(aq)": 1.0,
        "HCO3-": 1.0
      },
      "molar volume": 1,
      "molecular weight": 1E9,
      "logk": [2.727, 2.641, 2.699, 2.933, 3.28, 3.788, 4.336, 4.789]
    },
    "sulfate_reducer": {
      "species": {
        "CH3COO-": -1.0,
        "SO4--": -1.0,
        "HCO3-": 2.0,
        "HS-": 1.0
      },
      "molar volume": 1,
      "molecular weight": 1E3,
	"logk": [8.502, 8.404, 8.451, 8.657, 9.023, 9.457, 9.917, 10.31]
    },

The rate of Eq. (1) is assumed to be of the form (3) where

  • (units: kg) is the mass of solvent water;

  • mol.mg.smol.g.year is the intrinsic rate constant, where the mass (grams) in the denominator is the mass of the sulfate-reducer, and the direction is forward (dissolution of acetate only, not the reverse);

  • (units: g.kg) is the concentration of the sulfate-reducing microbe, where the mass (kg) in the denominator is the mass of solvent water;

  • and (units: mol.kg) are the molality of acetate and SO, respectively (these are free concentrations, not the bulk compositions);

  • mol.kg and mol.kg are the half-saturation constant of the acetate and SO, respectively;

  • is the activity product of Eq. (1);

  • is the equilibrium constant of Eq. (1);

  • J.mol is an estimate of the energy captured by the microbe for each mole turnover of Eq. (1);

  • J.K.mol is the gas constant;

  • (units: K) is the temperature.

In addition, the growth yield of the sulfate reducer is assumed to be 4.3g.mol. The sulfate_reducer has a molar mass of 1000g/mol, so kinetic_biological_efficiency = 4.3E-3.

[UserObjects]
  [rate_sulfate_reducer]
    type = GeochemistryKineticRate
    kinetic_species_name = "sulfate_reducer"
    intrinsic_rate_constant = 31.536 # 1E-9 mol(acetate)/mg(biomass)/s = 31.536 mol(acetate)/g(biomass)/year
    multiply_by_mass = true
    promoting_species_names = 'CH3COO- SO4--'
    promoting_indices = '1 1'
    promoting_monod_indices = '1 1'
    promoting_half_saturation = '70E-6 200E-6'
    direction = dissolution
    kinetic_biological_efficiency = 4.3E-3 # 4.3 g(biomass)/mol(acetate) = 4.3E-3 mol(biomass)/mol(acetate) (because sulfate_reducer has molar mass of 1E3 g/mol)
    energy_captured = 45E3
    theta = 0.2
    eta = 1
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_conc.i)

The rate of Eq. (2) is assumed to be of the form (4) where

  • (units: kg) is the mass of solvent water;

  • mol.mg.smol.g.year is the intrinsic rate constant, where the mass (grams) in the denominator is the mass of methanogen, and the direction is forward (dissolution of acetate only, not the reverse);

  • (units: g.kg) is the methanogen concentration, where the mass (kg) in the denominator is the mass of solvent water;

  • (units: mol.kg) is the molality of acetate (this is free concentration, not bulk composition);

  • mol.kg is the half-saturation constant of the acetate

  • is the activity product of Eq. (2);

  • is the equilibrium constant of Eq. (2);

  • J.mol is an estimate of the energy captured by the microbe for each mole turnover of Eq. (2);

  • J.K.mol is the gas constant;

  • (units: K) is the temperature.

In addition, the growth yield of the sulfate reducer is assumed to be 2g.mol. The methanogen has a molar mass of g/mol, so kinetic_biological_efficiency = 2.0E-9.

[UserObjects]
  [rate_methanogen]
    type = GeochemistryKineticRate
    kinetic_species_name = "methanogen"
    intrinsic_rate_constant = 63.072 # 2E-9 mol(acetate)/mg(biomass)/s = 63.072 mol(acetate)/g(biomass)/year
    multiply_by_mass = true
    promoting_species_names = 'CH3COO-'
    promoting_indices = '1'
    promoting_monod_indices = '1'
    promoting_half_saturation = '20E-3'
    direction = dissolution
    kinetic_biological_efficiency = 2.0E-9 # 2 g(biomass)/mol(acetate) = 2E-9 mol(biomass)/mol(acetate)  (because methanogen has molar mass of 1E9 g/mol)
    energy_captured = 24E3
    theta = 0.5
    eta = 1
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_conc.i)

In addition, both species of microbe are assumed to die at a rate of s, which is implemented in the following GeochemistryKineticRate userobjects:

[UserObjects]
  [death_sulfate_reducer]
    type = GeochemistryKineticRate
    kinetic_species_name = "sulfate_reducer"
    intrinsic_rate_constant = 0.031536E-3 # 1E-9 g(biomass)/g(biomass)/s = 0.031536 g(biomass)/g(biomass)/year = 0.031536E-3 mol(biomass)/g(biomass)/year (because sulfate_reducer has molar mass of 1E3 g/mol)
    multiply_by_mass = true
    direction = death
    eta = 0.0
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_conc.i)
[UserObjects]
  [death_methanogen]
    type = GeochemistryKineticRate
    kinetic_species_name = "methanogen"
    intrinsic_rate_constant = 0.031536E-9 # 1E-9 g(biomass)/g(biomass)/s = 0.031536 g(biomass)/g(biomass)/year = 0.031536E-9 mol(biomass)/g(biomass)/year (because methanogen has molar mass of 1E9 g/mol)
    multiply_by_mass = true
    direction = death
    eta = 0.0
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_conc.i)

To avoid the accumulation of HS from the sulfate reducers, the aquifer is assumed to include small amounts of siderite minerals, which reaction to form mackinawite:

The remainder of the geochemistry is standard, except for the source_species_rates that encode the groundwater flow that are sent from the flow App described below:

rate_Ca_diffuse = 6.66667E-9 # 2E-6 mol.m^-3.yr^-1 = 2E-9 mol.litre^-1.yr^-1 divided by porosity of 0.3
rate_CH3COO_diffuse = 13.3333E-9 # 4E-6 mol.m^-3.yr^-1 = 4E-9 mol.litre^-1.yr^-1 divided by porosity of 0.3
[Mesh]
  [gen]
    type = GeneratedMeshGenerator
    dim = 1
    nx = 10
    xmin = 0
    xmax = 200000
  []
[]

[GlobalParams]
  point = '100000 0 0'
  reactor = reactor
[]

[SpatialReactionSolver]
  model_definition = definition
  geochemistry_reactor_name = reactor
  swap_into_basis = 'Siderite'
  swap_out_of_basis = 'Fe++'
  prevent_precipitation = 'Pyrite Troilite'
  charge_balance_species = "HCO3-"
  constraint_species = "H2O              Ca++             HCO3-            SO4--            CH3COO-          HS-              CH4(aq)          Siderite         H+"
# ASSUME that 1 litre of solution initially contains:
  constraint_value = "  1.0              1E-3             2E-3             0.04E-3          1E-9             1E-9             1E-9             1               -7.5"
  constraint_meaning = "kg_solvent_water bulk_composition bulk_composition bulk_composition bulk_composition bulk_composition bulk_composition free_mineral     log10activity"
  constraint_unit = "   kg               moles            moles            moles            moles            moles            moles            cm3            dimensionless"
  controlled_activity_name = 'H+'
  controlled_activity_value = 3.16227E-8 # this is pH=7.5
  kinetic_species_name = "sulfate_reducer methanogen"
  kinetic_species_initial_value = '1E-6 1E-6'
  kinetic_species_unit = 'mg mg'
  source_species_names = "H2O              Ca++                       SO4--            CH3COO-                        HS-              CH4(aq)       Fe++"
  source_species_rates = "rate_H2O_per_1l  rate_Ca_per_1l_with_source rate_SO4_per_1l  rate_CH3COO_per_1l_with_source rate_HS_per_1l rate_CH4_per_1l rate_Fe_per_1l"
  ramp_max_ionic_strength_initial = 1
  ramp_max_ionic_strength_subsequent = 1
  execute_console_output_on = ''
  solver_info = true
  evaluate_kinetic_rates_always = true
  adaptive_timestepping = true
  abs_tol = 1E-14
  precision = 16
[]

[UserObjects]
  [rate_sulfate_reducer]
    type = GeochemistryKineticRate
    kinetic_species_name = "sulfate_reducer"
    intrinsic_rate_constant = 31.536 # 1E-9 mol(acetate)/mg(biomass)/s = 31.536 mol(acetate)/g(biomass)/year
    multiply_by_mass = true
    promoting_species_names = 'CH3COO- SO4--'
    promoting_indices = '1 1'
    promoting_monod_indices = '1 1'
    promoting_half_saturation = '70E-6 200E-6'
    direction = dissolution
    kinetic_biological_efficiency = 4.3E-3 # 4.3 g(biomass)/mol(acetate) = 4.3E-3 mol(biomass)/mol(acetate) (because sulfate_reducer has molar mass of 1E3 g/mol)
    energy_captured = 45E3
    theta = 0.2
    eta = 1
  []
  [death_sulfate_reducer]
    type = GeochemistryKineticRate
    kinetic_species_name = "sulfate_reducer"
    intrinsic_rate_constant = 0.031536E-3 # 1E-9 g(biomass)/g(biomass)/s = 0.031536 g(biomass)/g(biomass)/year = 0.031536E-3 mol(biomass)/g(biomass)/year (because sulfate_reducer has molar mass of 1E3 g/mol)
    multiply_by_mass = true
    direction = death
    eta = 0.0
  []
  [rate_methanogen]
    type = GeochemistryKineticRate
    kinetic_species_name = "methanogen"
    intrinsic_rate_constant = 63.072 # 2E-9 mol(acetate)/mg(biomass)/s = 63.072 mol(acetate)/g(biomass)/year
    multiply_by_mass = true
    promoting_species_names = 'CH3COO-'
    promoting_indices = '1'
    promoting_monod_indices = '1'
    promoting_half_saturation = '20E-3'
    direction = dissolution
    kinetic_biological_efficiency = 2.0E-9 # 2 g(biomass)/mol(acetate) = 2E-9 mol(biomass)/mol(acetate)  (because methanogen has molar mass of 1E9 g/mol)
    energy_captured = 24E3
    theta = 0.5
    eta = 1
  []
  [death_methanogen]
    type = GeochemistryKineticRate
    kinetic_species_name = "methanogen"
    intrinsic_rate_constant = 0.031536E-9 # 1E-9 g(biomass)/g(biomass)/s = 0.031536 g(biomass)/g(biomass)/year = 0.031536E-9 mol(biomass)/g(biomass)/year (because methanogen has molar mass of 1E9 g/mol)
    multiply_by_mass = true
    direction = death
    eta = 0.0
  []
  [definition]
    type = GeochemicalModelDefinition
    database_file = "../../../database/moose_geochemdb.json"
    basis_species = "H2O H+ CH3COO- CH4(aq) HS- Ca++ HCO3- SO4-- Fe++"
    kinetic_minerals = "sulfate_reducer methanogen"
    equilibrium_minerals = "*"
    kinetic_rate_descriptions = "rate_sulfate_reducer death_sulfate_reducer rate_methanogen death_methanogen"
  []
[]

[Executioner]
  type = Transient
  [TimeStepper]
    type = FunctionDT
    function = 'min(0.1 * (t + 1), 100)'
  []
  end_time = 20000
[]

[AuxVariables]
  [rate_H2O_per_1l] # change in H2O per 1 litre of aqueous solution that we consider at each node
  []
  [rate_CH3COO_per_1l] # change in CH3COO- per 1 litre of aqueous solution that we consider at each node
  []
  [rate_CH4_per_1l] # change in CH4(aq) per 1 litre of aqueous solution that we consider at each node
  []
  [rate_HS_per_1l] # change in HS- per 1 litre of aqueous solution that we consider at each node
  []
  [rate_Ca_per_1l] # change in Ca++ per 1 litre of aqueous solution that we consider at each node
  []
  [rate_SO4_per_1l] # change in SO4-- per 1 litre of aqueous solution that we consider at each node
  []
  [rate_Fe_per_1l] # change in Fe++ per 1 litre of aqueous solution that we consider at each node
  []

  [rate_CH3COO_per_1l_with_source] # change in CH3COO- per 1 litre of aqueous solution that we consider at each node, including the diffuse source
  []
  [rate_Ca_per_1l_with_source] # change in Ca per 1 litre of aqueous solution that we consider at each node, including the diffuse source
  []

  [transported_H2O]
  []
  [transported_CH3COO]
  []
  [transported_CH4]
  []
  [transported_HS]
  []
  [transported_Ca]
  []
  [transported_SO4]
  []
  [transported_Fe]
  []
[]

[AuxKernels]
  [rate_CH3COO_per_1l_with_source]
    type = ParsedAux
    args = 'rate_CH3COO_per_1l'
    variable = rate_CH3COO_per_1l_with_source
    function = 'rate_CH3COO_per_1l + ${rate_CH3COO_diffuse}'
    execute_on = 'timestep_begin timestep_end'
  []
  [rate_Ca_per_1l_with_source]
    type = ParsedAux
    args = 'rate_Ca_per_1l'
    variable = rate_Ca_per_1l_with_source
    function = 'rate_Ca_per_1l + ${rate_Ca_diffuse}'
    execute_on = 'timestep_begin timestep_end'
  []
  [transported_H2O]
    type = GeochemistryQuantityAux
    variable = transported_H2O
    species = H2O
    quantity = transported_moles_in_original_basis
    execute_on = 'timestep_end'
  []
  [transported_CH3COO]
    type = GeochemistryQuantityAux
    variable = transported_CH3COO
    species = "CH3COO-"
    quantity = transported_moles_in_original_basis
    execute_on = 'timestep_end'
  []
  [transported_CH4]
    type = GeochemistryQuantityAux
    variable = transported_CH4
    species = "CH4(aq)"
    quantity = transported_moles_in_original_basis
    execute_on = 'timestep_end'
  []
  [transported_HS]
    type = GeochemistryQuantityAux
    variable = transported_HS
    species = "HS-"
    quantity = transported_moles_in_original_basis
    execute_on = 'timestep_end'
  []
  [transported_Ca]
    type = GeochemistryQuantityAux
    variable = transported_Ca
    species = "Ca++"
    quantity = transported_moles_in_original_basis
    execute_on = 'timestep_end'
  []
  [transported_SO4]
    type = GeochemistryQuantityAux
    variable = transported_SO4
    species = "SO4--"
    quantity = transported_moles_in_original_basis
    execute_on = 'timestep_end'
  []
  [transported_Fe]
    type = GeochemistryQuantityAux
    variable = transported_Fe
    species = "Fe++"
    quantity = transported_moles_in_original_basis
    execute_on = 'timestep_end'
  []
[]

[Postprocessors]
  [time]
    type = TimePostprocessor
  []
[]

[VectorPostprocessors]
  [data]
    type = LineValueSampler
    start_point = '0 0 0'
    end_point = '200000 0 0'
    num_points = 501 # NOTE
    sort_by = x
    variable = 'transported_CH4 transported_CH3COO transported_SO4 free_mg_sulfate_reducer free_mg_methanogen'
  []
[]

[Outputs]
  exodus = true
  [csv]
    type = CSV
    time_step_interval = 10
    execute_on = 'INITIAL TIMESTEP_END FINAL'
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_conc.i)

Flow

The operator-split method is used to couple the flow with the biogeochemistry, as discussed in the theory page. Since porosity does not change in this simulation, and the groundwater velocity is specified, only the groundwater (not the surrounding rock) is considered. The flow is implemented using GeochemistryTimeDerivative and ConservativeAdvection Kernels, acting on the mole number per litre of groundwater of each of the chemical components that flow through the aquifer:

[Kernels]
  [dot_H2O]
    type = GeochemistryTimeDerivative
    variable = conc_H2O
    save_in = rate_H2O_times_vv
  []
  [dot_CH3COO]
    type = GeochemistryTimeDerivative
    variable = conc_CH3COO
    save_in = rate_CH3COO_times_vv
  []
  [dot_CH4]
    type = GeochemistryTimeDerivative
    variable = conc_CH4
    save_in = rate_CH4_times_vv
  []
  [dot_HS]
    type = GeochemistryTimeDerivative
    variable = conc_HS
    save_in = rate_HS_times_vv
  []
  [dot_Ca]
    type = GeochemistryTimeDerivative
    variable = conc_Ca
    save_in = rate_Ca_times_vv
  []
  [dot_SO4]
    type = GeochemistryTimeDerivative
    variable = conc_SO4
    save_in = rate_SO4_times_vv
  []
  [dot_Fe]
    type = GeochemistryTimeDerivative
    variable = conc_Fe
    save_in = rate_Fe_times_vv
  []
  [adv_H2O]
    type = ConservativeAdvection
    velocity = velocity
    upwinding_type = full
    variable = conc_H2O
  []
  [adv_CH3COO]
    type = ConservativeAdvection
    velocity = velocity
    upwinding_type = full
    variable = conc_CH3COO
  []
  [adv_CH4]
    type = ConservativeAdvection
    velocity = velocity
    upwinding_type = full
    variable = conc_CH4
  []
  [adv_HS]
    type = ConservativeAdvection
    velocity = velocity
    upwinding_type = full
    variable = conc_HS
  []
  [adv_Ca]
    type = ConservativeAdvection
    velocity = velocity
    upwinding_type = full
    variable = conc_Ca
  []
  [adv_SO4]
    type = ConservativeAdvection
    velocity = velocity
    upwinding_type = full
    variable = conc_SO4
  []
  [adv_Fe]
    type = ConservativeAdvection
    velocity = velocity
    upwinding_type = full
    variable = conc_Fe
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_flow.i)

The rate of change of each component is saved into an AuxVariable, for instance rate_H2O_times_vv. This has units mol.yr, because the Variables represent the number of moles per litre of groundwater of each component at each node. To convert it to mol.yr.litre(groundwater), a ParsedAux is used, such as:

[AuxKernels]
  [rate_H2O_auxk]
    type = ParsedAux
    variable = rate_H2O
    args = 'rate_H2O_times_vv nodal_void_volume'
    function = 'rate_H2O_times_vv / nodal_void_volume'
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_flow.i)

Fresh groundwater is added at the left, by boundary conditions such as:

[BCs]
  [inject_H2O]
    type = DirichletBC
    boundary = 'left right'
    variable = conc_H2O
    value = ${eqm_H2O}
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_flow.i)

At the end of each flow timestep, the geochemistry solver is run:

[MultiApps]
  [react]
    type = TransientMultiApp
    input_files = bio_zoning_conc.i
    clone_parent_mesh = true
    execute_on = 'timestep_end' # This is critical
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_flow.i)

with the flow rates from the flow solver acting as source terms to the geochemistry simulation:

[Transfers]
  [changes_due_to_flow]
    type = MultiAppCopyTransfer
    to_multi_app = react
    source_variable = 'rate_H2O rate_CH3COO rate_CH4 rate_HS rate_Ca rate_SO4 rate_Fe' # change in mole number at every node / dt
    variable = 'rate_H2O_per_1l rate_CH3COO_per_1l rate_CH4_per_1l rate_HS_per_1l rate_Ca_per_1l rate_SO4_per_1l rate_Fe_per_1l' # change in moles at every node / dt
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_flow.i)

After the geochemistry solver has completed its timestep, the new concentrations are retrieved from the geochemistry model in preparation for the next flow timestep:

[Transfers]
  [transported_moles_from_geochem]
    type = MultiAppCopyTransfer
    from_multi_app = react
    source_variable = 'transported_H2O transported_CH3COO transported_CH4 transported_HS transported_Ca transported_SO4 transported_Fe'
    variable = 'conc_H2O conc_CH3COO conc_CH4 conc_HS conc_Ca conc_SO4 conc_Fe'
  []
[]
(modules/geochemistry/test/tests/kinetics/bio_zoning_flow.i)

Results

As seen in Figure 1, the simulation gradual evolves to steady-state in which the aquifer is partitioned into two distinct zones: the upstream zone is inhabited by the sulfate reducers, who drive the sulfate concentration to low values by around 100km from the fresh groundwater source (which sits at ). The downstream zone is inhabited by methanogens, who produce significant dissolved methane.

Figure 1: Results of the aquifer-zoning biogeochemistry reactive transport simulation

References

  1. Craig M. Bethke. Geochemical and Biogeochemical Reaction Modeling. Cambridge University Press, 2 edition, 2007. doi:10.1017/CBO9780511619670.[BibTeX]