Skip to content

Handle MeshGeometry in cached solvers (-> #4638)#5188

Open
angus-g wants to merge 3 commits into
JHopeCollins/nlvs-hessian-fixfrom
angus-g/4638-meshgeometry
Open

Handle MeshGeometry in cached solvers (-> #4638)#5188
angus-g wants to merge 3 commits into
JHopeCollins/nlvs-hessian-fixfrom
angus-g/4638-meshgeometry

Conversation

@angus-g

@angus-g angus-g commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

This threads MeshGeometry dependencies through the CachedSolverBlock. Because CoordinateDerivative is a bit strange, we do need to special case forms and flip signs here and there. Particularly for the Hessian forms it goes back to the action -> derivative approach (restrained by CoordinateDerivative) rather than derivative -> action -> adjoint.

I did also test Fireshape here (it passes, whereas before this PR it would fail on test_L2tracking[FeMultiGridControlSpace] and test_TimeTracking). @APaganini do you have anything that we should ensure we check related to this?

@angus-g angus-g requested a review from JHopeCollins June 18, 2026 03:20
@angus-g angus-g added the base:main Run this PR using a main (dev) build label Jun 18, 2026
@angus-g angus-g force-pushed the angus-g/4638-meshgeometry branch from dc82f03 to 3be5af2 Compare June 18, 2026 04:32
Comment thread firedrake/adjoint_utils/blocks/solving.py
@APaganini

APaganini commented Jun 18, 2026 via email

Copy link
Copy Markdown
Contributor

@angus-g angus-g force-pushed the JHopeCollins/nlvs-hessian-fix branch from 442f29e to 62f3a75 Compare July 8, 2026 01:27
angus-g added 3 commits July 8, 2026 11:27
This test relies on moving geometry, so it does exercise the
changes in this branch. test_shape_derivatives probably does too,
but implicitly through interpolate so it might not fail until
we route all solves through the cached solver block.
For whatever reason, derivative(-F, m, dm) has very different behaviour
when passed through the adjoint -> action -> expand_derivatives chain
compared to the non-negated variant. We just push subtraction to the
dFdm_adj and dFdm_adj2 forms instead.
@angus-g angus-g force-pushed the angus-g/4638-meshgeometry branch from 3be5af2 to 923d2f4 Compare July 8, 2026 01:28
@angus-g angus-g marked this pull request as ready for review July 8, 2026 01:28
self._mesh_dependencies()):
if dep.tlm_value is None:
continue
replaced_dep.assign(dep.tlm_value)

@JHopeCollins JHopeCollins Jul 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we ever hit this line for a mesh dependency in the test suite?
I see that the replaced_dep is a Function matching the mesh.coordinates. Is the tlm_value for a mesh also a coordinate Function?

Comment on lines +348 to +353
for m in self._ad_forward_cache.meshes:
X = SpatialCoordinate(m)
# we can't take the CoordinateDerivative of an Action, so we have
# to invert this form compared to the expression above
dFdm = derivative(action(-F, adj_sol), X, TestFunction(m._ad_function_space()))
dFdm_adj_forms.append(dFdm)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain this comment a bit? It looks like we are in fact taking the derivative of an action wrt the coordinates here. Does it not resolve to a CoordinateDerivative?

Comment on lines +414 to +421
for m, dm in zip(self._ad_forward_cache.meshes,
self._ad_tangent_cache.mesh_tlms):
X = SpatialCoordinate(m)
d2Fdmdu = expand_derivatives(
derivative(dFdu_adj, X, dm)
)

d2Fdmdu_forms.append(d2Fdmdu)

@JHopeCollins JHopeCollins Jul 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite nit-picky, feel free to ignore if you don't agree.

The only difference in the body of this loop from the one above is the m_ = SpatialCoordinate(m) if (m is mesh) else m logic for what to take the derivative with respect to.

Would it be clearer to combine them into a single loop and have an if-block to set m and make it clear that the form calculation is identical? Or does it just make it too messy?

Comment on lines -34 to +36
solve(lhs(F) == rhs(F), u1)
lvp1 = LinearVariationalProblem(lhs(F), rhs(F), u1)
lvs1 = LinearVariationalSolver(lvp1)
lvs1.solve()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we parameterise these tests to run with either solve free function or a Solver?

@JHopeCollins JHopeCollins left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general this looks fine, thanks for working through all the cases. I have a few very very minor comments/questions.

I see that there are failures here related to the solve free function, and failures in the solve PR you think will be fixed by this PR. Which order would you like to merge in?
Merge this mesh PR into the main PR first, then update the solve PR and hopefully the test failures will be fixed, or the other way around?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

base:main Run this PR using a main (dev) build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants