Skip to content

Sum factorisation on simplices - #5263

Open
pbrubeck wants to merge 25 commits into
mainfrom
pbrubeck/simplex-sum-fact
Open

Sum factorisation on simplices#5263
pbrubeck wants to merge 25 commits into
mainfrom
pbrubeck/simplex-sum-fact

Conversation

@pbrubeck

@pbrubeck pbrubeck commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Depends on firedrakeproject/fiat#262.

Summary

  • gem.JaggedIndex represents simplex lattice indices with bounds depending on parent indices. TSFC lowers these to parameterized Loopy domains.
  • gem.FlattenedTensor exposes a jagged lattice through a flat DoF index while retaining its tensor-product structure.
  • gem.sparse_matrix represents sparse basis recombination.
  • DuffyElement.duffy_evaluation() constructs sum-factorized Legendre and IntegratedLegendre tabulations; get_sparse_coeffs() provides DG ordering or CG recombination data.
  • TSFC uses Duffy tabulations for coefficient and argument translation and selects collapsed quadrature for Duffy elements.

gem.optimise now:

  • rewrites flat contractions as jagged lattice contractions with unflatten() and unflatten_returns();
  • exposes both argument lattices before expanding separable derivative sums in bilinear forms;
  • refactorizes eligible sparse-transform expressions as monomial sums and applies the same staged COFFEE sum factorization used by TSFC;
  • retains the established local-distribution path for dense or residual argument transforms;
  • cancels sparse row/column deltas during contraction; and
  • lowers remaining FlattenedTensors to gather tables with replace_flattened().

For a three-dimensional collapsed basis, the reference derivative has 3 + 2 + 1 one-dimensional derivative paths. A bilinear Laplacian combines two such arguments and three physical gradient components. The previous unflatten_returns() expanded the first argument before exposing the second, cloning the second jagged lattice once per derivative term. The new ordering exposes both lattices jointly, then groups equal monomials and factorizes one contraction index at a time.

The final output loops remain genuinely jagged. In particular, the innermost tetrahedral domain has a bound of the form 0 <= k <= p - i - j; it is not replaced by a rectangular loop with masking.

Bilinear Laplacian code-generation benchmarks

These are tetrahedral Bernstein kernels generated in spectral mode. Structural counters and FLOPs are exact for the generated Loopy domains; times are representative local instrumented compiler runs.

Degree / scheme Compile unflatten_returns FLOPs Instructions Temporaries C for loops
3 collapsed, before 1.181 s 0.388 s 3,715,953 1,103 737 1,071
3 collapsed, optimized 0.254 s 0.044 s 439,113 220 189 92
3 Laplacian action reference 0.137 s 0.012 s 10,305 110 96 30
3 mass bilinear reference 0.076 s 0.006 s 43,768 23 26 39
10 canonical 0.489 s 0.001 s 495,076,099 52 54 4
10 collapsed, optimized 0.399 s 0.046 s 433,160,451 220 189 92

Degree 10 is the tested crossover where the sum-factorized bilinear Laplacian beats canonical quadrature in both generated FLOPs and compiler time. A regression test also checks the compact instruction/temporary/loop counts and requires a two-parent parameterized domain, which preserves the tetrahedral JaggedIndex loop.

One-element execution benchmarks

FLOPs use the exact generated jagged loop domains. Times are median assembly times after warm-up.

Case Degree Canonical FLOPs Collapsed FLOPs FLOP reduction Canonical time Collapsed time Speedup
CG Laplacian action (2D) 16 317197 215681 32.0% 369.0 us 230.0 us 1.60x
Bernstein Laplacian action (2D) 16 317197 144331 54.5% 486.2 us 118.5 us 4.10x
CG Laplacian action (3D) 15 33162804 28707004 13.4% 77.65 ms 4.955 ms 15.67x
Bernstein Laplacian action (3D) 15 33162804 4057153 87.8% 31.58 ms 418.7 us 75.43x
DG mass matrix (2D) 12 2814534 247178 91.2% 501.1 us 292.9 us 1.71x
DG mass matrix (3D) 7 14807576 302041 98.0% 9.885 ms 0.440 ms 22.47x

AI assistance: OpenAI Codex was used for code analysis, implementation support, testing, and benchmarking.

pbrubeck and others added 2 commits July 19, 2026 00:35
Extends tsfc/fem.py's translate_coefficient and translate_argument to
take a sum-factorized (Duffy/lattice) tabulation path for simplicial DG
Legendre elements under dx(scheme="collapsed"), targeting O(p^{d+1})
flops for the matrix-free residual instead of the dense O(p^{2d}).

The lattice multiindex from Legendre.duffy_evaluation is gathered
against coefficients and scattered back to the flat dof index through
FIAT's existing Morton dof numbering (new morton_forward_table /
morton_inverse_table in FIAT.expansions), entirely inside fem.py:
element.index_shape and argument_multiindices stay flat, so no
driver.py or kernel_interface changes are needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread tsfc/fem.py Outdated
Comment thread tsfc/fem.py Outdated
Comment thread tsfc/fem.py Outdated
Comment thread tsfc/fem.py Outdated
Comment thread tsfc/fem.py Outdated

@pbrubeck pbrubeck left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TSFC codegen diff should be as tight as possible. Add the new code in a separate finat submodule.

Comment thread tsfc/fem.py Outdated
Comment thread tsfc/fem.py Outdated
pbrubeck and others added 2 commits July 21, 2026 00:34
…rred

Legendre's dof order is now lattice-lexicographic (permuted in FIAT),
which simplified finat/duffy.py's index arithmetic but did not
eliminate the VariableIndex gather/scatter: get_indices() must stay a
flat index because it can't distinguish a cell-interior kernel from a
facet-integral kernel, and facet tabulation always uses the dense,
flat-(ndof,) FIAT path. Fully eliminating the gather/scatter would
need a bespoke jagged gem.FlexiblyIndexed view in kernel_interface/
common.py's prepare_arguments/prepare_coefficient, shared code every
Firedrake kernel depends on -- deliberately deferred as a separate,
higher-risk follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Temporarily pins firedrake-fiat to pbrubeck/simplex-sum-factor so CI
exercises this branch's paired FIAT changes (dof-order permutation,
gem.Delta fix). Revert to @main once the FIAT PR merges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread tests/firedrake/regression/test_quadrature.py Outdated
Comment thread tests/firedrake/regression/test_quadrature.py Outdated
Comment thread tests/firedrake/regression/test_quadrature.py Outdated
translate_coefficient no longer dispatches on isinstance(element,
DuffyElement): finat.duffy.DuffyElement.basis_evaluation now returns an
already flat-dof-indexed tabulation, so the generic dense contraction
path applies uniformly and recovers the same sum-factorized complexity
without any special-casing. This also makes the Duffy fast path compose
with Vector/TensorElement, which previously fell through to the slow
path since TensorFiniteElement is never itself a DuffyElement instance.

test_duffy_scatter_and_contract updated to contract generically instead
of calling the now-removed duffy_contraction.

test_collapsed_quadrature_sum_factorisation now compares against
dx(scheme="canonical") instead of the default scheme: same collapsed
Gauss-Jacobi points/weights as dx(scheme="collapsed"), but tabulated via
the dense FIAT path, isolating the comparison to the sum-factorized
tabulation itself rather than to a difference in quadrature rule.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread tests/tsfc/test_codegen.py Outdated

@rckirby rckirby left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This just checks that the code gives a correct answer. Do we have a way of checking whether the algorithm has the right complexity?

@pbrubeck

Copy link
Copy Markdown
Contributor Author

This just checks that the code gives a correct answer. Do we have a way of checking whether the algorithm has the right complexity?

This PR adds both correctness and complexity tests. Complexity in flops is not enough, I also had to enforce tests on temporaries

@rckirby

rckirby commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

This PR adds both correctness and complexity tests. Complexity in flops is not enough, I also had to enforce tests on temporaries

I see those tests, was thinking about FIAT. We should also test out Bernstein in the one-element benchmarks -- it doesn't have the indirection internally that modified C^0 expansions have but can be directly (after Duffy) sum-factored.

@pbrubeck
pbrubeck force-pushed the pbrubeck/simplex-sum-fact branch from 7cfae19 to 6e30e23 Compare July 24, 2026 10:27
Comment thread tests/firedrake/regression/test_helmholtz_bernstein.py Outdated
Comment thread DESIGN.md Outdated
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.

2 participants