Using the transport model classes
Class hierarchy
BaseTransportModel1D
is the abstract base class for all other classes. It includes functions i.e. to retrieve results, set up the model, etc., as far as they are not solver-specific.
Derived model classes are:
EulerBackwardsModel1D
, which uses the implicit Euler (first order) time evolution scheme andCrankNicolsonTransportModel1D
, which builds on some routines of the former and implements the Crank-Nicolson scheme.
The classes in ensemble.py
are wrappers to run multiple submodels of a given transport problem in parallel.
A submodel consists of the transport model for a complete set of radionuclides, that is, a set of radionuclides in which
no radionuclide decays into a radionuclide that is not included in the submodel
General remarks
The life cycle of transport model objects consists of instantiation/parametrization, running the model, and analyzing the results. Parametrization always happens at instantiation; i.e. parameters should not be changed after instantiation. Similarly, running the same model object twice will usually not work.
Instantiation/Parametrization
All model classes share, as far as possible, the same interface. For example, the EulerBackwardsModel1D
has the following arguments:
def __init__(self,
species,
parameters_rock,
parameters_transport_material=None,
geometry=None,
bcs=None,
time_interval=1e6 * u.yr,
options=None,
parameters_source=None
):
species
is just a list of the radionuclides considered in the model.
The physical parameters of the model domain are provided via
two arguments, parameters_rock
and parameters_transport_material
. The former is structured as a list of geological units,
where each list entry is a dictionary. An example is shown below
import astropy.units as u
parameters_rock = [{'name': "unit1",
'thickness': 1000 * u.m,
'porosity': 0.1,
'head_gradient': 0.0,
'hydraulic_conductivity': 2e-13 * u.m / u.s,
'bulk_density': 2650 * u.kg / u.m**3}]
This defines a setup with only one layer, named unit1.
Note the usage of the astropy.units package. We make use of it in many places. parameters_transport_material
is another list of dictionaries,
with each entry defining the transport parameters for one geological unit. An example is again shown below.
parameters_transport_material = [
{
'I-129':
{'effective_diffusion_coefficient': 2.4e-12 * u.m**2 / u.s,
'sorption_coefficient': 0.0 * u.m**3 / u.kg},
'Ca-41':
{'effective_diffusion_coefficient': 1e-11 * u.m**2 / u.s,
'sorption_coefficient': 6.3e-4 * u.m**3 / u.kg}
}
]
In this case, sorption and diffusion coefficients are given for unit1, for the radionuclides I-129 and Ca-41. Both
parameters_rock
and parameters_transport_material
need to have the same length.
geometry
specifies the grid spacing and/or the grid. Internally, this is treated as an GeometryArgs object, but it can also be provided
as a dict
object, e.g.
geometry = {'dx':1*u.m}
This will generate a grid with a grid spacing of 1 m.
bcs
will be handled as a BCArgs
object, but can be provided a dictionary as well. If None is provided, Dirichlet boundary conditions at 0 mol/m^3 will be assigned per default.
time_interval
defines the total time period for the simulation. options
will be converted to an OptionsArgs
object,
but can again be provided as a dictionary. It can be used to set various options, like flags to include/not include specific
processes, etc. parameters_source
will evaluate to a SourceArgs
object and can be provided as a dictionary. It controls
how source terms are included in the calculation.
Running the model
A parametrized model can be run like this:
model.run(ics, t_eval)
where we have omitted some optional arguments. ics
is a data structure that provides the initial conditions, usually a list or a dictionary.
If it is a list, it has to provide the initial concentration vector for each species. If it is a dict, it has to provide the initial concentration vector
for each species as key:value pair. All concentration vectors must have astropy units. Units can be kg/m^3 or mol/m^3.
t_eval
is a vector of the times at which outputs of the simulation should be stored, and needs to have units.
Initial conditions need to specify the total mass of radionuclides per rock volume, including the sorbed fraction of radionuclides.
Analyzing the results
The calculated concentrations are always the total concentration of radionuclides (including any sorbed species), either in mass or amount of substance, per rock volume or per volume with the functions below. Internally, the concentrations are stored as total concentrations in mol/m^3 per fluid volume.
Access to the model results can be obtained by the functions get_curves_t
, get_curves_x
, and some other derived functions. The
former allows accessing the spatial distribution of concentrations at a fixed time, whereas the latter returns the temporal
evolution of the system at a fixed location. Some more complex routines are already implemented, e.g. plot_overview_c_x
in ensemble.VSGEnsembleModel1D
.
For example, the following code retrieves the spatial distribution of I-129 (as c
), and the location of grid nodes (as x
), at the last time stored:
x, c = model.get_curves_x("I-129", itime=-1, quantity="molar density", units=u.mol/u.l, per_rock_volume=True)