# How to Implement Different Loading Conditions in PhaseFieldX Simulations

> Learn how to implement different loading conditions in PhaseFieldX simulations using its three-step workflow. Discover how to create, collect, and update load objects for advanced simulations.

- Repository: [Miguel Castillón/phasefieldx](https://github.com/castillonmiguel/phasefieldx)
- Tags: how-to-guide
- Published: 2026-02-26

---

**PhaseFieldX decouples loading definitions from the finite-element solver through a three-step workflow: create load objects using helper functions, collect them in a list with boundary measures, and provide an `update_loading` callable to modify values at each time step.**

The `castillonmiguel/phasefieldx` repository provides a modular framework for phase-field fracture simulations where you can implement different loading conditions without modifying the core solver. This architecture supports static, time-dependent, cyclic, and spatially varying loads through a clean separation between load definition and solution algorithms.

## The Three-Step Loading Workflow

### Step 1: Create Load Objects

Begin by constructing load objects using the helper functions in [`src/phasefieldx/Loading/loading_functions.py`](https://github.com/castillonmiguel/phasefieldx/blob/main/src/phasefieldx/Loading/loading_functions.py). These utilities generate `dolfinx.fem.Constant` objects that represent traction or body forces:

- **`loading_Tx`**: Creates a scalar traction in the x-direction
- **`loading_Txy`**: Creates a 2D traction vector (x and y components)
- **`loading_Txyz`**: Creates a 3D traction vector (x, y, and z components)

```python
from phasefieldx.Loading.loading_functions import loading_Txy

# Create a constant vertical traction (initially zero)

T_top = loading_Txy(mesh, value_x=0.0, value_y=0.0)

```

### Step 2: Collect Loads with Boundary Measures

The solvers expect loads packaged as a list of tuples, where each tuple pairs a load constant with a boundary measure (`ds`). The measure identifies the specific boundary or sub-domain where the traction acts:

```python

# Define the top boundary facet marker in the mesh

ds_top = ds(boundary_id=top_facet_marker)

# Assemble the load list

T_list_u = [(T_top, ds_top)]

```

### Step 3: Define the Update Function

Pass an `update_loading` callable to the solver. This function executes at every pseudo-time step, receiving the current load list and scalar time value. Inside this function, you can modify the `Constant` values, replace loads entirely, or change associated measures.

The function signature, as defined in [`src/phasefieldx/Element/Phase_Field_Fracture/solver/solver.py`](https://github.com/castillonmiguel/phasefieldx/blob/main/src/phasefieldx/Element/Phase_Field_Fracture/solver/solver.py), follows this pattern:

```python
def update_loading(T_list_u, time):
    """Modify loads at each time step."""
    # Update logic here

    return Tx, Ty, Tz  # Return values for bookkeeping

```

## Implementing Time-Dependent Loading

The elasticity example in [`examples/Elasticity/plot_1101.py`](https://github.com/castillonmiguel/phasefieldx/blob/main/examples/Elasticity/plot_1101.py) demonstrates force-controlled loading where vertical traction increases linearly with pseudo-time:

```python
def update_loading(T_list_u, time):
    """Increase the y-component of the top traction linearly with time."""
    val = 0.1 * time  # Linear ramp

    T_list_u[0][0].value[1] = petsc4py.PETSc.ScalarType(val)
    return 0, val, 0  # (Tx, Ty, Tz) for bookkeeping

```

Invoke the solver with this update function:

```python
solve(Data,
      msh,
      final_time=11.0,
      V_u=V_u,
      bcs_list_u=bcs_list_u,
      update_boundary_conditions=update_boundary_conditions,
      f=None,
      T_list_u=T_list_u,
      update_loading=update_loading,
      ds_list=ds_list,
      dt=1.0,
      path=None,
      quadrature_degree=2,
      bcs_list_u_names=bcs_list_u_names)

```

## Advanced Loading Scenarios

### Cyclic (Fatigue) Loading

For fatigue simulations, implement `update_loading` with periodic functions or piece-wise ramps based on `time % period`. The fatigue example in [`examples/Fatigue/plot_1800.py`](https://github.com/castillonmiguel/phasefieldx/blob/main/examples/Fatigue/plot_1800.py) follows this pattern, applying sinusoidal or repeated loading cycles without modifying the underlying solver.

### Spatially Varying Traction

Replace `dolfinx.fem.Constant` with `dolfinx.fem.Function` to create spatially dependent loads. Use the mesh coordinates to define traction that varies across the boundary surface. Reference `loading_Txy` in [`loading_functions.py`](https://github.com/castillonmiguel/phasefieldx/blob/main/loading_functions.py) as a template, substituting the constant with a function interpolated over the boundary.

### Prescribed Displacement (Dirichlet) Loading

Control displacement boundaries through the `update_boundary_conditions` callable, which operates in parallel to `update_loading`. This function updates Dirichlet boundary conditions at each time step, enabling displacement-controlled simulations. See [`plot_1101.py`](https://github.com/castillonmiguel/phasefieldx/blob/main/plot_1101.py) for the `update_boundary_conditions` implementation pattern.

### Combined Body Force and Surface Traction

Populate both `f_list_u` (volumetric body forces) and `T_list_u` (boundary tractions) simultaneously. The solver automatically sums these contributions according to the signature in [`solver.py`](https://github.com/castillonmiguel/phasefieldx/blob/main/solver.py) lines 82-84, which accepts both arguments and combines them in the variational form.

## Summary

- **PhaseFieldX** separates loading logic from solvers through the `update_loading` callable pattern implemented in [`src/phasefieldx/Element/Phase_Field_Fracture/solver/solver.py`](https://github.com/castillonmiguel/phasefieldx/blob/main/src/phasefieldx/Element/Phase_Field_Fracture/solver/solver.py).
- Create load objects using helpers in [`src/phasefieldx/Loading/loading_functions.py`](https://github.com/castillonmiguel/phasefieldx/blob/main/src/phasefieldx/Loading/loading_functions.py) such as `loading_Txy` and `loading_Txyz`.
- Package loads as tuples `[(constant, measure), ...]` and pass them to the solver via `T_list_u`.
- Implement time-dependent, cyclic, or spatially varying loads by modifying the `update_loading` function without changing core solver code.
- Reference working examples in [`examples/Elasticity/plot_1101.py`](https://github.com/castillonmiguel/phasefieldx/blob/main/examples/Elasticity/plot_1101.py) and [`examples/Fatigue/plot_1800.py`](https://github.com/castillonmiguel/phasefieldx/blob/main/examples/Fatigue/plot_1800.py) for force-controlled and fatigue loading implementations.

## Frequently Asked Questions

### How do I apply a constant traction that does not change over time?

Create the load using `loading_Txy` or `loading_Txyz` with your desired values, package it in `T_list_u`, and pass this list to the solver without providing an `update_loading` function. The solver will apply the constant traction at every step without modification.

### Can I apply different loads to different boundaries simultaneously?

Yes. Construct multiple load objects and pair each with its specific boundary measure, then include all tuples in `T_list_u`. For example: `T_list_u = [(T_top, ds_top), (T_side, ds_side)]`. The solver processes each load-boundary pair independently.

### What is the difference between `update_loading` and `update_boundary_conditions`?

The `update_loading` function modifies **Neumann** boundary conditions (tractions and body forces) by updating the values of `Constant` or `Function` objects in `T_list_u`. The `update_boundary_conditions` function modifies **Dirichlet** boundary conditions (prescribed displacements) by updating the values passed to `dolfinx.fem.dirichletbc` objects. Use `update_loading` for force-controlled simulations and `update_boundary_conditions` for displacement-controlled simulations.