Skip to content

Commit c3b136d

Browse files
Stop a term-by-term integration at the first declined term, and simplify a power of a symbolic cubic under the exponential substitution at level -2 (#2016)
Two causes of the heavy rows of Rubi's 6.1.7. SolveBySplittingSum's helper Integrated was a Select(...).Aggregate(...), which integrated every term even after one had declined; IntegratedTermByTerm and the partial fractions' && chains already stop. It is now a loop that returns at the first null, summing in the same order. 6.1.7:196 5.5 -> 1.3 GB from this alone, and five rows master does not answer within two to three minutes are answered: 6.7.1:703 and 735 in 0.2 s, 6.2.7:121 and 122, and 2.3:714 (-e^Q/Q + Ei(Q)) in 18 s. Under u = e^(c + d x), csch^m (a + b sinh^3)^p becomes a quotient holding (a (2u)^3 + b (u^2 - 1)^3)^p, and SolveByExponentialSubstitution's combined.Simplify() spent 2.2-7.7 GB on the two candidates offered after the search: Expand().Simplify(-2) and RuleBasedFactorization(2).Simplify(-2). For a whole power >= 2 of a sum that is a polynomial in u of degree >= 3 with a symbolic coefficient and a nonzero constant term, it now simplifies at level -2, the same search without them; not where the monomials in u above and below the bar have equal degree (6.1.7:187, where the factored form wins), and not without the constant term (6.7.1:854, 3,485 characters, 178,682 that way). The 37 heavy rows of 6.1.7 and 6.4.2: answers identical, 81.7 -> 21.7 GB. Chain against master 08a3c49: pocket of 3,599 rows (6.1.x and 6.4.x whole, the rest of family 6 every 3rd, every 30th elsewhere), 3,555 -> 3,561 solved, allocation over the rows solved on both 567.5 -> 469.8 GB; samples unchanged; alone, 1.1.2.4:1198 answered as well; no row's allocation grew past 1.3x (Welz 201 and 1.1.1.3:2861, flagged in the samples, are identical in allocation and answer cold). Part of #233. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent dc0463c commit c3b136d

3 files changed

Lines changed: 194 additions & 6 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,21 @@ arctangent, or an inverse hyperbolic tangent, of the tangent of the argument or
12941294
| `"1/(a+b*cosh(x)^5)".ToEntity().Integrate("x")` | `integral(...)` | 3,607 characters |
12951295
| `"1/(a-b*cosh(x)^8)".ToEntity().Integrate("x")` | `integral(...)` | 1,441 characters |
12961296

1297+
### A sum split into its terms is given up at the first term declined
1298+
1299+
**Answers where there were none.** `1/(a sech(x) + b tanh(x))^2`, `1/(a coth(x) + b csch(x))^2` and
1300+
`1/(1 - cosh(x)^8)`, Rubi's 6.7.1 and 6.2.7, were declined, and before this change ran past two minutes. Integrating
1301+
a sum term by term asked every term after one had been declined, and declining those came before the rules that
1302+
answer these integrands; the split now ends at the first term declined. More slowly, in ten to twenty seconds,
1303+
`1/(1 - cosh(x)^6)` and `e^(a + b x + c x^2) (b + 2 c x)/(a + b x + c x^2)^2` are answered too
1304+
([#233](https://github.com/asc-community/AngouriMath/issues/233)).
1305+
1306+
| Input | Was (2.5.0) | Now |
1307+
|---|---|---|
1308+
| `"1/(a*sech(x)+b*tanh(x))^2".ToEntity().Integrate("x")` | `integral(...)` | 1,836 characters |
1309+
| `"1/(a*coth(x)+b*csch(x))^2".ToEntity().Integrate("x")` | `integral(...)` | 1,831 characters |
1310+
| `"1/(1-cosh(x)^8)".ToEntity().Integrate("x")` | `integral(...)` | 1,232 characters |
1311+
12971312
### A whole power of an inverse function of `c + d x^2` is integrated by its recurrence
12981313

12991314
**Answers where there were none.** `(a + b acos(1 + d x^2))^4` was declined, and so were twenty-five more of Rubi's

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,21 @@ internal static class IndefiniteIntegralSolver
138138
return ((top * group.Radicals).InnerSimplified / bottom).InnerSimplified;
139139
}).ToList());
140140

141+
// Term by term, and over at the first term declined: the sum is answered only if every
142+
// term is, and the terms after one declined were each asked to no purpose. Rubi's 6.1.7
143+
// `csch(c + d x)^6 (a + b sinh(c + d x)^3)^3` is expanded into terms one of which is
144+
// declined, and the terms after it cost 4.2 GB of the 5.5 the integral took.
141145
Entity? Integrated(List<Entity> terms)
142-
=> terms.Select(e => Integration.ComputeAsAQuestionOfItsOwn(e, x, integrateByParts)).Aggregate((e1, e2) => (e1, e2) switch {
143-
(null, _) or (_, null) => null,
144-
(var int1, var int2) => int1 + int2
145-
});
146+
{
147+
Entity? sum = null;
148+
foreach (var term in terms)
149+
{
150+
if (Integration.ComputeAsAQuestionOfItsOwn(term, x, integrateByParts) is not { } integrated)
151+
return null;
152+
sum = sum is null ? integrated : sum + integrated;
153+
}
154+
return sum;
155+
}
146156
}
147157

148158
/// <summary>
@@ -28609,11 +28619,13 @@ static bool IsAMonomialIn(Entity expr, Entity.Variable x)
2860928619
// `sech(x)^2/(a + b tanh(x))^2` after three seconds: simplified once more it is a
2861028620
// quotient, which the symbolic quadratic rule answers with the case `a + b = 0` apart,
2861128621
// and a piecewise antiderivative leaves parts a piecewise to integrate. With numbers
28612-
// alone, or no sum written twice, it is simplified as it was.
28622+
// alone, or no sum written twice, it is simplified as it was -- but for a power of a
28623+
// symbolic polynomial of the third degree or more, without the expanded and factored
28624+
// forms the simplifier offers after its search: see HasAPowerOfASymbolicCubicOrMore.
2861328625
var combined = Functions.SingleQuotient.Combine(rewritten / (Number.Rational.Create(k) * u));
2861428626
var integrand = combined.Vars.Any(symbol => symbol != u)
2861528627
&& WithTheFactorsWrittenOnBothSidesCancelled(u, combined, keepTheWrittenBases: true, out var anyCancelled) is var cancelled && anyCancelled
28616-
? cancelled : combined.Simplify();
28628+
? cancelled : combined.Simplify(HasAPowerOfASymbolicCubicOrMore(combined, u) ? -2 : 2);
2861728629
if (integrand is Providedf(var inner, _))
2861828630
integrand = inner;
2861928631
integrand = WithLogarithmArgumentsOverOneBar(integrand, u);
@@ -28661,6 +28673,64 @@ static bool IsAMonomialIn(Entity expr, Entity.Variable x)
2866128673
}
2866228674
}
2866328675

