How to Implement Fatigue Simulations Using PhaseFieldX: A Complete Guide
Enable fatigue simulations in PhaseFieldX by setting fatigue=True in the Input class, selecting a degradation function like "asymptotic", and running the staggered solver which automatically updates the cumulative fatigue history at each load step.
PhaseFieldX is an open-source finite element framework for phase-field fracture modeling. This guide explains how to implement fatigue simulations using PhaseFieldX by leveraging its built-in variational phase-field fatigue model for brittle materials.
Core Components of Fatigue Simulations in PhaseFieldX
PhaseFieldX implements fatigue through three interconnected components that handle parameter storage, degradation calculation, and field updates during the staggered solution process.
The Input Class Configuration
The Input class in src/phasefieldx/Element/Phase_Field_Fracture/Input.py stores all simulation parameters, including the fatigue toggle and degradation function selection. Key fatigue-related attributes include:
fatigue– Boolean flag to activate the fatigue loopfatigue_degradation_function– String name of the degradation model (e.g., "asymptotic")fatigue_val– Material-specific critical fatigue value (\alpha_{\text{crit}})
Fatigue Degradation Functions
The file src/phasefieldx/Element/Phase_Field_Fracture/fatigue_degradation_functions.py implements the mathematical models that evaluate the evolution of the fatigue history variable (\bar{\alpha}) and return the degradation factor (f(\bar\alpha)).
Currently, the framework provides the asymptotic model, which uses the material parameter fatigue_val as a critical threshold. When (\bar\alpha) exceeds this value, the degradation factor follows (\bigl(2\alpha_{\mathrm{crit}}/(\alpha_{\bar}+\alpha_{\mathrm{crit}})\bigr)^2); otherwise it remains 1 (no degradation).
The Staggered Fracture Solver
The core integration happens in src/phasefieldx/Element/Phase_Field_Fracture/solver/solver.py. After the displacement field is updated at each load step, the solver executes the fatigue routine when Data.fatigue is True (lines 62-76):
- Updates the degradation-type field (g(\cdot)) for the current displacement
- Computes the incremental fatigue history (\Delta\alpha) using the difference between current and previous phase-field values
- Accumulates the fatigue history variable (\bar\alpha)
- Applies the selected fatigue degradation function to compute the final degradation field
Step-by-Step Implementation Guide
Follow these steps to configure and execute a fatigue simulation using PhaseFieldX.
Step 1: Configure Fatigue Parameters in Input
Import the Input class and enable fatigue with the appropriate degradation function and material constants.
from phasefieldx.Element.Phase_Field_Fracture.Input import Input
Data = Input(
E=210.0, # Young's modulus [kN/mm²]
nu=0.3, # Poisson's ratio
Gc=0.0027, # Critical energy release rate [kN/mm]
l=0.004, # Length-scale parameter [mm]
degradation="isotropic",
split_energy="no",
degradation_function="quadratic",
irreversibility="miehe",
fatigue=True, # Enable fatigue
fatigue_degradation_function="asymptotic",
fatigue_val=0.05625, # Material-specific fatigue constant
save_solution_vtu=True,
results_folder_name="fatigue_demo"
)
Step 2: Set Up Cyclic Boundary Conditions
Fatigue simulations require cyclic loading. Define a time-dependent boundary condition function that applies a periodic load. The example in examples/Fatigue/plot_1800.py demonstrates this pattern:
import numpy as np
import petsc4py
amplitude = 0.002
f = 1/8
w = 2 * np.pi * f
def update_boundary_conditions(bcs, t):
# Sinusoidal cyclic displacement
val = 2/np.pi * amplitude * np.arcsin(np.sin(w * t))
bcs[0].g.value[...] = petsc4py.PETSc.ScalarType(val)
return 0, val, 0
Step 3: Execute the Solver
Call the solve function from phasefieldx.Element.Phase_Field_Fracture.solver.solver with the configured Data object and cyclic boundary conditions.
from phasefieldx.Element.Phase_Field_Fracture.solver.solver import solve
dt = 1.0
final_time = 8 * 200 + 1 # 200 cycles, 8 steps per cycle
solve(
Data,
msh,
final_time,
V_u,
V_phi,
bcs_list_u,
bcs_list_phi,
update_boundary_conditions,
f=None,
T_list_u=None,
update_loading=None,
ds_list=ds_list,
dt=dt,
path=None,
bcs_list_u_names=["top", "bottom"],
min_stagger_iter=2,
max_stagger_iter=500,
stagger_error_tol=1e-8
)
The solver automatically writes the fatigue field Fatigue to VTU files at each saved timestep.
Step 4: Post-Process Fatigue Results
Use the AllResults class to extract the cumulative fatigue history or visualize the degradation field.
from phasefieldx.PostProcessing.ReferenceResult import AllResults
results = AllResults("fatigue_demo")
cycles = results.dof_files["top.dof"]["#step"] * (1/8)
fatigue_field = results.field_files["Fatigue"]
Complete Working Example
Below is a condensed, runnable script based on examples/Fatigue/plot_1800.py that demonstrates the full workflow:
#!/usr/bin/env python3
import os
import numpy as np
import dolfinx
import mpi4py
import petsc4py
from phasefieldx.Element.Phase_Field_Fracture.Input import Input
from phasefieldx.Element.Phase_Field_Fracture.solver.solver import solve
from phasefieldx.Boundary.boundary_conditions import bc_xy, bc_y, get_ds_bound_from_marker
# 1. Load mesh
msh_file = os.path.join("mesh", "mesh.msh")
gdim = 2
mesh_comm = mpi4py.MPI.COMM_WORLD
mesh_data = dolfinx.io.gmsh.read_from_msh(msh_file, mesh_comm, 0, gdim)
msh = mesh_data.mesh
facet_markers = mesh_data.facet_tags
fdim = msh.topology.dim - 1
# 2. Boundary setup
bottom = facet_markers.find(9)
top = facet_markers.find(10)
ds_bottom = get_ds_bound_from_marker(bottom, msh, fdim)
ds_top = get_ds_bound_from_marker(top, msh, fdim)
ds_list = np.array([[ds_bottom, "bottom"], [ds_top, "top"]])
# 3. Function spaces
V_u = dolfinx.fem.functionspace(msh, ("Lagrange", 1, (msh.geometry.dim,)))
V_phi = dolfinx.fem.functionspace(msh, ("Lagrange", 1))
# 4. Boundary conditions
bc_bottom = bc_xy(bottom, V_u, fdim)
bc_top = bc_y(top, V_u, fdim)
bcs_list_u = [bc_top, bc_bottom]
bcs_list_phi = []
# 5. Cyclic load definition
amplitude = 0.002
f = 1/8
w = 2 * np.pi * f
def update_boundary_conditions(bcs, t):
val = 2/np.pi * amplitude * np.arcsin(np.sin(w * t))
bcs[0].g.value[...] = petsc4py.PETSc.ScalarType(val)
return 0, val, 0
# 6. Fatigue configuration
Data = Input(
E=210.0, nu=0.3, Gc=0.0027, l=0.004,
degradation="isotropic", split_energy="no",
degradation_function="quadratic", irreversibility="miehe",
fatigue=True,
fatigue_degradation_function="asymptotic",
fatigue_val=0.05625,
save_solution_vtu=True,
results_folder_name="fatigue_demo"
)
# 7. Solve
dt = 1.0
final_time = 8 * 200 + 1
solve(
Data, msh, final_time, V_u, V_phi,
bcs_list_u, bcs_list_phi, update_boundary_conditions,
f=None, T_list_u=None, update_loading=None,
ds_list=ds_list, dt=dt, path=None,
bcs_list_u_names=["top", "bottom"],
min_stagger_iter=2, max_stagger_iter=500, stagger_error_tol=1e-8
)
Extending Fatigue Models
To implement a custom fatigue degradation law, extend src/phasefieldx/Element/Phase_Field_Fracture/fatigue_degradation_functions.py:
import numpy as np
def logarithmic(alpha_bar, Data):
"""
Custom logarithmic fatigue degradation model.
"""
alpha_crit = Data.fatigue_val
return np.log1p(alpha_bar / alpha_crit)
# Register in the dispatcher (around line 62-66 in the source)
def fatigue_degradation(alpha_bar, Data):
if Data.fatigue_degradation_function == "asymptotic":
return asymptotic(alpha_bar, Data)
elif Data.fatigue_degradation_function == "logarithmic":
return logarithmic(alpha_bar, Data)
else:
raise ValueError("Unknown fatigue model")
The framework automatically dispatches to your new function based on the fatigue_degradation_function string provided in the Input object.
Summary
- Enable fatigue by setting
fatigue=Truein theInputclass along withfatigue_degradation_functionandfatigue_valparameters. - Source files for fatigue logic are located in
src/phasefieldx/Element/Phase_Field_Fracture/Input.py,fatigue_degradation_functions.py, andsolver/solver.py. - Cyclic loading is required for meaningful fatigue analysis; define time-dependent boundary conditions using the
update_boundary_conditionscallback. - The solver automatically updates the cumulative fatigue history (\bar\alpha) and computes the degradation field at each load step when fatigue is enabled.
- Post-processing uses the
AllResultsclass to extract theFatiguefield and cumulative history from the output directory.
Frequently Asked Questions
What is the asymptotic fatigue degradation function in PhaseFieldX?
The asymptotic function is the default fatigue degradation model implemented in fatigue_degradation_functions.py. It uses the material parameter fatigue_val as a critical threshold (\alpha_{\text{crit}}). When the cumulative fatigue history (\bar\alpha) exceeds this value, the degradation factor follows (\bigl(2\alpha_{\mathrm{crit}}/(\alpha_{\bar}+\alpha_{\mathrm{crit}})\bigr)^2); otherwise it remains 1, indicating no degradation.
How does PhaseFieldX update the fatigue history variable during simulation?
Inside the staggered solver (solver.py), after each displacement field update, the code checks if Data.fatigue is True. If enabled, it computes the incremental fatigue history (\Delta\alpha) using the difference between the current and previous phase-field values, accumulates this into the cumulative history variable alpha_cum_bar, and applies the selected fatigue degradation function to update the Fatigue field for the next iteration.
Can I implement custom fatigue degradation laws in PhaseFieldX?
Yes, you can extend the fatigue functionality by adding new functions to src/phasefieldx/Element/Phase_Field_Fracture/fatigue_degradation_functions.py. Define your mathematical model as a Python function accepting alpha_bar and Data parameters, then register it in the fatigue_degradation dispatcher by adding a new conditional branch that checks for your custom function name. The framework will automatically use your implementation when you specify the corresponding string in fatigue_degradation_function.
Where are the fatigue simulation results stored in PhaseFieldX?
The fatigue simulation results are stored in the directory specified by results_folder_name in your Input object. When save_solution_vtu=True, the solver writes VTU files containing the Fatigue field (the degradation factor) and the phase-field variable at each saved timestep. You can access these results programmatically using the AllResults class from phasefieldx.PostProcessing.ReferenceResult to load the cumulative history data and field values for visualization or analysis.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →