Skip to content

perf: split the harmonic coefficient matrix into its independent blocks - #82

Merged
oameye merged 1 commit into
fix/version-quotefrom
perf/symbolics-block-solve
Jul 28, 2026
Merged

perf: split the harmonic coefficient matrix into its independent blocks#82
oameye merged 1 commit into
fix/version-quotefrom
perf/symbolics-block-solve

Conversation

@oameye

@oameye oameye commented Jul 28, 2026

Copy link
Copy Markdown
Member

rearrange! solves the linear system relating the harmonic equations to the derivatives of the harmonic variables. For an n-harmonic ansatz that matrix is n uncoupled 2x2 blocks, but it was eliminated whole. A Bareiss entry carries the determinant of everything eliminated so far, so by pivot k the polynomial arithmetic runs on k blocks' worth of determinant, and every solution comes out over the determinant of the full matrix instead of the single block determinant that survives.

fraction_free_linear_solve now finds the connected components of the bipartite row-column graph (union-find over the structural nonzeros) and eliminates each on its own. An unbalanced component is structurally singular and is handed to the full solve to reject, so nothing that used to throw now silently succeeds.

Over six representative systems rearrange_standard drops from 134.2 MiB to 10.1 MiB and from 0.114 s to 0.028 s. The worst case, two coupled oscillators at two harmonics, goes from 80.8 MiB to 3.1 MiB.

The solutions are deliberately not byte-identical to before: each now carries only its own block's determinant, so it is smaller. Verified by substituting them back into the source equations at random numeric points; the largest residual across all six systems is 9.4e-15.

Alongside this:

  • Sums and products are rebuilt with SymbolicUtils' n-ary add_worker/mul_worker rather than folded with sum/prod/+=, which is quadratic in the number of terms. A solved Bareiss entry has thousands of monomials.
  • simplify_exp_products missed exp(a)*exp(a): a product stores a repeated factor as a power, so it arrives as the single factor exp(a)^2 and the isexp test skipped it.
  • collapse_pythagorean committed to the first squared trig factor it found in a summand, leaving cos(3t)^2*sin(t)^2*a + sin(3t)^2*sin(t)^2*a uncollapsed. Every way of splitting a summand is now a candidate partner.
  • is_rearranged asked whether a derivative appears on the left-hand side by rendering both sides to strings and calling occursin. It now walks the tree.
  • trig_reduce called expand_all where expand suffices: the extra Postwalk(expand_exp_power) was the single most expensive step of the averaging, and the simplify_exp_products on the next line already normalises exp(a)^n at every node it reaches.
  • declare_variable built its variable with @eval and bound the result inside QuestBase. Nothing read that binding, and evaluating into a closed module makes the package impossible to precompile with a workload.

`rearrange!` solves the linear system relating the harmonic equations to the
derivatives of the harmonic variables. For an n-harmonic ansatz that matrix is n
uncoupled 2x2 blocks, but it was eliminated whole. A Bareiss entry carries the
determinant of everything eliminated so far, so by pivot k the polynomial
arithmetic runs on k blocks' worth of determinant and every solution comes out
over the determinant of the full matrix instead of the single block determinant
that survives.

`fraction_free_linear_solve` now finds the connected components of the bipartite
row-column graph (union-find over the structural nonzeros) and eliminates each on
its own. An unbalanced component is structurally singular and is handed to the
full solve to reject, so nothing that used to throw now silently succeeds.

Over six representative systems `rearrange_standard` drops from 134.2 MiB to
10.1 MiB and from 0.114 s to 0.028 s. The worst case, two coupled oscillators at
two harmonics, goes from 80.8 MiB to 3.1 MiB.

The solutions are deliberately not byte-identical to before: each now carries
only its own block's determinant, so it is smaller. Verified by substituting them
back into the source equations at random numeric points; the largest residual
across all six systems is 9.4e-15.

Alongside this:

- Sums and products are rebuilt with SymbolicUtils' n-ary `add_worker`/
  `mul_worker` rather than folded with `sum`/`prod`/`+=`, which is O(n^2) in the
  number of terms. A solved Bareiss entry has thousands of monomials.
- `simplify_exp_products` missed `exp(a)*exp(a)`: a product stores a repeated
  factor as a power, so it arrives as the single factor `exp(a)^2` and the
  `isexp` test skipped it.
- `collapse_pythagorean` committed to the first squared trig factor it found in a
  summand, leaving `cos(3t)^2*sin(t)^2*a + sin(3t)^2*sin(t)^2*a` uncollapsed.
  Every way of splitting a summand is now a candidate partner.
- `is_rearranged` asked whether a derivative appears on the left-hand side by
  rendering both sides to strings and calling `occursin`. It now walks the tree.
- `trig_reduce` called `expand_all` where `expand` suffices: the extra
  `Postwalk(expand_exp_power)` was the single most expensive step of the
  averaging, and the `simplify_exp_products` on the next line already normalises
  `exp(a)^n` at every node it reaches.
- `declare_variable` built its variable with `@eval` and bound the result inside
  QuestBase. Nothing read that binding, and evaluating into a closed module makes
  the package impossible to precompile with a workload.
@oameye
oameye merged commit dc97cfa into fix/version-quote Jul 28, 2026
2 checks passed
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