Skip to content

Cancel contractions of the Jacobian with its inverse#68

Closed
pbrubeck wants to merge 2 commits into
mainfrom
pbrubeck/cancel-jacobian
Closed

Cancel contractions of the Jacobian with its inverse#68
pbrubeck wants to merge 2 commits into
mainfrom
pbrubeck/cancel-jacobian

Conversation

@pbrubeck

@pbrubeck pbrubeck commented Jul 9, 2026

Copy link
Copy Markdown

Description

Piola-mapped elements generate expressions of the form J * inv(J) that were not being simplified, because JacobianInverse was expanded into individual matrix entries too early in the form preprocessing.

This PR delays that expansion: during geometry lowering, Jacobian, JacobianInverse, and JacobianDeterminant are preserved as opaque terminals (which also lets is_cellwise_constant fold their derivatives to zero early on affine cells). A new algorithm module cancel_jacobian_products then simplifies the integrand in three traversals:

  1. JacobianCanceller: IndexSum(J[a, k] * K[k, b] * factors, k) -> Identity[a, b] * factors (and the K·J order, which is also valid for pseudo-inverses on immersed manifolds). Contractions are chased through nested IndexSums by interchanging the order of summation.
  2. IdentityEliminator: IndexSum(Identity[a, k] * factors, k) -> factors[k -> a], and folds fixed-index Identity entries into scalar constants. This also cleans up user-written Identity contractions.
  3. ReciprocalCanceller: cancels reciprocal factors within products, such as detJ**2 * (1/detJ)**2 -> 1.

The surviving Jacobian quantities are lowered as usual afterwards, so form compilers see no new terminal types. The behaviour is opt-in via a new compute_form_data kwarg do_cancel_jacobian_products (default off, so ffcx is unaffected).

For the Johnson-Mercier element, the div-div integrand shrinks from 9630 to 664 characters, becoming literally (1/detJ**2) J : rdiv(sigma) on affine cells. Together with companion GEM changes this gives up to -42% flops and -51% compile time (see companion PRs in firedrakeproject/fiat and firedrakeproject/firedrake).

🤖 Generated with Claude Code

Preserve Jacobian, JacobianInverse and JacobianDeterminant during
geometry lowering, and cancel J-Jinv contractions into Kronecker
deltas before the inverse is expanded into individual matrix entries.
Eliminate the resulting Identity tensors by contraction against the
remaining factors, and cancel reciprocal factors within products,
such as detJ**2 * (1/detJ)**2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pbrubeck pbrubeck force-pushed the pbrubeck/cancel-jacobian branch from 23cffd8 to 0651e63 Compare July 9, 2026 23:40
return result


class IndexSumSimplifier(MultiFunction):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Make this a DAGTraverser

return Power(base, as_ufl(exponent))


class ReciprocalCanceller(MultiFunction):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Make this a DAGTraverser

return result


def cancel_jacobian_products(o):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Add tests

IndexSumSimplifier (and its JacobianCanceller/IdentityEliminator
subclasses) and ReciprocalCanceller were plain MultiFunctions; convert
them to DAGTraverser, matching the convention used by the rest of the
newer algorithms modules (apply_derivatives.py,
apply_coefficient_split.py). This switches the driver function from
map_integrand_dags (the MultiFunction-compatible entry point) to
map_integrands, since a DAGTraverser is called directly rather than
through map_expr_dag.

Add test/test_cancel_jacobian_products.py, covering JacobianCanceller
and IdentityEliminator on both contraction orders (J.K and K.J),
ReciprocalCanceller on both a fully- and a partially-cancelling
reciprocal product, the full pass end-to-end on a div-div form over a
contravariant Piola-mapped (Raviart-Thomas) element (verifying both
the size reduction and that the opt-in flag defaults to off), and a
no-op check on a form with no Jacobian contractions to cancel.
@pbrubeck

Copy link
Copy Markdown
Author

Superseded by FEniCS#496, which targets origin (FEniCS/ufl) instead of this fork.

@pbrubeck pbrubeck closed this Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant