Skip to content

A rational integral over the roots of its denominator - #1637

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
a-rational-integral-over-the-roots-of-its-denominator
Sep 30, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
a-rational-integral-over-the-roots-of-its-denominator

Conversation

@Rafael-SOWNet

@Rafael-SOWNet Rafael-SOWNet commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

The rational integrator declined a rational function whose residues lie in a field of degree three or more. 1/(x^3 + x + 1) was left unevaluated, and so was #1285's sqrt(x)/(1 + x + x^4), which is 2u^2/(1 + u^2 + u^8) under u = sqrt(x). Their logarithms are written now as a sum over the poles they belong to, the form #1285 asked for, with the node from #1624:

sum(A(r)/D'(r) ln(x - r), r in { r : E(r) = 0 })

  • Which poles. The poles whose residues are roots of the Rothstein–Trager resultant R are the common roots of D and D'^n R(A/D'), so E is their gcd over the rationals. No arithmetic in the residues' field is needed. Residues that are rational or roots of a quadratic keep the logarithms and arctangents they had.
  • Only as a last resort. Asked beside the other rules, the sum answered each of Jeffrey's three terms of (-1 + 4 cos(x) + 5 cos(x)^2)/(-1 - 4 cos(x) - 3 cos(x)^2 + 4 cos(x)^3) with a sum over the roots of a sextic, where the whole is 2 arctan(tan(x/2)^3 - 2 tan(x/2)). So the rule declines as before and notes that a sum would answer. Only when the whole question comes back unanswered, with that noted, is it asked again with sums allowed, under a memo of its own. The answer found then is remembered for the question.
  • Checked like any other answer. The integrator checks an answer in double intervals and then in decimals, and neither read the sum node, so every such answer would have been declined by its own check. PreciseEvaluation encloses each root now. It takes Durand–Kerner's approximations and widens each by its Weierstrass correction, n |w_k| about z_k. Where those rectangles are apart, each holds exactly one root (Braess and Hadeler), and the summand is worked out on each rectangle.
  • Still declined: the same kind of denominator with a symbol among its coefficients, which the sum is not written for.

Measured

Was (2.5.0) Is
"1/(x^3 + x + 1)".Integrate("x") integral(1 / (x ^ 3 + x + 1), x) sum(1 / (3 * r ^ 2 + 1) * ln(x - r), r in { r : r ^ 3 + r + 1 = 0 }) + C
"sqrt(x)/(1 + x + x^4)".Integrate("x") integral(sqrt(x) / (1 + x + x ^ 4), x) sum(2 * r ^ 2 / (8 * r ^ 7 + 2 * r) * ln(x ^ (1/2) - r), r in { r : r ^ 8 + r ^ 2 + 1 = 0 }) + C
"1/(1 - x^4 + x^8)".Integrate("x") integral(1 / (1 - x ^ 4 + x ^ 8), x) sum(1 / (8 * r ^ 7 + (-4) * r ^ 3) * ln(x - r), r in { r : r ^ 8 + -r ^ 4 + 1 = 0 }) + C
"1/(x^4 + a*x + 1)".Integrate("x") integral(1 / (x ^ 4 + a * x + 1), x) the same

Jeffrey's integrand is 2 arctan(tan(x/2)^3 - 2 tan(x/2)) on master and here. Every answer above differentiates back to its integrand at six points.

  • Rubi, master at ad05a79b and this change on it, run side by side, 0 wrong everywhere:
    • The independent suites: 1753 → 1756 of 1814. The gains are Bondarenko 22 and 23, and Hearn 66, 1/(1 - x^4 + x^8).
    • Family 1 at ten problems a file: 295 → 301 of 377. The gains are 1/(x^2 (1 - x^3 + x^6)), four quotients over 1 - x^4 + x^8 and 1 - 5x^4 + x^8, and 1.3.1 Update actions #361.
    • Families 2–7: 663 of 722 on both.
  • Run alone, the nine gains are 0 of 9 on master and 9 of 9 here. The tenth problem that moved, 1.1.4.3 we need more limit solvers #231, is 17.9 s unevaluated on both. Its master timeout came from the load of the side-by-side run.
  • A question still declined costs what it did. The problems unevaluated in both arms took:
    • 14.2 s against 13.4 s in the independent suites;
    • 65.6 s against 64.7 s in families 2–7;
    • 89.9 s against 92.3 s in the family 1 sample.
  • The unit tests pass: 14,221, none failed (net10.0). The native AOT publish of the C++ wrapper builds with no trim or AOT warnings.
  • The performance gate passes on 34c548a8, whose library code is this head's. Allocation matches the baseline on all 19 gated benchmarks.

Tests whose pins moved

BinomialDenominatorIntegralTest, PartialFractionsTest and RationalIntegralsTest pinned four denominators as declined: 1/(x^3 + x + 1), 1/(x^3 + x^2 + x + 2), 1/(x^4 + x + 1) and 1/(x^4 + x^3 + 1). Each is now the sum, checked by differentiating it back. The boundary pinned in their place is 1/(x^4 + a x + 1) and 1/(x^3 + x + a).

DecliningStaysCheap keeps its ten-second bound, renamed FindingThatNothingSplitsStaysCheap. The second pass starts only once the first has declined, so the decline it guards is still inside the bound.

Closes #1285.

🤖 Generated with Claude Code

The Rothstein-Trager resultant declined a rational function whose residues lie in a field of degree
above two: 1/(x^3 + x + 1) and #1285's sqrt(x)/(1 + x + x^4), 2u^2/(1 + u^2 + u^8) under
u = sqrt(x), were left unevaluated. Their logarithms are written now as a sum over the roots of the
factor of the denominator they belong to, sum(A(r)/D'(r) ln(x - r), r in { r : E(r) = 0 }), the
form #1285 asked for, with the node #1624 added. The poles whose residues are roots of R are the
common roots of D and D'^n R(A/D'), so E is their gcd over the rationals, and no arithmetic in the
residues' field is needed.

Only as a last resort. Asked beside the other rules, the sum answered each of Jeffrey's three terms
of (-1 + 4 cos(x) + 5 cos(x)^2)/(-1 - 4 cos(x) - 3 cos(x)^2 + 4 cos(x)^3) with a sum over the roots
of a sextic, where the whole is 2 arctan(tan(x/2)^3 - 2 tan(x/2)). So the rule declines as before
and notes that a sum would answer, and only where the question comes back unanswered and that was
noted is it asked again with sums allowed, under a memo of its own; the answer found then is
remembered for the question.

The integrator checks an answer in double intervals and then in decimals, and neither read the sum
node, so every such answer would have been declined by its own check. PreciseEvaluation encloses
each root now: Durand-Kerner's approximations, each widened by its Weierstrass correction in
intervals, n |w_k| about z_k, which holds exactly one root where the rectangles are apart
(Braess and Hadeler), and the summand worked out on each rectangle.

Measured on the Rubi corpus, master at ad05a79 and this change on it, run side by side: the
independent suites 1753 -> 1756 of 1814 (Bondarenko 22 and 23, and Hearn 66, 1/(1 - x^4 + x^8));
family 1 at ten problems a file, 295 -> 301 of 377 (1/(x^2 (1 - x^3 + x^6)), (1 + x^4)/(1 - x^4 +
x^8), (1 + x^4)/(1 - 5x^4 + x^8), (-1 + 2x^4 + sqrt(3))/(1 - x^4 + x^8), (1 - x^4)/(1 - x^4 + x^8)
and x^3 (5 + x + 3x^2 + 2x^3)/(2 + x + 5x^2 + x^3 + 2x^4)); families 2 to 7 at the usual sample, 663
of 722 on both. 0 wrong everywhere. Run alone, those nine are 0 of 9 on master and 9 of 9 here, and
the tenth problem that moved, 1.1.4.3 #231, is 17.9 s unevaluated on both: its master timeout was
the load of the side-by-side run. A question still declined costs what it did: the problems
unevaluated in both arms took 14.2 s against 13.4 s in the independent suites, 65.6 s against 64.7 s
in families 2 to 7 and 89.9 s against 92.3 s in the family 1 sample.

The performance gate passes on 34c548a8, whose library code is this commit's: allocation is what
the baseline says on all 19 gated benchmarks.

RothsteinTragerTest: the cubic, quartic and quintic rows that were pinned as declined are sums now,
and #1285's integral in PowerSubstitutionIntegralTest; that test's declined witnesses are the
elliptic x^2/sqrt(x^4 + x + 1) and x^2/sqrt(x^4 + x^3 + 1), which nothing answers.
BinomialDenominatorIntegralTest, PartialFractionsTest and RationalIntegralsTest pinned
1/(x^3 + x + 1), 1/(x^3 + x^2 + x + 2), 1/(x^4 + x + 1) and 1/(x^4 + x^3 + 1) as declined: each is
the sum now, checked by differentiating back, and the boundary pinned in their place is the same
kind of denominator with a symbol among its coefficients, which the sum is not written for.
DecliningStaysCheap keeps its ten-second bound as FindingThatNothingSplitsStaysCheap: the second
pass starts only once the first has declined, so the decline it guards is still inside the bound.

Part of #1285.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Rafael-SOWNet Rafael-SOWNet added this to the 2.6.0 milestone Sep 30, 2026
@Rafael-SOWNet
Rafael-SOWNet merged commit 29af2fd into master Sep 30, 2026
31 checks passed
Rafael-SOWNet added a commit that referenced this pull request Sep 30, 2026
…r across the bar (#1638)

None of the nine special functions was a candidate for the substitution u = g(x), so an integrand
that is a power of one beside its derivative had no route: e^(c - b^2 x^2) erf(b x)^n,
Ei(b x) e^(b x)/x and Si(b x) sin(b x)/x were left unevaluated. Each is a candidate now, as the
logarithm is, and its elementary derivative is the differential: e^(c - b^2 x^2) erf(b x) is
sqrt(pi) e^c/(2b) u du under u = erf(b x).

A constant in the exponent still stopped it: divided by du/dx, e^(c - b^2 x^2) over the
e^(-(b x)^2) of the derivative is e^c, and the quotient is simplified only a level, so the x in it
was left and the candidate refused, where e^(-b^2 x^2) erf(b x) was answered. The divisor's factors
are spread to the power -1 now, the powers of one base gathered, and the result simplified a level,
which is where e^(c - (b x)^2 + (b x)^2) becomes e^c.

Both only where they can answer. A special function is offered only where its derivative can be
the differential: an exponential of a quadratic for the error functions, a logarithm below the bar
for li, and something in x below the bar for the others, whose derivatives divide by their
argument. Offered beside anything, x cosh(a + b x) Shi(a + b x) went from a second to ten,
simplifying quotients of exponentials that were never going to lose their x. And the powers are
gathered only for a special-function candidate: done for every candidate that left an x, the
exponentials of 1/((c + d x)^3 (a + a tanh(e + f x))) were gathered and simplified for each one,
and seven declines of a third of a second became timeouts. The nine are listed once, in
IsASpecialFunction, where the solver listed them three times.

Rubi's family 8, #1501's tranche of 420: 258 -> 296 solved, 0 wrong, against master at ad05a79.

Nothing without a special function moves. The independent suites are 1753 of 1814 on both; families
1 to 7 at five problems a file are 809 and 811 of 912 run side by side, and the two that moved,
3.2.1 #12 and 6.7.1 #180, are answered alone by both, in 18 and 22 s: master's timeouts there were
the load. 0 wrong everywhere.

The unit tests pass on master with #1637, 14,230, and the performance gate passes on fd1a4416,
which is this change on ad05a79: allocation is what the baseline says on all 19 gated benchmarks.

SpecialFunctionSubstitutionTest has nine rows from Rubi's 8.1, 8.3 and 8.4, each differentiated
back with its parameters pinned.

Part of #1501.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

A sum over the roots of a polynomial, so that a rational function with an irreducible denominator can be integrated

1 participant