Factor scalar maps through basis transformations - #286
Draft
pbrubeck wants to merge 8 commits into
Draft
Conversation
pbrubeck
force-pushed
the
pbrubeck/coffee-scalar-factor
branch
from
August 22, 2026 14:38
e7cc67c to
6a06910
Compare
pbrubeck
force-pushed
the
pbrubeck/coffee-scalar-factor
branch
from
August 22, 2026 15:39
6a06910 to
b4f9db3
Compare
pbrubeck
force-pushed
the
pbrubeck/coffee-scalar-factor
branch
3 times, most recently
from
August 23, 2026 11:21
3a9882c to
d8b8e3f
Compare
pbrubeck
force-pushed
the
pbrubeck/coffee-scalar-factor
branch
from
August 24, 2026 17:01
2eb8724 to
7aba6e7
Compare
optimise_monomial_sum runs inside the recursive sum_factorise, so calling the reduction placement there walked the same subtrees once per recursion level: 754 calls over 75,497 nodes for a Johnson-Mercier 3D matrix, which the pass never improves. Leave the traversal to the caller, memoise it on the node rather than its id, and skip costing an IndexSum that offers no indirect gather to factor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sharing the result between structurally equal subexpressions, as the other rewrites in this module do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tie in _plan_contraction ranks indices by their position in sum_indices, which spectral.py fixes to quadrature source order and _independent_contractions preserves into each subproblem. A plan therefore depends only on the contraction it is planning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pbrubeck
force-pushed
the
pbrubeck/coffee-scalar-factor
branch
from
August 26, 2026 11:42
7aba6e7 to
a096411
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #281. The complete FIAT stack is #282 -> #284 -> #281 ->
this PR, with the TSFC and Loopy integration in
firedrakeproject/firedrake#5362.
Motivation
A physically mapped finite element kernel first applies a sparse basis
transformation to a reference tabulation and then contracts the transformed
tabulation with quadrature data. The compiler should retain those as distinct
linear operations: reference tabulation -> physical basis -> quadrature
contraction.
The padded representation from #281 gives the sparse map a regular iteration
domain, but equal symbolic coefficients were still materialised as every entry
of a mutable matrix. In the 3D Guzman--Neilan matrix this appeared as 144
individual assignments to a 16-by-9 temporary before the map loop.
What changes
COFFEE gives additive linear maps a commutative key and extracts uniform
multiplicity into the scalar monomial. Thus a diagonal strain component
simplifies naturally from 0.5 (Gii + Gii) to Gii, while off-diagonal
components retain their multiplicity.
GEM factors a reduction through a repeated indirect lookup when its cost
model finds the rewrite profitable. For an indirect column c(i), linearity
gives
This computes a dense reference residual once per table row, then gathers
through the padded map.
MappedTabulation interns structurally equal symbolic coefficients. Two
read-only selector tables choose the reference column and one of the distinct
coefficients. The generated 3D Guzman--Neilan matrix now has no mutable
16-by-9 coefficient matrix; the distinct coefficient workspace has length
43.
Common factors are extracted only inside scalar basis-transformation
coefficients. Sums carrying free indices remain with the contraction
planner, avoiding the action-kernel regression caused by globally
redistributing scalar factors.
A contraction of an empty product explicitly contributes the cardinality of
its index space. This is the identity sum_i 1 = dim(i), which arises after a
selector makes one contraction component independent of an index.
There are no branches on element family, cell dimension, polynomial degree, or
form operator.
The quadrature weight times abs(detJ) is computed once per quadrature point.
The component weights are applied to one argument vector before the final
outer-product sum. Moving that common scalar outside the final matrix update
would replace O(ndof) scaling with O(ndof^2) scaling, so the generated form
retains the cheaper placement.
Benchmarks
Re-run against
mainfor every case below. The bilinear form isinner(u, v)*dx + inner(d(u), d(v))*dx1, withd=gradfor CG and Q,divfor RT, andcurlfor NCE,dx1forcing a separate quadrature degreefor the derivative term; the zany forms are
(hess u, hess v)for Argyris(
hess = sym(grad(grad(.)))),(eps u, eps v) + (div u, div v)forGuzman--Neilan 1st kind H1 (
eps = sym(grad(.))), and(sym(u), sym(v)) + (div u, div v)*dx1for Johnson--Mercier.tsfc (s)isthe TSFC compile time;
build (s)is the isolated cold-cache C build(compiler and linker only, no TSFC, no PyOP2);
kernel (s)is the isolatedper-call kernel time: the compiled kernel is called directly, bypassing
PyOP2's Python wrapper and the local-to-global indirection, and averaged over
calls made in one second.
array temps/entries/largestcount mutableloopy temporaries with a shape, their total entries, and the largest single
temporary.
mainis measured against the real, unpatched firedrake, since itcannot import a tsfc that expects the gem API this PR's stack adds; the PR
side is measured against firedrake
pbrubeck/zany-matvecHEAD, which addsexactly that API.
Bilinear form
Matrix-free action
CG, RT, Q and NCE are unaffected by this PR beyond what #284 and #281 already
contribute: RT falls 10.4%/13.8%/18.1% at degree 1/3/5 in 2D and
9.3%/19.7%/24.3% in 3D; CG only moves at degree 1 (-6.3%/-9.8%); Q and NCE
flops are unchanged, since this PR's coefficient-interning mechanism only
changes the scalar coefficients inside a basis-transformation matrix, and
Q/NCE have none. NCE's
tsfc (s)column is the one place this PR's stackcosts something on a case it does not touch: compile time rises 32-66%,
consistent with the new indirect-reduction-factoring cost comparison walking
NCE's much larger expression DAG without finding anything to rewrite.
Zany elements, bilinear form
Zany elements, matrix-free action
Flops keep rising over #281 in the matrix (Argyris +10.5%, Guzman--Neilan
+9.2% in 2D) except where interning removes real arithmetic: Guzman--Neilan
in 3D falls 10.6% and Johnson--Mercier falls 0.1%/8.5% in 2D/3D. AST lines
fall further still: Argyris to 203 lines (-52.9% from
main), Guzman--Neilanin 3D to 440 (-75.6%), Johnson--Mercier in 3D to 358 (-82.0%, the largest
drop of any case). Isolated build time improves in every matrix and action
case measured (12-68%). The action keeps costing arithmetic everywhere (up to
+98.0% for Johnson--Mercier in 3D), which tracks the padding-related
arithmetic from #281 rather than anything this PR adds: this PR's
interning only removes redundant coefficient work, and an action has no
argument axis left to share coefficients across.
Bernstein control
Degree-4 Bernstein Laplacians remain structurally identical to main.
Validation
benchmarks
AI assistance
OpenAI Codex was used for implementation, benchmarking, and drafting this
description. The human contributor remains responsible for understanding,
validating, and maintaining the changes.