7.2
general documentation
Groundwater flow module using CDO schemes

Introduction

The Hydrogeology module of code_saturne is a numerical model for groundwater flow and solute transport in continuous porous media. The flow part is based on the Richards equation, derived from the Darcy law and the conservation of mass. The transport part is based on the the classical advection-diffusion equation of tracers, slightly modified to account the specificities of groundwater transport.

This module can be used to simulate transfers of water and solutes in several saturated and/or unsaturated porous media. The flow part can be steady or unsteady, with isotropic or anisotropic permeabilities and allows for any type of soil water retention model thanks to a user-defined model. Two classical models are predefined: the saturated model and the van Genuchten-Mualen model. The transport part considers dispersion, sorption and radioactive decay. The partition between soil and water phases can be modeled by a classical Kd approach model. Additionaly solute precipitation/dissolution phenomena can also be taken into account by an instantaneous model.

Physical concepts and equations are presented in the theory guide

The groundwater flow module (GWF) relies on CDO vertex-based or CDO vertex+cell-based discretization schemes. Here is listed a set of references useful for the reader willing to get a better understanding of the mathematical concepts underpinning CDO schemes.

To set-up a GWF computation, one has to update the cs_user_parameters.c file and edit the function cs_user_model at least in simple cases. In more complex cases, editing cs_user_finalize_setup should be necessary.

Activate the GWF module

The first step is to activate the CDO module in the function cs_user_model (please refer to Activation of CDO/HHO schemes).

Then, one has to activate the groundwater flow (GWF) module in the function cs_user_model. The function to call is cs_gwf_activate.

There are three parameters:

  1. The main hydraulic model to consider (i.e. which equations have to be solved). Please refer to cs_gwf_model_type_t. There are currently two models available :
  2. An optional flag to specify a physical phenomena to take into account or to specify a numerical treatment to apply. If no option is needed, then simply set 0. Here are listed the available option flags:
  3. An optional flag to specify the activation of automatic postprocessing for quantities of interest related to groundwater flows. Set 0 if no additional postprocessing is requested. Here are listed the available options:

Remark: If a CS_GWF_MODEL_SATURATED_SINGLE_PHASE is set at the activation step, then one expects that all soil models are defined by the type CS_GWF_SOIL_SATURATED


Examples of activation of the GWF module

Example 1: Activate the GWF model with a fully saturated single-phase flow model and no other option.

{
/* For the groundwater flow module:
cs_gwf_activate(model_type, option_flag, post_flag);
If option_flag or post_flag = 0, then there is nothing to set. */
cs_flag_t option_flag = 0, post_flag = 0;
option_flag, post_flag);
}

Example 2: Second example: Activate the GWF model with an unsaturated single-phase flow model without any additional option.

0, /* No option to set */
CS_GWF_POST_PERMEABILITY); /* Post-processing options */

Example 3: Activate the GWF model with an unsaturated single-phase flow model. Moreover, one takes into account the gravity effect and the postprocessing of the soil permeability as well as the Darcy flux across domain boundaries.

{
/* Physical modelling option or numerical option */
CS_GWF_GRAVITATION,/* Take into account the gravity */
/* Automatic postprocessing options */
/* In this case, the gravity vector has to be defined (either using the GUI
or in cs_user_parameters() function */
}

Soils

The second step is to add at least one new soil. The add of soil(s) should be done before adding tracers inside the function cs_user_model and after having activated the GWF module. Two functions are available to add a new soil:

  • cs_gwf_add_iso_soil when the soil is modelled by an isotropic absolute (or intrinsic) permeability
  • cs_gwf_add_aniso_soil when the soil is modelled by an anisotropic absolute (or intrinsic) permeability (i.e. one has to specific a 3x3 tensor)

These two functions have a similar set of parameters:

  1. The name of the volume zone associated to this soil (to add a volume, one can either use the GUI or use cs_volume_zone_define inside cs_user_zones ; see Simple volume zone example for more details)
  2. The value of the bulk mass density of the soil. This is only useful when a tracer is considered. If there is no tracer, one can set 1 for instance.
  3. The value of the absolute permeability. In case of an isotropic soil, this is a scalar and in case of an anisotropic soil, one expects a tensor.
  4. The value of the soil porosity (which is equivalent to the saturated moisture or the max. liquid saturation in single-phase flows)
  5. The type of soil model. There are two predefined soil models and one user-defined soil model

Examples of settings for a predefined soil model

Case of a saturated model

The saturated model is the simplest model.

Example 1: A saturated soils defined by an isotropic permeability on all the computational domain.

{
/* Example 1: Simplest case. A "saturated" isotropic soils */
/* saturated isotropic permeability */
const double iso_val = 1e-10;
const double theta_s = 0.85;
const cs_real_t bulk_density = 2500; /* useless if no tracer is
considered */
cs_gwf_add_iso_soil("cells", bulk_density, iso_val, theta_s,
}

Example 2: Two saturated soils defined by an anisotropic (saturated) permeability

{
/* Example 2: Two "saturated" and anisotropic soils */
/* saturated anisotropic permeability */
double aniso_val1[3][3] = {{1e-3, 0, 0},
{ 0, 1, 0},
{ 0, 0, 1e-1}};
double aniso_val2[3][3] = {{1e-5, 0, 0},
{ 0, 1, 0},
{ 0, 0, 1e-2}};
const double theta_s = 1;
const double bulk_density = 1.0; /* useless if no tracer is considered */
/* One assumes that two (volume) zones have be defined called "soil1" and
"soil2". */
cs_gwf_add_aniso_soil("soil1", bulk_density, aniso_val1, theta_s,
cs_gwf_add_aniso_soil("soil2", bulk_density, aniso_val2, theta_s,
}

Case of a Van Genuchten-Mualen model

Soils which behave according to a Van Genuchten-Mualen model are specified in two steps: a call to cs_gwf_add_iso_soil and then a call to cs_gwf_soil_set_genuchten_param to specifiy the parameters associated to this model.

Example 3: Soil relying on a Van Genuchten-Mualen and considering a isotropic permeability

{
/* Example 3: Add a new user-defined soil for all the cells */
cs_gwf_soil_t *s = cs_gwf_add_iso_soil("cells", /* volume zone name */
1800, /* bulk mass density */
3e-1, /* absolute permeability */
0.9, /* porosity */
CS_GWF_SOIL_GENUCHTEN); /* model */
0.078, /* residual moisture */
0.036, /* scaling parameter */
1.56, /* (n) shape parameter */
0.5); /* (L) tortuosity */
}

Example of settings for a user-defined soil

If the predefined models are not sufficient, it is possible to add a user-defined soil. In this case, the add of the new soil is made as follows

  1. Define a structure to get an access to the parameters defining the soil model
  2. Add a new soil (cs_gwf_add_iso_soil or cs_gwf_add_aniso_soil )
  3. Call cs_gwf_soil_set_user to specify the structure to use, the function to update quantities related to the hydraulic model and if needed a function to free the structure storing the soil parameters.

These three steps are performed inside the function cs_user_model

Here is a complete example of a user-defined soil model (called hereafter Tracy since it has been designed by F. T. Tracy in this article).

Define a structure to store the model parameters

Example of the structure used to handle the soil model parameters

/* Parameters defining the unsaturated soil model devised by Tracy */
typedef struct {
double L; /* column length */
double h_s; /* saturated head reference */
double h_r; /* residual head reference */
double theta_r; /* residual moisture */
double theta_s; /* saturated moisture */
} cs_tracy_param_t;

Add a user-defined soil

cs_gwf_soil_t *s =
1.0, /* bulk mass density (useless here) */
1.15741e-4, /* absolute permeability */
0.45, /* porosity */
/* 2.a Create and define a structure of parameters to manage the soil */
cs_tracy_param_t *tp = NULL;
BFT_MALLOC(tp, 1, cs_tracy_param_t);
tp->L = 200;
tp->h_s = 0.;
tp->h_r = -100;
tp->theta_r = 0.15;
/* 2.b Associate the parameter structure and the user-defined functions
* to manage the soil */
cs_gwf_soil_set_user(s, /* soil structure */
tp, /* soil parameter structure */
tracy_update, /* function to update the soil */
tracy_free_param); /* function to free the structure */

with the two requested functions (defined for instance as a static function in the file cs_user_parameters.c). These functions have to fullfill the prototype defined in cs_gwf_soil_update_t (for the update of the soil properties) and in cs_gwf_soil_free_param_t (for the free of the soil parameter structure).

Here is an example of how to update soil properties (function called tracy_update)

static void
tracy_update(const cs_real_t t_eval,
const cs_mesh_t *mesh,
const cs_cdo_connect_t *connect,
const cs_cdo_quantities_t *quant,
const cs_zone_t *zone,
cs_gwf_soil_t *soil)
{
CS_UNUSED(mesh);
CS_UNUSED(connect);
CS_UNUSED(quant);
CS_UNUSED(t_eval);
/* Retrieve the soil parameters */
const cs_tracy_param_t *sp = (cs_tracy_param_t *)soil->model_param;
/* Retrieve the hydraulic context */
cs_gwf_unsaturated_single_phase_t *hc = soil->hydraulic_context;
/* Additional parameters */
const cs_real_t *head_values = hc->head_in_law;
const double delta_m = soil->porosity - sp->theta_r;
const double k_s = soil->abs_permeability[0][0];
/* Retrieve field values associated to properties to update */
cs_real_t *permeability = hc->permeability_field->val;
cs_real_t *moisture = hc->moisture_field->val;
cs_real_t *capacity = hc->capacity_field->val;
for (cs_lnum_t i = 0; i < zone->n_elts; i++) {
const cs_lnum_t c_id = zone->elt_ids[i];
const cs_real_t h = head_values[c_id];
const cs_real_t k_r = (h - sp->h_r)/(sp->h_s - sp->h_r);
/* Set the permeability value */
permeability[c_id] = k_s * k_r;
/* Set the moisture content (Se = 1 in this case)*/
moisture[c_id] = sp->theta_r + k_r * delta_m;
/* Set the capacity values */
capacity[c_id] = delta_m /(sp->h_s - sp->h_r);
} /* Loop on selected cells */
}

and an example of how to free the soil parameter structure (function called tracy_free_param)

static void
tracy_free_param(void **p_soil_param)
{
cs_tracy_param_t *sp = (cs_tracy_param_t *)(*p_soil_param);
BFT_FREE(sp);
*p_soil_param = NULL;
}

Further settings (initial and boundary conditions)

In this example, we also give some highlights on how this soil structure can be used to further set the problem for instance to specify the initial and boundary conditions. This step is made in the function cs_user_finalize_setup

/* Final settings for the Richards equation */
/* 1. Retrieve the soil by its name and then its parameter structure */
cs_gwf_soil_t *soil = cs_gwf_soil_by_name("cells");
cs_tracy_param_t *tp = soil->model_param;
/* Define the boundary conditions */
"left", // boundary zone name
get_bc,
soil);
"right", // boundary zone name
&(tp->h_r));
/* Set the initial condition */
NULL,
get_ic,
tp);

where the two functions used to define either the boundary condition (called "get_bc") in the example or the initial condition (called "get_ic") in the example follow a predefined prototype (see cs_analytic_func_t)

Here are collected two examples of such functions:

static void
get_bc(cs_real_t time,
cs_lnum_t n_pts,
const cs_lnum_t *pt_ids,
const cs_real_t *xyz,
bool dense_output,
void *input,
cs_real_t *retval)
{
const cs_gwf_soil_t *soil = input;
assert(soil != NULL);
const cs_tracy_param_t *tp = soil->model_param;
assert(tp != NULL);
/* Physical parameters */
const double overL = 1./tp->L;
const double dtheta = soil->porosity - tp->theta_r;
const double td =
-5 * tp->L * tp->L * dtheta /( 6 * tp->h_r * soil->abs_permeability[0][0] );
const double alpha = 6 - 5*time/td;
for (cs_lnum_t p = 0; p < n_pts; p++) {
const cs_lnum_t id = (pt_ids == NULL) ? p : pt_ids[p];
const cs_lnum_t r_id = dense_output ? p : id;
const double xll = (xyz[3*id] - tp->L)*overL, beta = xll*xll;
retval[r_id] = tp->h_r*(1 - beta/alpha);
} /* Loop on selected points */
}
static void
get_ic(cs_real_t time,
cs_lnum_t n_pts,
const cs_lnum_t *pt_ids,
const cs_real_t *xyz,
bool dense_output,
void *input,
cs_real_t *retval)
{
CS_UNUSED(time);
cs_tracy_param_t *tp = input;
assert(input != NULL);
const double one6 = 1./6;
for (cs_lnum_t p = 0; p < n_pts; p++) {
const cs_lnum_t id = (pt_ids == NULL) ? p : pt_ids[p];
const cs_lnum_t r_id = (dense_output) ? p : id;
const double x = xyz[3*id], xll = (x - tp->L)/tp->L;
retval[r_id] = tp->h_r*(1-one6*xll*xll);
} /* Loop on selected points */
}

Tacers

The third step (which is not mandatory) is to add tracer(s) thanks to the function cs_gwf_add_tracer This tracer will be advected by the Darcy flux arising from the Richards equation.

There are currently two models :

  • a default model (the predefined one; see cs_gwf_cdo_predef_tracer)
  • a user-defined model (see cs_gwf_cdo_user_tracer)

The first parameter in cs_gwf_add_tracer is a flag which can be built with

Predefined tracers

Here is a simple example for a standard tracer which can be added in the function cs_user_model

{
/*
Add a tracer equation which is unsteady and convected by the darcean flux
This implies the creation of a new equation called eqname along with a
new field called varname.
*/
cs_gwf_tracer_model_t model = 0; /* default model without precipitation
effect */
cs_gwf_tracer_t *tr = cs_gwf_add_tracer(model, /* tracer model */
"Tracer", /* eq. name */
"C"); /* var. name */
/* For "simple" tracer definitions, definition can be made here.
*
* The parameters defining the tracer behavior can be set soil by soil
* (give the soil name as the second argument) or to all soils in one call
* (give NULL as the second argument)
*/
NULL, /* soil name or NULL for all */
0., /* water molecular diffusivity */
1., 0., /* alpha (longi. and transvesal) */
1e-4, /* distribution coef. */
0.01); /* 1st order decay coef. */
}

Remark: Get a tracer structure.

{
/* If the tracer structure has been added but not already defined, one can
* retrieve it thanks to \ref cs_gwf_tracer_by_name
*
* The name of the tracer is the same as the name of the associated
* equation given at the creation of the tracer
*/
cs_gwf_tracer_t *tr = cs_gwf_tracer_by_name("Tracer_01");
}

User-defined tracers

TODO

Automatic postprocessings

It is possible to activate or add an automatic post-processing of several quantities of interest related to groundwater flows. Here are available flags to activate through the usage of cs_gwf_set_post_options

Helper functions

Helper functions for soils

Get a soil structure from its name.

{
/* If a soil structure has been added but not already defined, one can
* retrieve it thanks to \ref cs_gwf_soil_by_name
*
* The name of the soil is the same as the name of the volume zone used at
* the creation of the soil
*/
cs_gwf_soil_t *s1 = cs_gwf_soil_by_name("soil1");
}

There is a similar which retrieve the soil structure from its id (see cs_gwf_soil_by_id).

Helper functions for tracers

Get a tracer structure from its name.

{
/* If the tracer structure has been added but not already defined, one can
* retrieve it thanks to \ref cs_gwf_tracer_by_name
*
* The name of the tracer is the same as the name of the associated
* equation given at the creation of the tracer
*/
cs_gwf_tracer_t *tr = cs_gwf_tracer_by_name("Tracer_01");
}