How to Add a New Model in Bil
Sources:
src/Models/ModelFiles/Template.c·src/Models/ModelFiles/TemplateFEM.csrc/Models/ModelFiles/TemplateFVM.c·src/Models/ListOfModels.hsrc/Models/Methods/MaterialPointMethod.h·src/Models/Methods/CustomValues.hsrc/Models/DataBases/
Table of contents
- Where and how
- Internal variables
- The 11 basic functions of a model
- The
MaterialPointMethod.hinterface (C++) - Physico-chemical databases
- Learning from existing models
1. Where and how
Adding a new model to Bil requires only two steps and does not modify any framework file.
Step 1 — Create the model file
Create src/Models/ModelFiles/MyModel.c (or .cpp) using one of the available templates:
| Template | Discretization | Use case |
|---|---|---|
Template.c |
Any | Minimal skeleton |
TemplateFEM.c |
FEM | Finite element models |
TemplateFVM.c |
FVM | Finite volume models |
Upper camel case convention is used for model file names (MyModel.c). The same convention applies to attributes and methods.
Step 2 — Register the model
Add the model to src/Models/ListOfModels.h:
/* Before */
#define ListOfModels_Nb 39
#define ListOfModels_Names ..., "Sulfaconew"
#define ListOfModels_Methods(m) ..., Sulfaconew##m
/* After */
#define ListOfModels_Nb 40
#define ListOfModels_Names ..., "Sulfaconew", "MyModel"
#define ListOfModels_Methods(m) ..., Sulfaconew##m, MyModel##m
Step 3 — Recompile
The input file can now use Model = MyModel. No other framework file is modified.
A short description of the available models can be displayed at any time with:
2. Internal variables
At each Gauss point (FEM) or node (FVM), three types of internal variables are stored and updated at different rates:
| Type | Array | When recomputed | Typical content |
|---|---|---|---|
Implicit (NVI) |
vi[k] |
Every Newton-Raphson iteration | Fluxes, stresses, storage terms |
Explicit (NVE) |
ve[k] |
Once per time step (from previous step) | Permeability, saturation, conductivity |
Constant (NV0) |
v0[k] |
Never after initialization | Initial state, material constants per element |
These three counters must be defined in each model file:
#define NVI 9 /* number of implicit variables per Gauss point */
#define NVE 2 /* number of explicit variables per Gauss point */
#define NV0 4 /* number of constant variables per Gauss point */
Choosing the correct type for each variable is important for correctness: - Use implicit for quantities that depend on the current unknown values. - Use explicit for quantities evaluated at the previous time step that remain fixed during Newton iterations (avoids recomputing expensive constitutive laws at each iteration). - Use constant for quantities that are spatially variable but time-independent.
3. The 11 basic functions of a model
Every model file must implement 11 functions that correspond to the methods of the Model_t interface. The PredefinedModelMethods.h macro handles the SetModelProp boilerplate automatically.
| Function | Signature | Description |
|---|---|---|
SetModelProp |
int (Model_t*) |
Set model properties (populates Model_t) |
ReadMatProp |
int (Material_t*, DataFile_t*) |
Read material properties from input file |
PrintModelProp |
int (Model_t*, FILE*) |
Print model properties (for -model option) |
DefineElementProp |
int (Element_t*, IntFcts_t*, ShapeFcts_t*) |
Define per-element arrays and integration order |
ComputeInitialState |
int (Element_t*, double) |
Compute initial state at \(t = 0\) |
ComputeExplicitTerms |
int (Element_t*, double) |
Compute explicit terms from previous step |
ComputeImplicitTerms |
int (Element_t*, double, double) |
Compute implicit terms at current step |
ComputeMatrix |
int (Element_t*, double, double, double*) |
Assemble tangent stiffness matrix |
ComputeResidu |
int (Element_t*, double, double, double*) |
Compute Newton-Raphson residual |
ComputeLoads |
int (Element_t*, double, double, Load_t*, double*) |
Compute load vector |
ComputeOutputs |
int (Element_t*, double, double*, Result_t*) |
Compute post-processing output fields |
Minimal skeleton
/* MyModel.c */
#include "CommonModel.h"
#include "FEM.h" /* or FVM.h */
#define TITLE "My model title"
#define AUTHORS "Author name"
#include "PredefinedModelMethods.h"
#define NEQ 2 /* number of equations / nodal unknowns */
#define NVI 5 /* implicit variables per Gauss point */
#define NVE 2 /* explicit variables per Gauss point */
#define NV0 0 /* constant variables per Gauss point */
/* Material property index */
int pm(const char* s)
{
if(strcmp(s,"young") == 0) return 0 ;
if(strcmp(s,"poisson") == 0) return 1 ;
return(-1) ;
}
/* Populate Model_t */
int SetModelProp(Model_t* model)
{
Model_GetNbOfEquations(model) = NEQ ;
Model_CopyNameOfEquation(model, 0, "eq1") ;
Model_CopyNameOfEquation(model, 1, "eq2") ;
Model_CopyNameOfUnknown(model, 0, "u1") ;
Model_CopyNameOfUnknown(model, 1, "u2") ;
return(0) ;
}
int ReadMatProp(Material_t* mat, DataFile_t* datafile)
{
Material_ScanProperties(mat, datafile, pm) ;
return(2) ;
}
int PrintModelProp(Model_t* model, FILE* f)
{
/* Print example input for bil -model mymodel */
fprintf(f, "young = 1.e9\n") ;
fprintf(f, "poisson = 0.3\n") ;
return(0) ;
}
int DefineElementProp(Element_t* el, IntFcts_t* intfcts, ShapeFcts_t* shpfcts)
{
/* Allocate NVI, NVE, NV0 arrays */
Element_GetNbOfImplicitTerms(el) = NVI ;
Element_GetNbOfExplicitTerms(el) = NVE ;
Element_GetNbOfConstantTerms(el) = NV0 ;
return(0) ;
}
int ComputeInitialState(Element_t* el, double t) { return(0) ; }
int ComputeExplicitTerms(Element_t* el, double t) { return(0) ; }
int ComputeImplicitTerms(Element_t* el, double t, double dt) { return(0) ; }
int ComputeMatrix(Element_t* el, double t, double dt, double* k) { return(0) ; }
int ComputeResidu(Element_t* el, double t, double dt, double* r) { return(0) ; }
int ComputeLoads(Element_t* el, double t, double dt, Load_t* cg, double* r) { return(0) ; }
int ComputeOutputs(Element_t* el, double t, double* s, Result_t* r) { return(0) ; }
4. The MaterialPointMethod.h interface (C++)
For C++ models, Bil provides a higher-level interface that significantly simplifies implementation. Instead of manually writing all 11 functions, one derives a class MPM_t from MaterialPointMethod_t.
4.1 Define internal variables with CustomValues.h
#include "CustomValues.h"
template<typename T> struct ImplicitValues_t; /* vary at each iteration */
template<typename T> struct ExplicitValues_t; /* vary at each time step */
template<typename T> struct ConstantValues_t; /* constant over time */
template<typename T>
using V = CustomValues_t<T, ImplicitValues_t, ExplicitValues_t, ConstantValues_t, ...>;
The three variable types correspond to the per-Gauss-point arrays described in section 2.
4.2 Derive the MPM_t class
#include "MaterialPointMethod.h"
struct MPM_t : public MaterialPointMethod_t<V> {
/* Initialize val with nodal unknowns and their gradients */
V<double>* SetInputs(Element_t* el, double const& t, int const& p,
double const* const* u, V<double>& val);
/* Initialize all internal variables (initial state) */
V<double>* Initialize(Element_t* el, double const& t, V<double>& val);
/* Integrate the constitutive law from t-dt to t */
V<double>* Integrate(Element_t* el, double const& t, double const& dt,
V<double> const& val_n, V<double>& val);
/* Fill the k-th column of the tangent matrix */
int SetTangentMatrix(Element_t* el, double const& t, double const& dt,
int const& p, V<double> const& val, V<double> const& dval,
int const& k, double* c);
/* Fill the transfer matrix (FVM) */
int SetTransferMatrix(Element_t* el, double const& dt, int const& p,
V<double> const& val, double* c);
/* Compute fluxes between nodes i and j (FVM) */
V<double>* SetFluxes(Element_t* el, double const& t, int const& i, int const& j,
V<double> const& grdval, V<double>* val);
/* Give the index in V of each primary unknown */
void SetIndexOfPrimaryVariables(Element_t* el, int* ind);
/* Give the increment Δu used for numerical derivatives */
void SetIncrementOfPrimaryVariables(Element_t* el, double* dui);
};
4.3 Automatic differentiation with autodiff.h
The tangent matrix can be obtained by automatic differentiation rather than finite differences:
When USE_AUTODIFF is defined, the Differentiate operator analytically computes the derivative of Integrate with respect to the primary unknowns, without manual Jacobian implementation.
Otherwise, numerical differentiation is used automatically via SetIncrementOfPrimaryVariables:
void SetIncrementOfPrimaryVariables(Element_t* el, double* dui) {
ObVal_t* obval = Element_GetObjectiveValue(el);
dui[0] = 1.e-2 * ObVal_GetValue(obval + 0); /* increment = 1% of objective variation */
}
5. Physico-chemical databases
Bil provides a library of physico-chemical data accessible from any model via header inclusion. These data are stored in src/Models/DataBases/.
/* Water viscosity as a function of temperature (in K) */
#include "WaterViscosity.h"
double mu_w = WaterViscosity(293); /* Pa·s at 293 K */
/* Diffusion coefficient of an ion in water */
#include "DiffusionCoefficientOfMoleculeInWater.h"
double d_ca = DiffusionCoefficientOfMoleculeInWater(Ca, 293); /* m²/s */
/* Diffusion coefficient in air */
#include "DiffusionCoefficientOfMoleculeInAir.h"
double d_co2_air = DiffusionCoefficientOfMoleculeInAir(CO2, 293);
Available databases:
| Header | Content |
|---|---|
WaterViscosity.h |
Water viscosity as a function of T |
AirViscosity.h |
Air viscosity |
DiffusionCoefficientOfMoleculeInWater.h |
Ionic diffusion coefficients in aqueous solution |
DiffusionCoefficientOfMoleculeInAir.h |
Diffusion coefficients in gas phase |
HardenedCementChemistry.h |
Chemistry of hydrated cement-based binders |
CementSolutionChemistry.h |
Chemistry of cement pore solution |
ElectricChargeOfIonInWater.h |
Electric charges of ions |
EquilibriumConstantOfHomogeneousReactionInWater.h |
Equilibrium constants of reactions in solution |
IonizationConstantOfWater.h |
Ionization constant of water |
HenrysLawConstantForSolubilityOfGasInWater.h |
Henry's law constant (gas solubility) |
AtmosphericPressure.h |
Reference atmospheric pressure |
6. Learning from existing models
The best way to implement a new model is to study an existing one with similar physics. The following models are good starting points:
Flow problems
| Model | File | Description |
|---|---|---|
M1 |
M1.c |
1D unsaturated flow (Richards equation, FVM) |
Fick |
Fick.c |
Diffusion by Fick's second law |
Richards |
Richards.c |
2D/3D unsaturated flow (FEM) |
These are relatively compact models that illustrate:
- scalar unknowns (pressure, concentration)
- explicit/implicit variable split for permeability/saturation
- simple retention curves via Curves input
Coupled mechanical problems
| Model | File | Description |
|---|---|---|
Elast0 |
Elast0.c |
Linear elasticity (FEM) |
Plast |
Plast.c |
Elastoplasticity with Drucker-Prager criterion |
M7 |
M7.c |
Unsaturated poromechanics (Biot, FEM) |
BBM |
BBM.c |
Barcelona Basic Model (unsaturated plasticity) |
These models illustrate:
- vector unknowns (displacement)
- stress integration (return mapping algorithm)
- coupled hydro-mechanical behavior
- the full use of NVI, NVE, NV0
FEM² multi-scale
| Model | File | Description |
|---|---|---|
MechaMic |
MechaMic.c |
Multi-scale mechanics via FEM² |
This model shows how to couple a macro-scale mesh with a micro-scale representative volume element (RVE). Periodic boundary conditions on the RVE are handled via the PERIODICITIES block in the input file: