diff --git a/demos/jax/elastoplasticity/Hosford_yield_surface.md b/demos/jax/elastoplasticity/Hosford_yield_surface.md
index 70125c3..2e2aa37 100644
--- a/demos/jax/elastoplasticity/Hosford_yield_surface.md
+++ b/demos/jax/elastoplasticity/Hosford_yield_surface.md
@@ -49,7 +49,7 @@ elastic_model = LinearElasticIsotropic(E, nu)
a = 10.0
sig0 = 500.0
material = GeneralIsotropicHardening(
- elastic_model, lambda sig: sig0, lambda sig: Hosford_stress(sig, a=a)
+ elastic_model, lambda p: sig0, lambda sig: Hosford_stress(sig, a=a)
)
```
diff --git a/demos/mfront/finite_strain_elastoplasticity/finite_strain_elastoplasticity.md b/demos/mfront/finite_strain_elastoplasticity/finite_strain_elastoplasticity.md
index 06ba9d8..f28a69e 100644
--- a/demos/mfront/finite_strain_elastoplasticity/finite_strain_elastoplasticity.md
+++ b/demos/mfront/finite_strain_elastoplasticity/finite_strain_elastoplasticity.md
@@ -62,11 +62,14 @@ H0.setEntryName("HardeningSlope");
};
```
-## `FEniCS` implementation
+## `FEniCSx` implementation
We define a box mesh representing half of a beam oriented along the $x$-direction. The beam will be fully clamped on its left side and symmetry conditions will be imposed on its right extremity. The loading consists of a uniform self-weight.
-
+```{image} finite_strain_plasticity_solution.png
+:align: center
+:width: 500px
+```
```{code-cell} ipython3
import numpy as np
@@ -84,12 +87,16 @@ from dolfinx_materials.utils import (
nonsymmetric_tensor_to_vector,
)
+
+comm = MPI.COMM_WORLD
+rank = comm.rank
+
current_path = os.getcwd()
length, width, height = 1.0, 0.04, 0.1
-nx, ny, nz = 30, 5, 8
+nx, ny, nz = 20, 4, 6
domain = mesh.create_box(
- MPI.COMM_WORLD,
+ comm,
[(0, -width / 2, -height / 2.0), (length, width / 2, height / 2.0)],
[nx, ny, nz],
cell_type=mesh.CellType.tetrahedron,
@@ -134,10 +141,11 @@ material = MFrontMaterial(
"LogarithmicStrainPlasticity",
material_properties={"YieldStrength": 250e6, "HardeningSlope": 1e6},
)
-print(material.behaviour.getBehaviourType())
-print(material.behaviour.getKinematic())
-print(material.gradient_names, material.gradient_sizes)
-print(material.flux_names, material.flux_sizes)
+if rank == 0:
+ print(material.behaviour.getBehaviourType())
+ print(material.behaviour.getKinematic())
+ print(material.gradient_names, material.gradient_sizes)
+ print(material.flux_names, material.flux_sizes)
```
In this large-strain setting, the `QuadratureMapping` acts from the deformation gradient $\boldsymbol{F}=\boldsymbol{I}+\nabla\boldsymbol{u}$ to the first Piola-Kirchhoff stress $\boldsymbol{P}$. We must therefore register the deformation gradient as `Identity(3)+grad(u)`.
@@ -178,7 +186,7 @@ Finally, we setup the nonlinear problem, the corresponding Newton solver and sol
problem = NonlinearMaterialProblem(qmap, Res, Jac, u, bcs)
-newton = NewtonSolver(MPI.COMM_WORLD)
+newton = NewtonSolver(comm)
newton.rtol = 1e-4
newton.atol = 1e-4
newton.convergence_criterion = "residual"
@@ -203,7 +211,8 @@ for i, t in enumerate(load_steps[1:]):
converged, it = problem.solve(newton, print_solution=False)
- print(f"Increment {i+1} converged in {it} iterations.")
+ if rank == 0:
+ print(f"Increment {i+1} converged in {it} iterations.")
p0 = qmap.project_on("EquivalentPlasticStrain", ("DG", 0))
@@ -211,7 +220,10 @@ for i, t in enumerate(load_steps[1:]):
vtk.write_function(p0, t)
w = u.sub(2).collapse()
- results[i + 1, 0] = max(np.abs(w.vector.array))
+ local_max = max(np.abs(w.vector.array))
+ # Perform the reduction to get the global maximum on rank 0
+ global_max = comm.reduce(local_max, op=MPI.MAX, root=0)
+ results[i + 1, 0] = global_max
results[i + 1, 1] = t
vtk.close()
```
@@ -220,11 +232,12 @@ During the load incrementation, we monitor the evolution of the maximum vertical
The load-displacement curve exhibits a classical elastoplastic behavior rapidly followed by a stiffening behavior due to membrane catenary effects.
```{code-cell} ipython3
-plt.figure()
-plt.plot(results[:, 0], results[:, 1], "-oC3")
-plt.xlabel("Displacement")
-plt.ylabel("Load")
-plt.show()
+if rank==0:
+ plt.figure()
+ plt.plot(results[:, 0], results[:, 1], "-oC3")
+ plt.xlabel("Displacement")
+ plt.ylabel("Load")
+ plt.show()
```
## References
diff --git a/demos/mfront/finite_strain_elastoplasticity/finite_strain_elastoplasticity.py b/demos/mfront/finite_strain_elastoplasticity/finite_strain_elastoplasticity.py
index 32783d9..95009c8 100644
--- a/demos/mfront/finite_strain_elastoplasticity/finite_strain_elastoplasticity.py
+++ b/demos/mfront/finite_strain_elastoplasticity/finite_strain_elastoplasticity.py
@@ -1,21 +1,38 @@
-# %% [markdown]
+# ---
+# jupyter:
+# jupytext:
+# text_representation:
+# extension: .py
+# format_name: light
+# format_version: '1.5'
+# jupytext_version: 1.16.1
+# kernelspec:
+# display_name: Python 3
+# language: python
+# name: python3
+# ---
+
# # Finite-strain elastoplasticity within the logarithmic strain framework
#
-# This demo is dedicated to the resolution of a finite-strain elastoplastic problem using the logarithmic strain framework proposed in [@miehe_anisotropic_2002].
+# This demo is dedicated to the resolution of a finite-strain elastoplastic problem using the logarithmic strain framework proposed in {cite:p}`miehe2002anisotropic`.
+#
+# ```{tip}
+# This simulation is a bit heavy to run so we suggest running it in parallel.
+# ```
#
# ## Logarithmic strains
#
-# This framework expresses constitutive relations between the Hencky strain measure $\boldsymbol{H} = \dfrac{1}{2}\log (\boldsymbol{F}^T\cdot\boldsymbol{F})$ and its dual stress measure $\boldsymbol{T}$. This approach makes it possible to extend classical small strain constitutive relations to a finite-strain setting. In particular, the total (Hencky) strain can be split additively into many contributions (elastic, plastic, thermal, swelling, etc.) e.g. $\boldsymbol{H}=\boldsymbol{H}^e+\boldsymbol{H}^p$. Its trace is also linked with the volume change $J=\exp(\operatorname{tr}(\boldsymbol{H}))$. As a result, the deformation gradient $\boldsymbol{F}$ is used for expressing the Hencky strain $\boldsymbol{H}$, a small-strain constitutive law is then written for the $(\boldsymbol{H},\boldsymbol{T})$-pair and the dual stress $\boldsymbol{T}$ is then post-processed to an appropriate stress measure such as the Cauchy stress $\boldsymbol{\sigma}$ or Piola-Kirchhoff stresses.
+# This framework expresses constitutive relations between the Hencky strain measure $\boldsymbol{H} = \dfrac{1}{2}\log (\boldsymbol{F}^\text{T}\cdot\boldsymbol{F})$ and its dual stress measure $\boldsymbol{T}$. This approach makes it possible to extend classical small strain constitutive relations to a finite-strain setting. In particular, the total (Hencky) strain can be split additively into many contributions (elastic, plastic, thermal, swelling, etc.) e.g. $\boldsymbol{H}=\boldsymbol{H}^e+\boldsymbol{H}^p$. Its trace is also linked with the volume change $J=\exp(\operatorname{tr}(\boldsymbol{H}))$. As a result, the deformation gradient $\boldsymbol{F}$ is used for expressing the Hencky strain $\boldsymbol{H}$, a small-strain constitutive law is then written for the $(\boldsymbol{H},\boldsymbol{T})$-pair and the dual stress $\boldsymbol{T}$ is then post-processed to an appropriate stress measure such as the Cauchy stress $\boldsymbol{\sigma}$ or Piola-Kirchhoff stresses.
#
# ## `MFront` implementation
#
-# The logarithmic strain framework discussed in the previous paragraph consists merely as a pre-processing and a post-processing stages of the behaviour integration. The pre-processing stage compute the logarithmic strain and its increment and the post-processing stage inteprets the stress resulting from the behaviour integration as the dual stress $\boldsymbol{T}$ and convert it to the Cauchy stress.
+# The logarithmic strain framework discussed in the previous paragraph consists merely as a pre-processing and a post-processing stages of the behavior integration. The pre-processing stage compute the logarithmic strain and its increment and the post-processing stage inteprets the stress resulting from the behavior integration as the dual stress $\boldsymbol{T}$ and convert it to the Cauchy stress.
#
-# `MFront` provides the `@StrainMeasure` keyword that allows to specify which strain measure is used by the behaviour. When choosing the `Hencky` strain measure, `MFront` automatically generates those pre- and post-processing stages, allowing the user to focus on the behaviour integration.
+# `MFront` provides the `@StrainMeasure` keyword that allows to specify which strain measure is used by the behavior. When choosing the `Hencky` strain measure, `MFront` automatically generates those pre- and post-processing stages, allowing the user to focus on the behavior integration.
#
# This leads to the following implementation (see the [small-strain elastoplasticity example](https://thelfer.github.io/mgis/web/mgis_fenics_small_strain_elastoplasticity.html) for details about the various implementations available):
#
-# ```cpp
+# ```
# @DSL Implicit;
#
# @Behaviour LogarithmicStrainPlasticity;
@@ -45,15 +62,16 @@
# };
# ```
#
-# ## `FEniCS` implementation
+# ## `FEniCSx` implementation
#
# We define a box mesh representing half of a beam oriented along the $x$-direction. The beam will be fully clamped on its left side and symmetry conditions will be imposed on its right extremity. The loading consists of a uniform self-weight.
#
-#
-#
-#
+# ```{image} finite_strain_plasticity_solution.png
+# :align: center
+# :width: 500px
+# ```
-# %%
+# +
import numpy as np
import matplotlib.pyplot as plt
import os
@@ -69,12 +87,16 @@
nonsymmetric_tensor_to_vector,
)
+
+comm = MPI.COMM_WORLD
+rank = comm.rank
+
current_path = os.getcwd()
length, width, height = 1.0, 0.04, 0.1
-nx, ny, nz = 30, 5, 8
+nx, ny, nz = 20, 4, 6
domain = mesh.create_box(
- MPI.COMM_WORLD,
+ comm,
[(0, -width / 2, -height / 2.0), (length, width / 2, height / 2.0)],
[nx, ny, nz],
cell_type=mesh.CellType.tetrahedron,
@@ -109,26 +131,25 @@ def right(x):
du = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
u = fem.Function(V, name="Displacement")
+# -
-# %% [markdown]
-# The `MFrontMaterial` instance is loaded from the `MFront` `LogarithmicStrainPlasticity` behaviour. This behaviour is a finite-strain behaviour (`material.is_finite_strain=True`) which relies on a kinematic description using the total deformation gradient $\boldsymbol{F}$. By default, a `MFront` behaviour always returns the Cauchy stress as the stress measure after integration. However, the stress variable dual to the deformation gradient is the first Piola-Kirchhoff (PK1) stress. An internal option of the MGIS interface is therefore used in the finite-strain context to return the PK1 stress as the "flux" associated to the "gradient" $\boldsymbol{F}$. Both quantities are non-symmetric tensors, aranged as a 9-dimensional vector in 3D following [`MFront` conventions on tensors](http://tfel.sourceforge.net/tensors.html).
+# The `MFrontMaterial` instance is loaded from the `MFront` `LogarithmicStrainPlasticity` behavior. This behavior is a finite-strain behavior (`material.is_finite_strain=True`) which relies on a kinematic description using the total deformation gradient $\boldsymbol{F}$. By default, a `MFront` behavior always returns the Cauchy stress as the stress measure after integration. However, the stress variable dual to the deformation gradient is the first Piola-Kirchhoff (PK1) stress. An internal option of the MGIS interface is therefore used in the finite-strain context to return the PK1 stress as the "flux" associated to the "gradient" $\boldsymbol{F}$. Both quantities are non-symmetric tensors, aranged as a 9-dimensional vector in 3D following [`MFront` conventions on tensors](https://thelfer.github.io/tfel/web/tensors.html).
-# %%
material = MFrontMaterial(
os.path.join(current_path, "src/libBehaviour.so"),
"LogarithmicStrainPlasticity",
material_properties={"YieldStrength": 250e6, "HardeningSlope": 1e6},
)
-print(material.behaviour.getBehaviourType())
-print(material.behaviour.getKinematic())
-print(material.gradient_names, material.gradient_sizes)
-print(material.flux_names, material.flux_sizes)
+if rank == 0:
+ print(material.behaviour.getBehaviourType())
+ print(material.behaviour.getKinematic())
+ print(material.gradient_names, material.gradient_sizes)
+ print(material.flux_names, material.flux_sizes)
-# %% [markdown]
-# The `QuadratureMap` object must therefore register the deformation gradient as `Identity(3)+grad(u)`.
+# In this large-strain setting, the `QuadratureMapping` acts from the deformation gradient $\boldsymbol{F}=\boldsymbol{I}+\nabla\boldsymbol{u}$ to the first Piola-Kirchhoff stress $\boldsymbol{P}$. We must therefore register the deformation gradient as `Identity(3)+grad(u)`.
-# %%
+# +
def F(u):
return nonsymmetric_tensor_to_vector(ufl.Identity(gdim) + ufl.grad(u))
@@ -139,75 +160,81 @@ def dF(u):
qmap = QuadratureMap(domain, 2, material)
qmap.register_gradient("DeformationGradient", F(u))
+# -
-# %%
-sig = qmap.fluxes["FirstPiolaKirchhoffStress"]
-Res = (ufl.dot(sig, dF(v)) - ufl.dot(selfweight, v)) * qmap.dx
+# We will work in a Total Lagrangian formulation, writing the weak form of equilibrium on the reference configuration $\Omega_0$, thereby defining the nonlinear residual weak form as:
+# Find $\boldsymbol{u}\in V$ such that:
+#
+# $$
+# \int_{\Omega_0} \boldsymbol{P}(\boldsymbol{F}(\boldsymbol{u})):\nabla \boldsymbol{v} \,\text{d}\Omega - \int_{\Omega_0} \boldsymbol{f}\cdot\boldsymbol{v}\,\text{d}\Omega = 0 \quad \forall \boldsymbol{v}\in V
+# $$
+# where $\boldsymbol{f}$ is the self-weight.
+#
+# The corresponding Jacobian form is computed via automatic differentiation. As for the [small-strain elastoplasticity example](https://thelfer.github.io/mgis/web/mgis_fenics_small_strain_elastoplasticity.html), state variables include the `ElasticStrain` and `EquivalentPlasticStrain` since the same behavior is used as in the small-strain case with the only difference that the total strain is now given by the Hencky strain measure. In particular, the `ElasticStrain` is still a symmetric tensor (vector of dimension 6). Note that it has not been explicitly defined as a state variable in the `MFront` behavior file since this is done automatically when using the `IsotropicPlasticMisesFlow` parser.
+
+PK1 = qmap.fluxes["FirstPiolaKirchhoffStress"]
+Res = (ufl.dot(PK1, dF(v)) - ufl.dot(selfweight, v)) * qmap.dx
Jac = qmap.derivative(Res, u, du)
-# %% [markdown]
-# The loading is then defined and, as for the [small-strain elastoplasticity example](https://thelfer.github.io/mgis/web/mgis_fenics_small_strain_elastoplasticity.html), state variables include the `ElasticStrain` and `EquivalentPlasticStrain` since the same behaviour is used as in the small-strain case with the only difference that the total strain is now given by the Hencky strain measure. In particular, the `ElasticStrain` is still a symmetric tensor (vector of dimension 6). Note that it has not been explicitly defined as a state variable in the `MFront` behaviour file since this is done automatically when using the `IsotropicPlasticMisesFlow` parser.
-#
-# Finally, we setup a few parameters of the Newton non-linear solver.
+# Finally, we setup the nonlinear problem, the corresponding Newton solver and solve the load-stepping problem.
-# %%
+# + tags=["hide-output"]
problem = NonlinearMaterialProblem(qmap, Res, Jac, u, bcs)
-newton = NewtonSolver(MPI.COMM_WORLD)
+newton = NewtonSolver(comm)
newton.rtol = 1e-4
newton.atol = 1e-4
-newton.convergence_criterion = "incremental"
+newton.convergence_criterion = "residual"
newton.report = True
# Set solver options
ksp = newton.krylov_solver
opts = PETSc.Options()
option_prefix = ksp.getOptionsPrefix()
-opts[f"{option_prefix}ksp_type"] = "cg"
-opts[f"{option_prefix}pc_type"] = "gamg"
+opts[f"{option_prefix}ksp_type"] = "preonly"
+opts[f"{option_prefix}pc_type"] = "lu"
opts[f"{option_prefix}pc_factor_mat_solver_type"] = "mumps"
ksp.setFromOptions()
Nincr = 30
load_steps = np.linspace(0.0, 1.0, Nincr + 1)
-vtk_u = io.VTKFile(domain.comm, "results_u.pvd", "w")
-vtk_p = io.VTKFile(domain.comm, "results_p.pvd", "w")
+vtk = io.VTKFile(domain.comm, f"results/{material.name}.pvd", "w")
results = np.zeros((Nincr + 1, 2))
for i, t in enumerate(load_steps[1:]):
selfweight.value[-1] = -50e6 * t
converged, it = problem.solve(newton, print_solution=False)
- print(f"Increment {i+1} converged in {it} iterations.")
+ if rank == 0:
+ print(f"Increment {i+1} converged in {it} iterations.")
p0 = qmap.project_on("EquivalentPlasticStrain", ("DG", 0))
- vtk_u.write_function(u, t)
- vtk_p.write_function(p0, t)
+ vtk.write_function(u, t)
+ vtk.write_function(p0, t)
w = u.sub(2).collapse()
- results[i + 1, 0] = max(np.abs(w.vector.array))
+ local_max = max(np.abs(w.vector.array))
+ # Perform the reduction to get the global maximum on rank 0
+ global_max = MPI.COMM_WORLD.reduce(local_max, op=MPI.MAX, root=0)
+ results[i + 1, 0] = global_max
results[i + 1, 1] = t
-vtk_u.close()
-vtk_p.close()
+vtk.close()
+# -
-# %% [markdown]
# During the load incrementation, we monitor the evolution of the maximum vertical downwards displacement.
-#
-# This simulation is a bit heavy to run so we suggest running it in parallel:
-# ```bash
-# mpirun -np 4 python3 finite_strain_elastoplasticity.py
-# ```
-
-# %% [markdown]
-# The load-displacement curve exhibits a classical elastoplastic behaviour rapidly followed by a stiffening behaviour due to membrane catenary effects.
+# The load-displacement curve exhibits a classical elastoplastic behavior rapidly followed by a stiffening behavior due to membrane catenary effects.
-# %%
-plt.figure()
-plt.plot(results[:, 0], results[:, 1], "-o")
-plt.xlabel("Displacement")
-plt.ylabel("Load")
-plt.show()
+if rank==0:
+ plt.figure()
+ plt.plot(results[:, 0], results[:, 1], "-oC3")
+ plt.xlabel("Displacement")
+ plt.ylabel("Load")
+ plt.show()
-# %%
+# ## References
+#
+# ```{bibliography}
+# :filter: docname in docnames
+# ```