Skip to content

A long chain of sums is simplified from the bottom up, not by a recursion as deep as the chain - #2167

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
like-terms-are-summed-without-recursing-once-per-term
Oct 27, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
like-terms-are-summed-without-recursing-once-per-term

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Fixes #2166.

The defect: a sum simplifies its left operand first, and that operand's left operand, and so on. So a chain of n sums nested to the left was simplified by a recursion n deep. A sum built one term at a time has that shape: CollectLikeTerms builds one, and Simplify's search hands the collected chain on to later candidates.

The integral of (q + p x)/(c + b x + a x^2)^3, with its symbols pinned, reached about 900 levels:

The change: Sumf and Minusf first simplify every 32nd node of a left spine that is at least 64 deep, deepest first. Every node's simplification is cached, so each of those recursions stops at the checkpoint below it. The same nodes are simplified, each once, in a different order. An ordinary sum pays only for a walk of a few nodes.

Measured on a thread of a given stack size:

case master this PR
cubic row, Release, 384 KB overflow, 909 repeats passes
cubic row, Release, 256 KB overflow passes
cubic row, Debug, 1 MB overflow, 654 repeats passes
(a_1+..+a_30)(b_1+..+b_30) expanded, Release, 256 KB overflows at 400 terms 900 and 1,600 pass

The next thing to overflow on the cubic row, at 192 KB in Release, is CostModel.ChildrenCost, 872 deep. That is a different recursion, and it has 2.7× headroom on macOS.

Test: LongSumStackDepthTest expands the 900-term product on a 256 KB thread.

Suite and performance:

  • The unit suite passes: 17,128 passed, 0 failed.
  • Against master, on one machine, alternating the two builds:
master this PR
InnerSimplified on five ordinary sums, bytes per round 23,356 23,356
the same, ms per 200,000 rounds (mean of 3) 2,525 2,557
Simplify on ten expressions, bytes per round 16,745,849 16,745,849

The time difference is +1.3%, inside the 5% spread between runs of a single build.

No answer changes, so there is no BREAKING-CHANGES entry.

🤖 Generated with Claude Code

https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

…sion as deep as the chain

A sum simplifies its left operand first, and that operand's left operand,
so a chain of n sums nested to the left was simplified by a recursion n
deep. A sum built one term at a time is that shape: CollectLikeTerms
builds one, and Simplify's search hands the collected chain on. The
integral of (q + p x)/(c + b x + a x^2)^3 with its symbols pinned reached
about 900, which overflowed C# Test's Debug build on a 1 MB stack on most
Windows runs since 2026-09-15 (#2165 moved that leg to Release), and
needed more than 384 KB in Release, where macOS gives 512 KB.

Sumf and Minusf now simplify every 32nd node of a left spine at least 64
deep first, deepest first. Every node's simplification is cached, so each
of those recursions stops at the checkpoint below it. The same nodes are
simplified, each once, in a different order, and an ordinary sum pays for a
walk of a few nodes and nothing else.

On a thread of a given stack size:
  cubic row, Release   384 KB overflow (909 repeats) -> 256 KB passes
  cubic row, Debug     1 MB overflow (654 repeats)   -> 1 MB passes
  (a_1+..+a_30)(b_1+..+b_30) expanded, Release, 256 KB:
                       400 terms overflow             -> 900 and 1,600 pass
What overflows next on the cubic row, at 192 KB in Release, is
CostModel.ChildrenCost, 872 deep: a different recursion, with 2.7x
headroom on macOS.

Unit suite 17,128 passed, 0 failed. Against master on one machine:
allocation is identical (23,356 B per round of InnerSimplified on five
ordinary sums; 15.79 MB per round of Simplify on ten expressions), and
time is within noise (InnerSimplified 2,525 ms vs 2,557 ms, mean of three
200,000-round runs whose spread in one arm is 5%).

Fixes #2166

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
@Rafael-SOWNet Rafael-SOWNet added this to the 2.7.0 milestone Oct 27, 2026
@Rafael-SOWNet Rafael-SOWNet moved this from Todo to In review in AngouriMath Oct 27, 2026
@codecov

codecov Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.41%. Comparing base (90ac975) to head (c25df29).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #2167   +/-   ##
=======================================
  Coverage   86.41%   86.41%           
=======================================
  Files         295      295           
  Lines       64790    64806   +16     
  Branches    19053    19059    +6     
=======================================
+ Hits        55987    56005   +18     
+ Misses       4773     4770    -3     
- Partials     4030     4031    +1     
Flag Coverage Δ
cpp 37.77% <ø> (ø)
csharp 86.99% <100.00%> (+<0.01%) ⬆️
fsharp 32.97% <ø> (ø)
interactive 40.72% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...nuous/Evaluation.Continuous.Arithmetics.Classes.cs 87.48% <100.00%> (+0.32%) ⬆️

... and 12 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Rafael-SOWNet
Rafael-SOWNet merged commit e828414 into master Oct 27, 2026
35 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in AngouriMath Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Simplify recurses once per nested sum, about 900 deep on one integral, and Release needs more than 384 KB for it

1 participant