Skip to content

A sum of fractions that is a constant over one denominator simplifies to it - #2174

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
a-rational-function-is-offered-its-canonical-form
Oct 27, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
a-rational-function-is-offered-its-canonical-form

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Fixes #2137, and the first row of #2133, which is the same shape.

The defect: Simplify gathers fractions a pair or a sort level at a time, and left some sums apart that are a constant over one denominator. Transformation.RationalCanonicalization decides every row of #2137, and Simplify never asked it.

The change: the canonical form is offered as one more candidate, last, as the Boolean minimiser's form is. It is offered only where:

  • the expression is a rational function with a quotient in it,
  • its complexity is at most 64,
  • the integrator is not running.

The complexity metric decides, and a tie keeps the form the rules reached. The candidate is written with whole coefficients, 2/(4x^2 + 1) and not (1/2)/(x^2 + 1/4), through a flag on TryCanonicalize. The transformation keeps its monic form.

Simplify 2.6.0 this PR
x/(x+y) + y/(x+y) unchanged 1 provided not x + y = 0
1/(1 - (b/a)^2) - a^2/(a^2 - b^2) unchanged 0 provided not a ^ 3 - a * b ^ 2 = 0
x/c + (y*c - x*d)/(c^2*(z + d/c)) - (x*z + y)/(c*z + d) unchanged 0 provided not c * d + c ^ 2 * z = 0
R^2 - R^2*(a+b)*(1/a+1/b)/4 - R^2*(a-b)*(1/a-1/b)/4 (#2133) R ^ 2 + -1/2 * (R ^ 2 + R ^ 2) provided … 0 provided not a * b = 0

Each condition is the input's domain, written canonically: square-free, and without what the answer's own denominator already excludes. Three tests pinned a longer condition over the same points, (b - 1)(1 - b) for b - 1, and a b c x for a b c beside a quotient over x. Their expectations are updated with a comment saying why.

Why each restriction is there, measured against master 8a85f8ccf:

  • Last: offered first, the candidate won ties. That changed forms across the suite, and an acoth integral lost its answer. Offered last, both are back.
  • A walk that allocates nothing: asked through Nodes, the check cost SimplifyEasy 3.4% more allocation and failed the performance gate. Now it is byte-identical, 80,350 B on both.
  • Not inside the integrator: there, 1/(b cosh x + c sinh x + sqrt(b^2 - c^2))^4 took 4.8 s to a 737-character answer, against master's 1.1 s to 427. The canonicalization itself cost 2.9 ms in all. What cost the time was the route the gathered quotient sent the integrator down. With the integrator excluded, that integral is identical to master.

Checked:

  • The intbench chain: identical verdicts in all nine families, 0 moves, allocation within 0.05%.
  • Performance gate: passed on all 19 gated benchmarks.
  • Unit suite: 17,147 passed, 0 failed.
  • SimpSweep 10,463/10,463; CanonCheck matches its baseline; BoundCheck 0 disagreements; CasBench 120/121, unchanged; RootCheck 596/596.
  • SumOfFractionsOverOneDenominatorTest: 7 rows. Each checks the value, and that the answer is defined where the input is.
  • BREAKING-CHANGES entry added, measured on the 2.6.0 package.

Cost where it applies: a sum of fractions that is a rational function pays for the canonicalization even when its form loses. 1/a + 1/b + 1/c + 1/d allocates 952 KB against 743 KB, and x/(x+y) + y/(x+y) 1.80 MB against 1.72 MB. None of the gated benchmarks is such an expression, which is why the gate does not see it.

🤖 Generated with Claude Code

https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

… to it

Simplify gathers fractions a pair or a sort level at a time, and left some
sums apart that are a constant over one denominator: x/(x+y) + y/(x+y),
1/(1 - (b/a)^2) - a^2/(a^2 - b^2), and the nested-denominator row of #2137.
Transformation.RationalCanonicalization decides all of them and was never
asked. It is now offered as one more candidate, last, as the Boolean
minimiser's form is: where the expression is a rational function with a
quotient in it, of complexity at most 64, and not while the integrator is
running. The metric decides, and a tie keeps the form the rules reached.
The candidate is written with whole coefficients (2/(4x^2 + 1), not
(1/2)/(x^2 + 1/4)), through a flag on TryCanonicalize; the transformation
keeps its monic form.

Measured against master 8a85f8c:
  - offered first, it won ties and changed forms across the suite, and an
    acoth integral lost its answer; offered last, those are back;
  - asked through Nodes, the gate cost SimplifyEasy +3.4% allocation; a walk
    that allocates nothing makes it byte-identical (80,350 B both);
  - offered inside the integrator, 1/(b cosh x + c sinh x + sqrt(b^2-c^2))^4
    took 4.8 s to a 737-character answer against 1.1 s to 427; excluded
    there, it is identical, and the chain's nine families have the same
    verdicts with allocation within 0.05%.

The first row of #2133 is the same shape and is answered too. Three tests
pinned the longer condition of the same domain: (b - 1)(1 - b) for b - 1,
and a b c x for a b c beside a quotient over x.

Chain: verdicts identical in every family, 0 moves; performance gate
passed on all 19 gated benchmarks; unit suite 17,147 passed, 0 failed.
SimpSweep 10,463/10,463, CanonCheck matches its baseline, BoundCheck 0
disagreements, CasBench 120/121 unchanged, RootCheck 596/596.

Fixes #2137

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.43%. Comparing base (8a85f8c) to head (ce80455).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2174      +/-   ##
==========================================
- Coverage   86.43%   86.43%   -0.01%     
==========================================
  Files         295      295              
  Lines       64881    64905      +24     
  Branches    19084    19092       +8     
==========================================
+ Hits        56081    56100      +19     
- Misses       4770     4777       +7     
+ Partials     4030     4028       -2     
Flag Coverage Δ
cpp 37.77% <ø> (ø)
csharp 87.00% <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 Δ
.../Functions/Algebra/Polynomials/RationalFunction.cs 73.33% <100.00%> (+3.21%) ⬆️
...s/Continuous/Integration/Integration.Definition.cs 90.59% <100.00%> (+0.01%) ⬆️
...ouriMath/Functions/Simplification/Simplificator.cs 96.27% <100.00%> (+0.18%) ⬆️

... and 8 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 966f26a into master Oct 27, 2026
35 checks passed
@Rafael-SOWNet Rafael-SOWNet moved this from In review to Done in AngouriMath Oct 27, 2026
@github-project-automation github-project-automation Bot moved this from In review to Done in AngouriMath Oct 27, 2026
@Rafael-SOWNet
Rafael-SOWNet deleted the a-rational-function-is-offered-its-canonical-form branch October 28, 2026 10:28
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 does not combine fractions over a common denominator

1 participant