28676+
/// <summary>
28677+
/// Whether <paramref name="quotient"/> holds a whole power past the first of a sum that is a
28678+
/// polynomial in <paramref name="u"/> of the third degree or more with a symbol in one of
28679+
/// its coefficients: <c>(a (2u)^3 + b (u^2 - 1)^3)^2</c>, which is what the exponential
28680+
/// substitution makes of <c>(a + b sinh(x)^3)^2</c>.
28681+
/// </summary>
28682+
/// <remarks>
28683+
/// <para>
28684+
/// Such a quotient is simplified without the two forms the simplifier offers after its
28685+
/// search, the quotient multiplied out and the quotient factored, each simplified again
28686+
/// before it is ranked; the search itself runs as before. With the symbols in it both are
28687+
/// polynomials of a high degree with a page of coefficients: Rubi's 6.1.7
28688+
/// <c>csch(c + d x)^m (a + b sinh(c + d x)^3)^p</c> spent 2.2 to 7.7 GB of its 5.2 to 10.5
28689+
/// simplifying them -- the common denominator of the expanded terms and the long division
28690+
/// of the factored quotient, each coefficient simplified on its own -- to rank both behind
28691+
/// what the search had found, where the search alone takes 12 to 130 MB.
28692+
/// </para>
28693+
/// <para>
28694+
/// Those fourteen rows, <c>m</c> from 1 to 7 and <c>p</c> 2 or 3, are the corpus's 6.1.7
28695+
/// quotients with such a power. On thirteen the search alone gives what the whole
28696+
/// simplification gives. On <c>csch^7 (a + b sinh^3)^2</c> the factored form wins: the powers
28697+
/// of <c>u</c> written apart above the bar and below it are of one degree, seven, and in the
28698+
/// factored form they cancel to a number. So where they are of one degree the quotient is
28699+
/// simplified in full; and so it is where the sum has no constant term, so that <c>u</c>
28700+
/// divides it, as in 6.7.1's <c>(A + B cosh(x) + C sinh(x))/(b cosh(x) + c sinh(x))^2</c>:
28701+
/// the factored form wins there too, and from the search's form the antiderivative was
28702+
/// 178,682 characters where it is 3,485. A power of a polynomial with numbers for its
28703+
/// coefficients is simplified in full as well, as is one of the second degree: there the
28704+
/// forms after the search win often and cost little.
28705+
/// https://github.com/asc-community/AngouriMath/issues/233
28706+
/// </para>
28707+
/// </remarks>
28708+
private static bool HasAPowerOfASymbolicCubicOrMore(Entity quotient, Entity.Variable u)
28709+
{
28710+
// With a constant term: a sum that u divides has a factor the factored form takes out.
28711+
if (!quotient.Nodes.Any(node => node is Powf(var @base, Number.Integer { EInteger: var power })
28712+
&& @base is Sumf or Minusf && power.CompareTo(EInteger.One) > 0
28713+
&& TreeAnalyzer.TryGetPolynomial(@base, u, out var terms)
28714+
&& terms.Keys.All(degree => degree.Sign >= 0) && terms.Keys.Any(degree => degree.CompareTo(EInteger.FromInt32(3)) >= 0)
28715+
&& terms.Values.All(coefficient => !coefficient.ContainsNode(u)) && terms.Values.Any(coefficient => coefficient.Vars.Any())
28716+
&& terms.TryGetValue(EInteger.Zero, out var constant) && !Functions.PartialFractions.IsZeroAsAValue(constant)))
28717+
return false;
28718+
// Where the powers of u written apart above the bar and below it are of one degree the
28719+
// factored form cancels them to a number and wins: kept to the whole simplification.
28720+
var (top, bottom) = Functions.SingleQuotient.Of(quotient);
28721+
return !DegreeOfTheMonomials(top).Equals(DegreeOfTheMonomials(bottom));
28722+
28723+
EInteger DegreeOfTheMonomials(Entity side)
28724+
{
28725+
var degree = EInteger.Zero;
28726+
foreach (var factor in Mulf.LinearChildren(side))
28727+
if (factor.ContainsNode(u) && TreeAnalyzer.TryGetPolynomial(factor, u, out var monomial) && monomial.Count == 1
28728+
&& monomial.Keys.First() is { Sign: > 0 } power && !monomial.Values.First().ContainsNode(u))
28729+
degree += power;
28730+
return degree;
28731+
}
28732+
}
28733+
2866428734
/// <summary>
2866528735
/// <paramref name="expr"/> with every logarithm whose argument is a rational function
2866628736
/// of <paramref name="x"/> with a quotient nested inside it written over one bar in
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// Rubi's 6.1.7 <c>csch(c + d x)^m (a + b sinh(c + d x)^3)^p</c>. Under <c>u = e^(c + d x)</c> it is a
16+
/// quotient with a power of a symbolic polynomial of the sixth degree in it, which is simplified
17+
/// without the expanded and factored forms the simplifier offers after its search; and the split
18+
/// of the expanded integrand into terms, which declines, stops at the first term declined. Each
19+
/// row cost 5.2 to 10.5 GB, and the answers are the ones they were.
20+
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
21+
/// </summary>
22+
/// <remarks>
23+
/// Each row has symbols of its own, so that no answer the thread remembers from another test
24+
/// makes its bound hold without the work it measures. The bounds fail with either change
25+
/// undone: with the quotient simplified in full every row, at 4.9 to 8.8 GB, and with every
26+
/// term of the split asked the second and the fourth, at 5.2 and 2.8 GB.
27+
/// </remarks>
28+
[Trait("Area", "Calculus")]
29+
public sealed class AHyperbolicCosecantBesideAPowerOfABinomialInTheCubedSineIntegralTest
30+
{
31+
[Theory]
32+
[InlineData("csch(v + w*x)^5*(p + q*sinh(v + w*x)^3)^2", "v,w,p,q", 2_000_000_000L)]
33+
[InlineData("csch(r + s*x)^6*(k + m*sinh(r + s*x)^3)^3", "r,s,k,m", 3_000_000_000L)]
34+
[InlineData("csch(g + h*x)*(n + t*sinh(g + h*x)^3)^3", "g,h,n,t", 2_000_000_000L)]
35+
[InlineData("csch(y + z*x)^7*(f + u*sinh(y + z*x)^3)^3", "y,z,f,u", 2_000_000_000L)]
36+
public void AnsweredWithinItsAllocation(string integrand, string symbols, long mostBytes)
37+
{
38+
var before = GC.GetAllocatedBytesForCurrentThread();
39+
var integral = integrand.ToEntity().Integrate("x");
40+
var allocated = GC.GetAllocatedBytesForCurrentThread() - before;
41+
Assert.True(allocated < mostBytes, $"{integrand} allocated {allocated / 1_000_000} MB, past {mostBytes / 1_000_000} MB");
42+
DifferentiatesBack(integrand, integral, symbols);
43+
}
44+
45+
/// <summary>
46+
/// Where the powers of <c>u</c> above the bar and below it are of one degree, seven here, the
47+
/// factored form the simplifier offers after its search wins and is kept: from the search's
48+
/// form alone the antiderivative was 1,938 characters where it is 571.
49+
/// </summary>
50+
[Fact]
51+
public void WherePowersOfTheExponentialCancelTheFactoredFormIsKept()
52+
{
53+
const string integrand = "csch(c + d*x)^7*(a + b*sinh(c + d*x)^3)^2";
54+
var integral = integrand.ToEntity().Integrate("x");
55+
Assert.True(integral.ToString().Length < 1000, $"the antiderivative of {integrand} is {integral.ToString().Length} characters");
56+
DifferentiatesBack(integrand, integral, "c,d,a,b");
57+
}
58+
59+
/// <summary>
60+
/// And where the sum to a power has no constant term in <c>u</c>, so that <c>u</c> divides it:
61+
/// under <c>u = e^x</c> Rubi's 6.7.1 <c>(A + B cosh(x) + C sinh(x))/(b cosh(x) + c sinh(x))^2</c>
62+
/// is a quotient with such a sum squared, the factored form wins there, and from the search's
63+
/// form alone the antiderivative was 178,682 characters where it is 3,485.
64+
/// </summary>
65+
[Fact]
66+
public void WhereTheSumHasNoConstantTermTheFactoredFormIsKept()
67+
{
68+
const string integrand = "(A + B*cosh(x) + C*sinh(x))/(b*cosh(x) + c*sinh(x))^2";
69+
var integral = integrand.ToEntity().Integrate("x");
70+
Assert.True(integral.ToString().Length < 10000, $"the antiderivative of {integrand} is {integral.ToString().Length} characters");
71+
DifferentiatesBack(integrand, integral, "A,B,C,b,c");
72+
}
73+
74+
private static void DifferentiatesBack(string integrand, Entity integral, string symbols)
75+
{
76+
Assert.DoesNotContain("integral(", integral.Stringize());
77+
var names = symbols.Split(',');
78+
var values = new[] { 0.3, 1.1, 1.3, 0.7, 0.4 };
79+
Entity Pinned(Entity e)
80+
{
81+
for (var k = 0; k < names.Length; k++)
82+
e = e.Substitute(names[k], values[k]);
83+
return e;
84+
}
85+
// A constant of integration differentiates away; C is a coefficient of one integrand here.
86+
var derivative = Pinned(integral).Differentiate("x");
87+
var original = Pinned(integrand.ToEntity());
88+
var compared = 0;
89+
foreach (var at in new[] { 0.2, 0.45, 0.7, 0.95, 1.2, 1.5 })
90+
{
91+
var want = original.Substitute("x", at).EvalNumerical();
92+
var got = derivative.Substitute("x", at).EvalNumerical();
93+
if (want.IsNaN)
94+
continue;
95+
compared++;
96+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
97+
< 1e-8 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
98+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
99+
}
100+
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)