A common lisp implementation of MPM, written in an extensible CLOS (object oriented) style for easy exploratory programming. Currently only implemented for 2d problems with limited 3d support. The system is written with lparallel and per-node locks to allow for simple multi-threading, there is work on MPI integration.
Simple mesh conforming nodal boundary conditions are implemented, and nonconforming pressure and buoyancy conditions are implement using a virtual stress method. A simple frictional releasing boundary condition is enforced via the use of a penalty method. Cell crossing errors are alleviated with the use of GIMP elements, but B-splines can be used as well.
Large deformations are handled using a logarithmic strain update and the Kirchhoff, allowing small strain constitutive models to be used directly in a large deformation case. Stress-rate equations may be implemented using the logarithmic corotational objective stress rate.
2D/3D Explicit Dynamic MPM
Large deformation mechanics through logarithmic strain and the co-rotational logarithmic rate
MPM, GIMP, B-spline shape-functions
Mesh conforming extensible BCs
Nonconforming pressure and buoyancy BCs via virtual stress method
Nonconforming releasing surface and friction BCs via a penalty method
Nonlocal continuum damage via integration scheme with efficient spacial implementation
Code available on git hub:
The material point method is a hybrid Euler-Lagrange method, it uses particles to track the flow of mass and the state of the simulation and a grid to calculate the evolution of the system.
Roughly speaking the particles are used to solve dynamics and the grid to solve kinematics.
Explicit MPM needs fine timesteps, and has higher memory requirements than FEM - but allows for multiple bodies and destructive simulations with ease.
The basic algorithm for MPM is stated as:
Transfer mass & velocities from particles to grid
Apply constraints on the grid & solve new velocities
Transfer velocities from grid to particle
Update particle model with velocity field & advect particles
An explicit dynamic code needs to make 2 major decisions in its update schemes. Whether to use update stress first (USF) or update stress last (USL), and whether to use particle in cell (PIC), or fluid implicit particles (FLIP) for velocity updates.
In general one should use USF and FLIP, making the update cycle:
Transfer mass & velocities from particles to grid
Apply velocity constraints on the grid
Calculate strain rate, and update stress
Transfer forces from particle to grid
Apply force constraints on the grid
Update velocity and acceleration on the grid
Apply velocity constraints on the grid
Update particle velocity & advect particles
There's some documentation on the github, but this is a mini tutorial for getting an MPM sim up and running.
Lets run a quasi-static elastic 2D problem where a square of material loaded under gravity squishes a little. We start by loading the cl-mpm and setup package. This can either be done in an interactive REPL, or putting this in a file and loading it with sbcl --load.
(ql:quickload :cl-mpm)
(ql:quickload :cl-mpm/setup)
;;Create a simulation of 10x10 nodes 1x1 meter wide
(defparameter *sim*
(cl-mpm/setup::make-block
0.1d0 ;; Resolution h=h_x=h_y
(list 10 10) ;; Elements
))
;;Create a block of elastic material points 0.5x0.5 meters across with 10x10 material points total
(setf (cl-mpm:sim-mps *sim*)
(cl-mpm/setup::make-block-mps
(list 0d0 0d0);; Offset
(list 0.5d0 0.5d0) ;; Size
(list 20 20);; Mp count
1000d0 ;Density 1kg/m^3
'cl-mpm/particle::particle-elastic
:E 1d5 ;; Young's modulus 1GPa
:nu 0.35d0 ;; Poission's ratio
:gravity -9.8d0 ;;Gravity acting in the y direction
))
;;Apply a damping n/m^2
(setf (cl-mpm:sim-damping-factor *sim*) 10d0)
;;Estimate a dt value from the p wave modulus
(setf (cl-mpm:sim-dt *sim*) (cl-mpm/setup:estimate-elastic-dt *sim*))
;;Output this to the terminal
(format t "Estimated dt ~f~%" (cl-mpm:sim-dt *sim*))
You can then visulise the domain with gnuplot (if installed)
(ql:quickload :cl-mpm/plotter)
(cl-mpm/plotter:simple-plot *sim*)
We need to set a kernel count for the parallel implementation (I have a 4 core machine). We can then run through 500 steps to let the system settle
;;Set the kernel thread count
(setf lparallel:*kernel* (lparallel:make-kernel 4 :name "custom-kernel"))
;;Do 500 updates
(dotimes (i 500)
(format t "Steps ~D~%" i)
(cl-mpm:update-sim *sim*))
We can then check the displacement of the system, using a lambda function to extract the x displacement from the (3x1) sized vector
(cl-mpm/plotter:simple-plot
*sim*
:plot :deformed
:colour-func (lambda (mp) (magicl:tref (cl-mpm/particle::mp-displacement mp) 0 0)))
We get a displacement variation as expected:
To change consitutitve model to a von-mises perfectly plastic model, we just change our setup code from
...
'cl-mpm/particle::particle-elastic
:E 1d5 ;; Young's modulus 1GPa
:nu 0.35d0 ;; Poission's ratio
...
To a von-mises particle with an extra rho field.
...
'cl-mpm/particle::particle-vm
:E 1d5 ;; Young's modulus 1GPa
:nu 0.35d0 ;; Poission's ratio
:rho 1.5d3 ;; Yield stress
...
This results in a much greater displacement under plastic deformation
This code is being developed to help model ice sheets and glaciers and to investigate new calving models.
As such most of the development is directed towards this process.
To handle large deformations it is possible to use the Kirchhoff stress and logarithmic strains as a basis, and use small strain constitutive models with an additive strain split. What this means is that out of the box any strain->stress mapping that works with small strain will work in a large deformation context. Plasticity may be adapted using an exponential mapping.
To model stress rate equations, we can't use this method. To solve this an objective stress rate must be used.
We can update our logarithmic strain by first taking calculating the velocity gradient \(L\)
\[ L = \nabla v \]The stretch rate can be found from either from this, or directly from the grid:
\[ D = \frac{1}{2}(L + L^T) \]\[ D = \frac{1}{2}(\nabla v + (\nabla v)^T) \]We can then find our deformation gradient update from this update.
\[ \Delta F = I + L \]\[ F_{n+1} = \Delta F F_n \]By taking our engineering logarithmic strain, we can calculate the left Cauchy-Green strain tensor \(b_n\):
\[ \varepsilon_{n+1} = exp(2b_{n+1}) \]We can then update the left Cauchy-Green strain by the formula:
\[ b_{n+1} = \Delta F b_n (\Delta F)^T \]From our updated strain \(b_{n+1}\), we can re-derive our engineering logarithmic strain measures.
\[ \varepsilon_{n+1} = \frac{1}{2}\ln(b_{n+1}) \]This is fine for constitutive modelling of hyperelastic materials, and plastic materials - but if you want to write constitutive models in terms of a stress rate this won't work.
If you want to define a viscoelastic material - where the stress at every step is not defined entirely by the strain we need an integral form of stress update.
If we have a constitutive function \(C\) of the form:
\[ \dot\sigma = C(\sigma,\dot\varepsilon) \]We will find that as the material moves and rotates through the world we will get stresses pointing in the wrong direction. To solve this we use a pair of so-called "objective" measures of stress and strain \(\mathring{\sigma},\mathring{\varepsilon}\).
There are many objective stress rates, with varying degrees of complexity. A very good choice of objective stress rate keeping with our logarithmic strains is the corrotational logarithmic stress rate, as it turns out the strain-rate measure \(\mathring{\varepsilon}\) is the natural Eulieran stretch measure \(D\)!.
\[ \mathring{\sigma} = \dot{\sigma} + \Omega^{log} \sigma - \sigma\Omega^{log}\ \]Where we use the tensor factor \(\Omega^{log}\) which somehow captures the rotation of the stress \(\sigma\) over each timestep. For posterity the form of this tensor is defined by the eigenprojectections and the stretch-rate:
\[ P^i = v_i \otimes v_i\\ \]\[ \Omega^{log}_{ij} = w_{ij} + \sum^{n}_{A,B=1,A\neq B}{\left(\frac{\lambda_A + \lambda_B}{\lambda_A - \lambda_B} + \frac{2}{\ln{\lambda_A} - \ln{\lambda_B}}\right)} P^A_{ik} D_{kl} P^B_{lm} \]By utilising this adjustments factor we may still keep our rate-based small-strain constitutive formulations in a large deformation context but understanding that we are using logarithmic strain under the hood.
From our logarithmic strain, we may implement small strain theory constitutive models (additive strain split) in a large deformation system with ease. Each constitutive model is implemented as a different class, found in the src/particles file.
The simplest model we may implement is isotropic linear elasticity with material non-linearity. We state there is a linear relationship between the work conjugate pair of logarithmic strain and Kirchhoff stress.
\[ \tau = [D^e] \varepsilon \]Material points with elastic behaviour are derived from the particle-elastic class. The class contains \(E\) and \(\nu\), and \([D^e]\) variables. When either of \(E\) or \(\nu\) are updated, the elastic matrix is updated alongside it.
This is made easy by CLOS's ability to hook into setting parameters:
(defun update-elastic-matrix (particle)
(with-accessors ((de mp-elastic-matrix)
(E mp-E)
(nu mp-nu))
particle
(setf de (cl-mpm/constitutive::linear-elastic-matrix E nu))))
(defmethod (setf mp-E) :after (value (p particle-elastic))
(update-elastic-matrix p))
We define a function (defun) for updating the elastic matrix, and a generic method (defmethod) that runs after a material points mp-E slot has been set. On the caller side, this happens seamlessly.
(setf (mp-e mp) 10d0) -> [D^e] updated
(setf (mp-damage mp) 1d0) -> No special behaviour
We may use the corotational rate of Kirchhoff stress to write a simple compressible viscoelastic model.
\[ \dot{\tau} = G \mathring{\varepsilon}^D - \frac{G}{\eta} \tau^D \]Where we have extended the Maxwell viscoelastic model to only apply on the deviatoric stress. This is then implemented with a non-objective integration algorithm as for an explicit-dynamic code the rotation per timestep is small enough to be accurate.
\[ \tau_{n+1} = \tau_n + (G \Delta t \mathring{\varepsilon}^D - \Delta t \frac{G}{\eta} \tau^D + \Delta t \tau \Omega^{log} - \Delta t \Omega^{log}\tau) \]Material points with viscoelastic behaviour are derived from the particle-viscoelastic class - itself inheriting from the particle-elastic class.
Modeling fracture numerically is a very complex and finicky topic, and often comes with many issues that are hard to overcome.
One of the most powerful and easy to understand frameworks is continuum damage mechanics, where we attempt to model fracture in some bulk averaged way.
Instead of trying to represent every micro-crack in a continuum discreetly, we lump them together into a state parameter damage \(d\). The materials state of decay can be represented with a value of 0 (undamaged) to 1 (completely damaged). One way of thinking of this parameter is the ratio of area of the material that has now become a crack/void.
\[ d = \frac{A_D}{A} \]This parameter then effects the average properties of a material, such as through the constitutive model of a damaging elastic material.
Undamaged:
\[ \tau = [D^e] \varepsilon \]Damaged:
\[ \overline{\tau} = (1-d)[D^e] \varepsilon \]Where we represent the damaged stress with an over-bar.
To evolve damage, we can prescribe a relationship between an unbounded history dependent variable \(\kappa\) and the bounded damage \(d\).
\[ d(\kappa) = 1 - e^{\frac{\kappa - \varepsilon_0}{\varepsilon_f - \varepsilon_0}} \]Where \(\varepsilon_0\) is the initiation strain - directly limited to the failure stress, and \(\varepsilon_f\) is the failure strain and related to the softening.
For a quasi-static simulation we may evolve \(\kappa\) as the maximum of our damage criteria \(\chi\) over time:
\[ \kappa_{n+1} = max[\chi,\kappa_{n}] \]For dynamic fracture simulations with lots of transient stresses we can take a delayed damage approach, and use a similar approach to viscoplastic regularization.
We introduce a characteristic time \(\tau\) that slows damage updating, leading to a PDE that must be time-integrated.
\[ \dot{\kappa} = \frac{\langle\chi - \kappa\rangle}{\tau} \]The damage criteria determines how the damage evolves.
The maximum principal stress is the simplest damage criteria:
\[ \chi = \sigma_I \]Although this project started out as a way of learning common lisp, the flexibility and interactive development make it easy to extend and work with complicated topics simply. Some tasks that are difficult-to-impossible in other languages are trivial in common lisp. This codebase is not the prettiest, but is also not too hard to extend.
Eulerian methods of fluid simulation use a background mesh/grid as the frame of reference, each grid has fluid moving in, and some moving out. Due to the mesh being well defined we can use it to help us calculate gradients in the material like pressure and volume.
Lagrangian methods use the particles themselves as the frame of reference, each particle of material exists with some mass and volume and moves around through the system interacting with other particles.