User Material Models
Introduction
IMPETUS Solver comes with support for user defined material models. Our solution is to let the user compile a DLL (dynamic-link library) file which then is linked to the solver during the start of a simulation. The user material is not compiled into the software itself and it allows for several advantages. Since object code of the software isn't needed, it will works with any version of the solver (unless we've done major changes to the interface). This means one can use the latest version of the Solver. We allows up to five different user defined material models.
Structure
The user material zip file (provided by our support) contains a Visual Studio 2022 solution with two project folders inside. The C++ project "mat_user_functions" contains a wrapper to call built-in subroutines/functions from the solver. Do not edit the files inside this project. The other project "mat_user" which is an Intel Fortran project, contains the code for user defined material models. There are several files inside this project: mat_user.F, a few mat_XXXX.F files (examples) and custom_functions.F. As the name suggests, custom_functions.F can be used to define custom subroutines/functions. This is completely optional but we recommend using it. The file mat_user.F is the main file and this is the one that should be modified. Start by opening mat_user.sln with Visual Studio. Before compilation, remember to change compilation mode from Debug to Release.
Now, expand "mat_user" project from the solution tree and open mat_user.F under "Source Files". We've provided with some example models which are implemented in different files, but called by mat_user_1(), mat_user_2(), mat_user_3() and mat_user_4(). Feel free to modify these and customise them to your needs. The example in mat_user_1() is similar to our built-in *MAT_METAL material model.
After the compilation, mat_user.dll will be copied to the output folder located in the root directory of the Visual Studio project. When executing a simulation, check the "Use defined material model" checkbox and add the path to the file.
Defined subroutines in mat_user.F
-
mat_user_X
(
)
- Main subroutine for the user material X (X: 1-5)
-
init_mat_user_X
(
)
- Initialisation subroutine for the user material X (X: 1-5)
-
mat_user_set_state_variables
(
)
- Set the number of state variables, state variable output, texture location (for fibre directions), damage type location and temperature location.
Arguments for mat_user_X():
Arguments for init_mat_user_X():
Getting started
Symmetric tensors
All symmetric tensors in our solver uses an alternative notation of the Voigt notation. The traditional voigt notation for a 3X3 matrix is defined as: 11, 22, 33, 23, 13, 12. Our definition is: 11, 22, 33, 12, 23, 13.
Material parameters
Material parameters defined in the *MAT_USER_X command can be accessed by the cmat array. Position 1-6 are tied to the first row after the material ID definition (1st value). For example cmat(1) tied to the 2nd value, cmat(2) the 3rd, cmat(3) the 4th, etc. For a normal use case, we use the following: cmat(1) = density, cmat(2) = young's modulus, cmat(3) = poisson's ratio, etc.
Second row starts at index cmat(7), third row at cmat(15), forth row at cmat(23), etc.
There are a few slots in cmat which are reserved. cmat(43) and cmat(44) are reserved for pre-calculated shear and bulk modulus.
! *MAT_USER_1
! 1, 7800.0, 2.0e9, 0.3
dens = cmat(1) ! 7800.0
young = cmat(2) ! 2.0e9
pr = cmat(3) ! 0.3
! pre-calculated
shear = cmat(43)
bulk = cmat(44)
Damage properties
If a damage command ID is defined in the input, then the damage properties will be stored in cmat(60) → cmat(70) and cmat(80) → cmat(93).
! these variables are automatically set depending damage and erosion type
damage_type = nint(cmat(80))
erode_flg = nint(cmat(81))
[...]
! damage evolution (see definition in the section "Built-in subroutines/functions")
if (damage_type.ne.0.and.(dmg.lt.1.0d0.or.erode_flg.ge.2)) then
call damage(damage_type, erode_flg, cmat(60), cmat(82), dmg, dmg2,
stress, sig_eff, p, strain, epsp, depsp, rate, T, evol, 0, 0, 0)
endif
Thermal properties
If a thermal command ID is defined in the input, then the thermal properties will be stored in cmat(71) → cmat(75).
! thermal properties (see *PROP_THERMAL in manual for explanation)
hexp = cmat(71)
Cp = cmat(72)
hcond = cmat(73)
k = cmat(74)
Tref = cmat(75)
[...]
! update temperature T if we have plastic flow
if (depsp.gt.0.0d0.and.Cp.ne.0.0d0) then
T = T + k*sig_eff*depsp/(Cp*dens)
endif
State variables
The state variable array is a container where one can store data for each integration point. These variables can change throughout the simulation. Examples of typical variables are "Effective Plastic Strain", "Damage" and temperature. To use state variables, use the hist array to load and store data. It's a good idea to set initial values for these variables in init_mat_user_X() subroutine.
! load state variables: effective plastic strain, damage, yield stress and temperature
epsp = hist(1)
dmg = hist(2)
sigy0 = hist(3)
T = hist(4)
! some code involving epsp, dmg, sigy0 and/or T
[...]
! save state variables
hist(1) = epsp
hist(2) = dmg
hist(3) = sigy0
hist(4) = T
Before state variables can be used, one needs to define the size of the array. This is done by editing mat_user_set_state_variables() in mat_user.F. In our examples, these are set in the subsequential subroutine calls since they are implemented in different files). For example, see mat_template_state_variables() in mat_template.F (which is called by mat_user_set_state_variables() in mat_user.F).
For instance, if we are going to specify the number of history variables for mat_user_1(), we need to assign a value to num_variable(1). In the example (see code below), we have set the size to 3 for position 1 in the num_variable() array. Position 1 indicates user material ID 1, value 3 is the number of state variables for this user material.
num_variable(1) = 4
State variables as contour plot
With the size set, we are ready to go but we will not be able to plot these values in the GUI. None of these values are written to our output files by default. To add a state variable to the output, we need to tell the solver to output it. This can be done by calling mat_user_set_name(). variable_id tells which index in the state variable array to output. variable_name specifies the name the state variable should have.
user_material_id = 1
variable_id = 1
variable_name(1:40) = "Effective plastic strain"
call mat_user_set_name(user_material_id, variable_id, variable_name)
variable_id = 2
variable_name(1:40) = "Damage"
call mat_user_set_name(user_material_id, variable_id, variable_name)
They can be found under the "General" category in Contour plot. The prefix "User: " will automatically be added to the name when using Contour plot in the GUI.
Curves & functions
Curves & functions can be used in the user material models just like in *MAT_METAL. To use curves, one needs to tell the solver where in the material parameter list the curve is located. This can be done by editing mat_user_set_state_variables() and modify/add the array user_curve_location. This is a 2D array with two arguments defining user curve ID and material ID.
user_curve_location(1, 1) = 7
user_curve_location(2, 1) = 8
This will tell the solver that there are curves in positions 7 and 8 in the material parameter array (cmat) for mat_user_1. Position 7 and 8 represents the two first columns on the second line in the *MAT_USER_X command. More information about how to call curves and functions can be found in the section "Built-in subroutines/functions" further down in this document.
Texture location
When implementing material models using fibre direction (for example fibre composites) with *INITIAL_MATERIAL_DIRECTION_WRAP, a texture location must be set. The location tells IMPETUS Solver where in the state variables array (hist) to set the initial fibre direction. It requires 9 slots (3 vectors) in the state variables array (from the specified index) so make sure to allocate enough space when setting num_variable().
Example below shows allocation of 14 state variables and that we set the last 9 of them for initial fibre direction:
num_variable(1) = 14
texture_location(1) = 6
Temperature location
Temperature definition from for example *INITIAL_TEMPERATURE can be obtained through the temperature_location array. The value set in this array will tells IMPETUS Solver where in the state variables array (hist) to set the initial temperature.
num_variable(1) = 14
temperature_location(1) = 4
Damage definition
Material failure can be defined as element erosion or node splitting. The variable damage_location defines where in the state variable array that damage is located. This variable is only needed for node splitting but we recommend to set it anyway even when not using node splitting. erode_flg defines if the material model should use element erosion (=1), node splitting (=2) or none (=0). If erode_flg_pos is set, the value of the material parameter (cmat) in that specified location will determine to use element erosion or node splitting.
damage_location = 2
erode_flg = 2
erode_flg_pos = 0
call mat_user_set_damage(user_material_id, damage_location, erode_flg, erode_flg_pos)
This should enough for node splitting since the Solver will handle the rest. For element erosion, failure handling must be defined manually. To do this, add this after the damage calculations in mat_user_X():
! element erosion (node splitting will not execute this if-statement)
if (dmg.ge.1.0d0.and.erode_flg.lt.2) then
dmg = 1.0d0
! reset deviatoric stresses
stress(:) = 0.0d0
! erosion -> set failure flag
if (erode_flg.eq.1) ifail = ifail + 1
endif
We set the dmg variable to 1.0, reset the stress array and we increment ifail with 1. If enough integration points per element fails, then the solver will automatically erode it. This code can co-exist with node splitting capability since node splitting will set the erode_flg variable to 2. If that happens then this code will never be executed.
Built-in subroutines/functions
We've made some of the core functionality of the solver available. Return values from the subroutines are marked with underscore in the parameter list. Functions return its value as return values.
Damage interface for *PROP_DAMAGE_X
-
call
damage
(
type , erode_flg , dmg_fcn , dmg_parm , dmg , dmg2 , sig_dev , sig_eff , p , eps , epsp , depsp , rate , T , evol , Q , ctip , ip_vol)
- Interface to invoke one of the built-in damage failure criterion models. The first four parameters should be passed on from cmat.
damage_type = nint(cmat(80))
erode_flg = nint(cmat(81))
[...]
! damage evolution
if (damage_type.ne.0.and.(dmg.lt.1.0d0.or.erode_flg.ge.2)) then
call damage(damage_type, erode_flg, cmat(60), cmat(82), dmg, dmg2, stress,
sig_eff, p, strain, epsp, depsp, rate, T, evol, 0, 0, 0)
endif
Load Curve / Function
-
call
load_curve
(
idlc , x , f , p , epsp , sigy0 , T , dmg)
-
Returns the ordinate value of x in the defined idlc to the variable f.
The last 5 parameters (p, epsp, sigy0, T & dmg) are for functions. They must be set to 0 if not being used.
This call can be used both to call curves and functions.
See the command *CURVE and *FUNCTION for more information.
! Load a curve
call load_curve(idlc, epsp+deps, sigy1, 0, 0, 0, 0, 0)
! Load a curve or a function with pressure and effective strain rate variables set
call load_curve(idlc, epsp+deps, sigy1, p, epsp, 0, 0, 0)
! Load a curve or a function width all parameters set
call load_curve(idlc, epsp+deps, sigy1, p, epsp, sigy0, T, dmg)
Normalise a vector
-
call
normalize
(
v , x)
- This subroutine will return normalised v and also its length x.
Cross product
-
call
cross
(
a1 , a2 , a3)
- This subroutine will return the cross product between a1 & a2 in a3.
Dot product
-
value =
dot
(
v1 , v2)
- Returns the dot product between v1 and v2.
Polar decomposition
-
call
polar_decomposition
(
F , R , U , Uinv)
- This subroutine computes the polar decomposition and returns new values for R, U & Uinv.
Eigenvalues of symmetric 3X3-matrix
-
call
eigval_3x3_symm
(
avec , eigval)
- This subroutine returns the eigen values calculated from avec.
Eigenvectors of symmetric 3X3-matrix
-
call
eigvec_3x3_symm
(
avec , eigval , eigvec)
- This subroutine returns the eigen vectors calculated from avec.
! First, find eigenvalues and then use them to find the eigenvectors
call eigval_3x3_symm(stress, eigval)
call eigvec_3x3_symm(stress, eigval, eigvec)
Invert 3X3-matrix
-
call
inv_3x3
(
A , B , detA)
- This subroutine reads matrix A and returns the inverted matrix to B. The determinate is also returned to detA.
Volume of an element
-
value =
element_volume
(
element_id , element_type)
- Returns volume of an element in double precision.
Volume of an integration point
-
value =
ip_volume
(
element_id , element_type , ip_id)
- Returns volume of an integration point in double precision.
Initial volume of an integration point
-
value =
ip_initial_volume
(
element_id , element_type , ip_id)
- Returns initial volume of an integration point in double precision.
Get element nodes
-
call
element_nodes
(
element_id , element_type , node_list)
- This subroutine returns an array of node IDs of a specific element.
Get element neighbours
-
call
element_neighbor_list
(
element_id , element_type , element_list , num_neigh)
- This subroutine returns an 2D array of neighbouring elements and the number of neighbours. First value of the array contains the neighbouring element ID. The second value contains the element type of the neighbouring element.
Get integration point coordinate
-
call
ip_coord
(
element_id , element_type , ip_id , ip_coord)
- This subroutine returns the coordinate of an integration point.
Element conversion (internal to user ID)
-
value =
element_i2u
(
element_id , element_type)
- Returns user (external) ID of an element.