Skip to content

User defined material

The input files can be downloaded here.

In this example the use of user defined material (mechanical) model is demonstrated. For this purpose we consider the same rigid foundation on an elastic soil layer already studied in the tutorial Rigid strip footing. The model specifications are shown in Figure 1. The foundation is loaded by a distributed load along the top surface of the foundation and the settlement is studied assuming ideally drained conditions and a linearly increasing external load (\(\Delta \sigma_v(t=0 ~\text{s}) = 0\) kPa and \(\Delta \sigma_v(t=1 ~\text{s})=500\) kPa).

figure 1 Figure 1. Model of the rigid foundation

The resulting vertical displacements of the top left node of the shallow foundation are reported in Figure 2.

figure 1
Figure 2. Settlement time history of the top left node of the shallow foundation

For the sake of this tutorial we will now consider a slightly more complicated stress-strain relation. Instead of a linear elasticity with a constant Young's modulus, we now want to consider a pressure-dependent stiffness of the following form

\[ E(p) = E_0 f_p ~~~ \text{with:} ~~~ f_p = \left(\dfrac{-\sigma_{ii}/3}{p_{ref}}\right)^n \]

Compared to the linear elastic constitutive model, this constitutive model requires 3 more material parameters: the reference stiffness \(E_0\) at \(p=p_{ref}\), the exponent \(n\) and the reference pressure \(p_{ref}\). Due to its simplicity this model does not require the definition of any state variables. However, for output purposes we would like to be able to track the development of \(f_p\) and \(E\), thus defining two state variables for the user material. In order to replace the linear elastic model for the soil by the user defined material model we use the following code snipped in the input file of numgeo:

*Material, name = soil, phases = 1
*Mechanical = user
*User properties, props=4, statev=2
5000, 0.3, 0.2, 1
*Density
2.0

The material definition is very similar to using a material model already implemented in numgeo. The *Mechanical=user command tells numgeo to use a material model that is not available in the internal library. The material parameters are then set using the *User properties command. In this line the number of material parameters (props=) and state variables (statev=) must also be specified.

In the present case we have a total of 4 properties (\(E_0=5000\) kPa, \(\nu=0.3\), \(n=0.2\) and \(p_{ref}=1\) kPa) and the number of state variables is 2 (\(f_p\) and \(E\)).

Since we want to see the evolution of the state variables \(f_p\) and \(E\) in the field output (Paraview) we need to modify the output request as follows:

*Output, field, vtk, ASCII
...
*Element output, elset = Soil.all
S, Contact, sdv1, sdv2

Output variables starting with sdv followed by a number are state variables of constitutive models that have not (yet) been saved by name. For example, for hypoplasticity with intergranular strain, the void ratio can be initialised with void_ratio or the intergranular strain can be initialised with int_strain11, int_strain12, ..., int_strain33 or requested directly as output.

For the "freely assignable" state variables, sdv1 = statev(1), sdv2 = statev(2), ... Here statev(:) is the array containing the state variables of the model. If a user-defined constitutive model is implemented, the correct implementation of the evolution equation of the state variables is the responsibility of the user.

Next, we copy the files user_material.f90 and user_material.bat to the directory where we want to run the simulation. Once the files are relocated, we implement above equation into the Fortran file:

subroutine user_material(material_name,nchar,ielem,igp,istep,iinc,ntens,nprops,nstatev, &
    strain,dstrain,coords,time,dtime,props,statev,stress,dds_dde) 
    implicit none
    !DEC$ ATTRIBUTES DLLEXPORT, STDCALL, REFERENCE :: user_material
    character                       , intent(in) :: material_name(*)
    integer                         , intent(in) :: nchar
    integer                         , intent(in) :: ielem
    integer                         , intent(in) :: igp
    integer                         , intent(in) :: istep
    integer                         , intent(in) :: iinc
    integer                         , intent(in) :: ntens
    integer                         , intent(in) :: nprops
    integer                         , intent(in) :: nstatev
    real(8), dimension(ntens)       , intent(in) :: strain
    real(8), dimension(ntens)       , intent(in) :: dstrain
    real(8), dimension (3)          , intent(in) :: coords
    real(8)                         , intent(in) :: time
    real(8)                         , intent(in) :: dtime
    real(8), dimension(nprops)      , intent(in) :: props
    real(8), dimension(nstatev)     , intent(inout) :: statev 
    real(8), dimension(ntens)       , intent(inout) :: stress
    real(8), dimension(ntens ,ntens), intent(inout) :: dds_dde

    character(len=nchar) :: material_str

    real(c_double) :: E, nu, k1, k2, k3
    real(8) :: E0, nu, n, pref
    real(8) :: E, fp, p
    real(8) :: k1, k2, k3

    ! required to make the material name usable in select case of if-clauses
    material_str = transfer(material_name(1:nchar), material_str)

    dds_dde = 0.0d0
    E0 = props(1) ; nu = props(2) ; n = props(3) ; pref = props(4)

    p = -sum(stress(1:3))/3.0d0

    fp = (max(pref,p)/pref)**n
    E = E0 * fp

    k1 = nu * E / (1.0d0+nu) / (1.0d0-2.0d0*nu)
    k2 = E / 2.0d0 / (1.0d0+nu)
    k3 = k1 + 2.0d0 * k2

    dds_dde(1:3,1:3) = k1
    dds_dde(1,1) = k3 ; dds_dde(2,2) = k3 ; dds_dde(3,3) = k3
    dds_dde(4,4) = k2

    if (ntens == 6) then
    dds_dde(5,5) = k2
    dds_dde(6,6) = k2
    end if

    stress = stress + matmul(dds_dde,dstrain)

    statev(1) = fp
    statev(2) = E

end subroutine user_material
subroutine user_material(material_name,nchar,ielem,igp,istep,iinc,ntens,nprops, &
    nstatev,strain,dstrain,coords,time,dtime,props,statev,stress,dds_dde) &
    bind(c,name='user_material')
    use, intrinsic :: iso_c_binding
    implicit none
    character(c_char)                      , intent(in) :: material_name (*)
    integer(c_int)                         , intent(in) :: nchar
    integer(c_int)                         , intent(in) :: ielem
    integer(c_int)                         , intent(in) :: igp
    integer(c_int)                         , intent(in) :: istep
    integer(c_int)                         , intent(in) :: iinc
    integer(c_int)                         , intent(in) :: ntens
    integer(c_int)                         , intent(in) :: nprops
    integer(c_int)                         , intent(in) :: nstatev
    real(c_double), dimension(ntens)       , intent(in) :: strain
    real(c_double), dimension(ntens)       , intent(in) :: dstrain
    real(c_double), dimension (3)          , intent(in) :: coords
    real(c_double)                         , intent(in) :: time
    real(c_double)                         , intent(in) :: dtime
    real(c_double), dimension(nprops)      , intent(in) :: props
    real(c_double), dimension(nstatev)     , intent(inout) :: statev 
    real(c_double), dimension(ntens)       , intent(inout) :: stress
    real(c_double), dimension(ntens ,ntens), intent(inout) :: dds_dde

    character(len=nchar) :: material_str

    real(c_double) :: E, nu, k1, k2, k3
    real(c_double) :: E0, nu, n, pref
    real(c_double) :: E, fp, p
    real(c_double) :: k1, k2, k3

    ! required to make the material name usable in select case of if-clauses
    material_str = transfer(material_name(1:nchar), material_str)

    dds_dde = 0.0d0
    E0 = props(1) ; nu = props(2) ; n = props(3) ; pref = props(4)

    p = -sum(stress(1:3))/3.0d0

    fp = (max(pref,p)/pref)**n
    E = E0 * fp

    k1 = nu * E / (1.0d0+nu) / (1.0d0-2.0d0*nu)
    k2 = E / 2.0d0 / (1.0d0+nu)
    k3 = k1 + 2.0d0 * k2

    dds_dde(1:3,1:3) = k1
    dds_dde(1,1) = k3 ; dds_dde(2,2) = k3 ; dds_dde(3,3) = k3
    dds_dde(4,4) = k2

    if (ntens == 6) then
    dds_dde(5,5) = k2
    dds_dde(6,6) = k2
    end if

    stress = stress + matmul(dds_dde,dstrain)

    statev(1) = fp
    statev(2) = E

end subroutine user_material

The shell script guiding the compilation of the user defined amplitude is as follows:

ifort -O3 /dll user_material.f90
#!/bin/bash
ifort -c -shared -fPIC user_material.f90
ifort -shared -fPIC user_material.o -o user_material.so

The simulation can now be started from the terminal using the standard procedure, e.g.

numgeo inp=input out=simulation ncpus=4
./numgeo inp=input out=simulation ncpus=4

numgeo will automatically compile the user subroutine user_material.f90 and link it to the code. The resulting settlement-time history is given in Figure 3 and compared to the reference simulation using a linear elastic model.

figure 1
Figure 3. Settlement time history of the top left node of the shallow foundation for a linear increasing load for two different constitutive models for the soil

As expected we observe a reduced settlement (orange) at the end of the simulation compared to the reference solution (blue) due to the increase in stiffness with increasing mean effective stress.

Plotting the results

For plotting the results as shown in above figures, the following python script can be used:

#%%
import numpy as np
import matplotlib.pyplot as plt

plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['text.usetex'] = True
plt.rcParams['font.size'] = 12
plt.rcParams['axes.spines.right'] = False
plt.rcParams['axes.spines.top'] = False

def cm2inch(*tupl):
    inch = 2.54
    if isinstance(tupl[0], tuple):
        return tuple(i/inch for i in tupl[0])
    else:
        return tuple(i/inch for i in tupl)

node_set_name = "FOUNDATION.TOP_LEFT_NODE_node_7703.dat"

ref = np.genfromtxt("./reference/simulation-print-out/"+node_set_name, skip_header=1)
data = np.genfromtxt("./user material/simulation-print-out/"+node_set_name, skip_header=1)

plt.figure(figsize=cm2inch(12,8))
plt.plot(ref[:,0], ref[:,2], label='$E=10$ MPa')
plt.plot(data[:,0], data[:,2], label='$E(p) = E_0 \\left( \\frac{-\sigma_{ii}}{3p_{ref}} \\right)^n$ \n with: $E_0=5$ MPa, $n=0.2$, $p_{ref}=1$ kPa')
plt.xlabel('Time in s')
plt.ylabel('Settlement in m')
plt.legend(loc='lower left', frameon=False)
plt.tight_layout()
plt.savefig('./time_settlement_user_material.pdf', dpi=400)
plt.savefig('./time_settlement_user_material.png', dpi=400)