Computational Bottlenecks in PhaseFieldX: Optimization Strategies for Phase-Field Fracture Simulations

The primary computational bottlenecks in PhaseFieldX involve repeated assembly of global residuals and Jacobians, direct LU factorization for linear solves, and excessive post-processing during Newton iterations, which can be optimized through cached assemblies, iterative Krylov solvers with AMG preconditioning, and reduced-frequency energy calculations.

PhaseFieldX is an open-source finite element framework built on dolfinx (FEniCS x) and PETSc for simulating phase-field fracture mechanics. While the variational solver in src/phasefieldx/Element/Phase_Field_Fracture/solver/solver_ener_variational.py provides robust energy-controlled fracture modeling, several computational bottlenecks limit scalability for fine meshes and three-dimensional problems.

Identifying Computational Bottlenecks in the Variational Solver

Repeated Assembly of Residuals and Jacobians

The most significant bottleneck occurs in the _assemble_residual method (lines 18‑38 of solver_ener_variational.py). Each Newton iteration calls dolfinx.fem.petsc.assemble_vector_block (and the fallback assemble_vector), traversing every cell to evaluate non-linear forms. This assembly is O(#cells × poly(order)) and dominates runtime for fine meshes or 3‑D problems.

Direct Linear Solves with LU Factorization

The solver defaults to direct LU factorization via PETSc options defined at lines 62‑65: ksp_type: preonly, pc_type: lu, and pc_factor_mat_solver_type: mumps. For large systems, this requires O(N³) memory and CPU operations, quickly becoming prohibitive when unknowns exceed a few × 10⁴, especially in three dimensions.

Excessive Post-Processing Overhead

Energy and reaction-force calculations introduce significant overhead when performed every Newton step. The compute_total_energies function (called around line 42) assembles two scalar forms (psi_a, psi_b) each iteration, while calculate_reaction_forces (lines 28‑38) repeats full residual assembly for every Dirichlet boundary condition. Though individually cheap, these operations accumulate when performed hundreds of times per load step.

Python-Level Loop Overhead

The reaction-force block iterates over bc_list_u in pure Python, calling the expensive assembly routine for each boundary condition. While the overhead is small compared to assembly itself, the repeated assembly is unnecessary because the same residual vector can be reused.

Optimization Strategies for PhaseFieldX Performance

Cache Assembled Forms to Reduce Assembly Costs

Separate the linear and non-linear components of the residual. Assemble the linear parts once outside the Newton loop, then reuse them with dolfinx.fem.petsc.assemble_vector for non-linear contributions only.


# Before the Newton loop

F_u_form = dolfinx.fem.form(ufl.inner((g(Φ,…) + …)*sigma_a(u,…) + sigma_b(u,…), epsilon(δu))*ufl.dx)
J_u_form = dolfinx.fem.form(ufl.derivative(F_u, u, du))

# Inside _assemble_residual, update only non-linear additions

Switch to Iterative Krylov Solvers with AMG

Replace LU factorization with GMRES or CG preconditioned by algebraic multigrid (AMG). This reduces memory usage from O(N³) to O(N) and significantly improves scalability for large 3‑D meshes.

petsc_options = {
    "ksp_type": "gmres",
    "pc_type": "hypre",
    "pc_hypre_type": "boomeramg",
    "ksp_rtol": 1e-8,
    "ksp_atol": 1e-10,
}
solver_snap = RealSpaceNewtonSolver(
    F_blocked, [u, Φ, λ],
    J=J_blocked, bcs=bc_list_u,
    petsc_options=petsc_options,
)

Reduce Energy and Reaction Force Evaluation Frequency

Move energy calculations out of the Newton iteration and perform them only after load step convergence, or every n steps using a modulo check.

if step % 5 == 0:  # Compute every 5 load steps

    E, psi_a, psi_b = compute_total_energies(u, Φ, Data, comm)
    header = "#step\tE\tpsi_a\tpsi_b"
    append_results_to_file(
        os.path.join(result_folder_name, "total.energy"),
        header, step, E, psi_a, psi_b,
    )

Reuse Residual Vectors for Reaction Forces

After Newton convergence, the residual vector already contains reaction contributions. Store it once and slice it for each boundary condition instead of re-assembling.


# After solver_snap.solve() converges

residual_vector = solver_snap._F  # Blocked residual already assembled

for i, bc in enumerate(bc_list_u):
    # Slice the vector according to the DOF layout of the BC

    R = calculate_reaction_forces_from_vector(residual_vector, bc, dimension)
    append_results_to_file(
        os.path.join(result_folder_name, f"{bcs_list_u_names[i]}.reaction"),
        "#step\tRx\tRy\tRz", step, *R,
    )

Implement Matrix-Free Operators

For very large problems, use dolfinx.fem.create_matrix with matrix-free operators to avoid storing the dense blocked Jacobian. This is particularly effective when combined with iterative solvers.

J_mat = dolfinx.fem.create_matrix(J_blocked, mat_type="aij")

# Pass J_mat to the Newton solver instead of the full blocked matrix

Expected Performance Improvements

Applying these optimization strategies to PhaseFieldX typically yields:

  • 30 %‑50 % reduction in wall-clock time for 2‑D benchmark meshes (≈ 10⁴ degrees of freedom).
  • > 5× memory savings for 3‑D simulations when moving from direct LU factorization to AMG-preconditioned GMRES.
  • Significantly faster post-processing because energy and reaction force calculations are no longer performed every Newton iteration.

Summary

  • The primary computational bottlenecks in PhaseFieldX are repeated assembly of residuals and Jacobians, direct LU linear solves, and excessive post-processing during Newton iterations.
  • Optimization strategies include caching assembled forms, switching to iterative Krylov solvers with algebraic multigrid preconditioners, and reducing the frequency of energy and reaction force evaluations.
  • Reusing residual vectors for reaction force calculations and implementing matrix-free operators provide additional performance gains for large-scale 3‑D simulations.
  • These changes are implemented in src/phasefieldx/Element/Phase_Field_Fracture/solver/solver_ener_variational.py and related solver configuration files according to the PhaseFieldX source code.

Frequently Asked Questions

What causes the slow performance in PhaseFieldX Newton iterations?

The slow performance stems from repeated assembly of the global residual and Jacobian inside the _assemble_residual method of solver_ener_variational.py. Each Newton step traverses every mesh cell to evaluate non-linear forms, resulting in O(#cells × poly(order)) complexity that dominates runtime for fine meshes.

How can I reduce memory usage for large 3‑D phase-field simulations?

Switch from direct LU factorization to an iterative Krylov solver such as GMRES preconditioned with algebraic multigrid (AMG). In solver_ener_variational.py, replace the PETSc options ksp_type: preonly and pc_type: lu with ksp_type: gmres and pc_type: hypre, which reduces memory complexity from O(N³) to O(N).

Why are energy calculations making my simulations slower?

The compute_total_energies function is called every Newton iteration by default, assembling scalar forms for elastic energy components repeatedly. This post-processing overhead accumulates significantly over hundreds of iterations. Moving energy evaluation to occur only every n load steps—or only after Newton convergence—eliminates this bottleneck.

Can reaction forces be computed without re-assembling the residual?

Yes. After the Newton solver converges, the residual vector already contains the reaction forces. Instead of calling calculate_reaction_forces with repeated assembly for each Dirichlet boundary condition, access solver_snap._F (the blocked residual) and slice it according to each boundary condition's DOF layout. This avoids O(#BCs) redundant assemblies per step.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →