# FEniCS + dolfin-adjoint in the Mosaic benchmark suite

**URL:** <https://fenicsproject.discourse.group/t/fenics-dolfin-adjoint-in-the-mosaic-benchmark-suite/19782>\
**Category:** General\
**Created:** [July 20, 2026, 4:19pm UTC](https://fenicsproject.discourse.group/t/fenics-dolfin-adjoint-in-the-mosaic-benchmark-suite/19782 "2026-07-20T16:19:26Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![dionhaefner](https://yyz2.discourse-cdn.com/free1/user_avatar/fenicsproject.discourse.group/dionhaefner/32/9581_2.png) [@dionhaefner](https://fenicsproject.discourse.group/u/dionhaefner)\
**Post date:** [July 20, 2026, 4:19pm UTC](https://fenicsproject.discourse.group/t/fenics-dolfin-adjoint-in-the-mosaic-benchmark-suite/19782/1 "2026-07-20T16:19:26Z")

</div>

Hi FEniCS folks,

We just released **Mosaic** ([github.com/pasteurlabs/mosaic](https://github.com/pasteurlabs/mosaic)), a benchmark for _differentiable_ physics solvers. Each solver runs in a container behind a uniform `apply` / `vjp` interface, so one harness compares gradient accuracy and cost across JAX, PyTorch, Julia, and C++ adjoint codes on shared tasks.

**FEniCS (2019.1.0, with dolfin-adjoint) is one of the benchmarked solvers** on the structural-mesh and thermal-mesh domains ([results](https://docs.pasteurlabs.ai/projects/mosaic/stable/docs/results.html) if you’re interested).

We did our best to do it justice, but we don’t know your code as well as you do and have surely made suboptimal calls. For example, we compute the gradient with dolfin-adjoint (`ReducedFunctional.derivative()`), but we call `solve()` with no explicit `solver_parameters`, so we’re just using DOLFIN’s defaults for the linear solve. If there’s a solver, preconditioner, or tolerance you’d pick for these elasticity and heat problems, or if we’re not even taping the right functional, we’d love to know.

The setup is self-contained under [`mosaic/tesseracts/structural-mesh/fenics-structural/`](https://github.com/pasteurlabs/mosaic/tree/main/mosaic/tesseracts/structural-mesh/fenics-structural/) and [`mosaic/tesseracts/thermal-mesh/fenics-heat/`](https://github.com/pasteurlabs/mosaic/tree/main/mosaic/tesseracts/thermal-mesh/fenics-heat/). If something looks odd, corrections or a PR are very welcome, here or on the [Tesseract Forum](https://si-tesseract.discourse.group/).

Mostly we wanted you to know your work is represented, and to get it right.

Cheers,  
Dion on behalf of the Mosaic authors

---

<div class="post-metadata">

**Author:** ![dokken](https://yyz2.discourse-cdn.com/free1/user_avatar/fenicsproject.discourse.group/dokken/32/1560_2.png) [@dokken](https://fenicsproject.discourse.group/u/dokken)\
**Post date:** [September 1, 2026, 9:16pm UTC](https://fenicsproject.discourse.group/t/fenics-dolfin-adjoint-in-the-mosaic-benchmark-suite/19782/2 "2026-09-01T21:16:35Z")

</div>

@dionhaefner as I’ve been working on setting up DOLFINx-adjoint for a while, I had a go at rewriting your example from legacy DOLFIN-adjoint in DOLFINx adjoint:

> <https://github.com/scientificcomputing/dolfinx-adjoint/pull/81/files>
>
> Implementation of the structural problem from Pasteur Labs (mosaic) using DOLFIN…x adjoint.
> Heads up @dionhaefner :)
> 
> Instead of using ADAM I've opted for using an optimization solver that can use reduced hessian vector products as well.
> 
> \## Bug-fixes along the way
> \- Compute\_adjoint split blocked elements when calling extract\_blocks. This was not intentional and therefore we now ensure that blocks are not extracted for the un-blocked case. Test added to verify that this was an issue on main.
> \- Nonlinear test locally exposed an issue with snes converging early if tolerance is too low.
> 
> \## Summary of outputs:
> \[corner\_load\] one tape recompute (Jhat(rho)): 4.7843e-02 s
> \[corner\_load\] one adjoint solve (Jhat.derivative()): 6.0980e-02 s
> 
> \[full\_face\_load\] one tape recompute (Jhat(rho)): 5.1036e-02 s
> \[full\_face\_load\] one adjoint solve (Jhat.derivative()): 6.2825e-02 s
> 
> \<img width="897" height="678" alt="topopt\_compliance\_convergence" src="https://github.com/user-attachments/assets/0ab6e8e8-fabe-4c15-8ffd-1cd7a00e01d5" /\>
> \<img width="1024" height="768" alt="topopt\_full\_face\_load\_final" src="https://github.com/user-attachments/assets/e6eabdb7-aa00-4838-8f55-0899c158fa44" /\>
> \<img width="1024" height="768" alt="topopt\_full\_face\_load" src="https://github.com/user-attachments/assets/9a1162d5-62cd-4b94-be77-a73f9c8041c1" /\>
> \<img width="1024" height="768" alt="topopt\_corner\_load\_final" src="https://github.com/user-attachments/assets/fe9a80c7-7fc0-4647-adf0-e4ac8895a37f" /\>
> \<img width="1024" height="768" alt="topopt\_corner\_load" src="https://github.com/user-attachments/assets/f072762c-4e44-4570-8fb2-689e18130e6b" /\>

I opted in on a different optimizer.  
Under the hood, DOLFINx-adjoint caches alot more work than the old code, which is reflected in the run-time of the solvers.  
On my laptop (one process, Intel® Core™ i7-7700HQ) I get the runtime of:

```bash
\[corner_load\] one tape recompute (Jhat(rho)): 4.7843e-02 s
\[corner_load\] one adjoint solve (Jhat.derivative()): 6.0980e-02 s

\[full_face_load\] one tape recompute (Jhat(rho)): 5.1036e-02 s
\[full_face_load\] one adjoint solve (Jhat.derivative()): 6.2825e-02 s

```

Compared to the timings for forward and adjoint in your code:  
| FEniCS | 0.105 s @ N=16 | 0.358 s @ N=16 |  
Let me know if VJP timed something different than computing the derivative.  
|
