Skip to content

Commit 2a98be4

Browse files
Expand a power of a long sum without visiting the combinations it does not return (#2030)
Combinations(min, max, cellCount) enumerates the ways to share a power among the terms of a sum, and its loop ran i up to max at every depth, so every prefix of every length was visited: about 2^(n + k - 1) of them for the C(n + k - 1, k) it returns. Squaring a sum of thirty terms, 465 terms and well inside MaxExpansionTermCount, ran past a minute. The loop now stops where too few values are left above i to fill the remaining cells; a branch past that yields nothing, so no result and no order changes. Five of Rubi's integrals whose working squares such a sum were past the budget and are answered: 3.2.1's 1/((a g + b g x)^2 (A + B ln(e (a + b x)/(c + d x)))^2) and its n-th power, 4.1.3.1:357, and 6.7.1:1178 and 1251. Master f7f2a7a ran past 150 s on each; 2.5.0 declined three and ran past 60 s on two. Chain against master f7f2a7a: pocket of 571 rows, 532 -> 537 solved, 0 wrong; the samples unchanged; of the 8 rows that moved, alone, master solved none and this solves 5, the other three declined here where master declined one and ran out of time on two; 6,064 rows compared for allocation, none grew. 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 1cb6991 commit 2a98be4

4 files changed

Lines changed: 114 additions & 1 deletion

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,24 @@ out of it ([#233](https://github.com/asc-community/AngouriMath/issues/233)).
294294
| `"(a*m*x^m+b*n*q*ln(c*x^n)^(q-1))/(x*(a*x^m+b*ln(c*x^n)^q))".ToEntity().Integrate("x")` | `integral(...)` | `ln(a x^m + b ln(c x^n)^q)` |
295295
| `"(a*(m-1)*x^(m-1)+b*n*q*ln(c*x^n)^(q-1))/(a*x^m+b*x*ln(c*x^n)^q)".ToEntity().Integrate("x")` | `integral(...)` | `ln(a x^(m - 1) + b ln(c x^n)^q)` |
296296

297+
### A power of a long sum is expanded without visiting the combinations it does not return
298+
299+
**Answers where there were none.** Expanding `(t_1 + ... + t_n)^k` enumerates the ways to share `k`
300+
among the `n` terms, and the enumeration visited every prefix of every length, about
301+
`2^(n + k - 1)` of them for the `C(n + k - 1, k)` it returns; squaring a sum of thirty terms ran past
302+
a minute. It now stops a branch where too few values are left to fill it, which changes no term and
303+
no order. Five integrals whose working squares such a sum were past the budget and are answered
304+
([#233](https://github.com/asc-community/AngouriMath/issues/233)); this branch's master ran past
305+
150 s on each.
306+
307+
| Input | Was (2.5.0) | Now |
308+
|---|---|---|
309+
| `"1/((a*g+b*g*x)^2*(A+B*ln(k*((a+b*x)/(c+d*x))^n))^2)".ToEntity().Integrate("x")` | `integral(...)` | 412 characters |
310+
| `"1/((a*g+b*g*x)^2*(A+B*ln(k*(a+b*x)/(c+d*x)))^2)".ToEntity().Integrate("x")` | past 60 s | 358 characters |
311+
| `"(a+a*sin(k+f*x))^(3/2)*(A+B*sin(k+f*x))/(c+d*sin(k+f*x))^2".ToEntity().Integrate("x")` | `integral(...)` | 5,373 characters |
312+
| `"sech(x)^2*tanh(x)^2/(2+tanh(x)^3)^2".ToEntity().Integrate("x")` | past 60 s | 117 characters |
313+
| `"coth(x)^3*csch(x)*sqrt(1+csch(x))".ToEntity().Integrate("x")` | `integral(...)` | 325 characters |
314+
297315
### A rational evaluated at a higher precision than before has all of its digits
298316

299317
**More digits where there were a hundred.** A rational kept its decimal value in the precision current when it was

‎Sources/.editorconfig‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,3 +734,6 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
734734

735735
[Tests/UnitTests/Calculus/HyperbolicIntegralIntegrationTest.cs]
736736
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n
737+
738+
[Tests/UnitTests/Common/SimplifyOverASymbolicCommonDenominatorTest.cs]
739+
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

‎Sources/AngouriMath/Functions/TreeAnalyzer/Expansion.cs‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,18 @@ internal static IEnumerable<List<EInteger>> CombinateSums(EInteger itemCount, EI
6363
// Combinations(2, 6, 3) ->
6464
// { 4, 3, 2 }, { 5, 3, 2 }, { 6, 3, 2 }, { 5, 4, 2 }, { 6, 4, 2 },
6565
// { 6, 5, 2 }, { 5, 4, 3 }, { 6, 4, 3 }, { 6, 5, 3 }, { 6, 5, 4 }
66+
//
67+
// The loop stops where too few values are left above i to fill the remaining
68+
// cells, since a branch past that yields nothing. Without the bound every prefix of
69+
// every length was visited -- about 2^(itemCount + targetSum - 1) of them for
70+
// C(itemCount + targetSum - 1, targetSum) results -- so squaring a sum of thirty
71+
// terms, 465 terms and well inside MaxExpansionTermCount, ran past a minute, and two
72+
// more terms took four times as long. The bound changes no result and no order.
73+
// https://github.com/asc-community/AngouriMath/issues/233
6674
static IEnumerable<List<EInteger>> Combinations(EInteger min, EInteger max, EInteger cellCount)
6775
{
68-
for (EInteger i = min; i <= max; i++)
76+
var last = cellCount.Sign > 0 ? max - cellCount + 1 : max;
77+
for (EInteger i = min; i <= last; i++)
6978
if (!cellCount.Equals(EInteger.One))
7079
foreach (var l in Combinations(i + 1, max, cellCount - 1))
7180
{
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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 System.Linq;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using AngouriMath.Extensions;
13+
using Xunit;
14+
15+
namespace AngouriMath.Tests.Common
16+
{
17+
/// <summary>
18+
/// <c>Simplify</c> returns on quotients over a symbolic denominator. Bringing two of their
19+
/// fractions over a common denominator re-simplifies the cross products, and reading those as
20+
/// polynomials expands squares of sums of thirty terms and more, whose exponent splits were
21+
/// enumerated in time exponential in the number of terms. The integrator met them after a
22+
/// substitution. <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
23+
/// </summary>
24+
[Trait("Area", "Common")]
25+
public sealed class SimplifyOverASymbolicCommonDenominatorTest
26+
{
27+
[Fact]
28+
public void TheSquareOfALongSumExpands()
29+
{
30+
var names = Enumerable.Range(0, 30).Select(i => "s" + (char)('a' + i / 26) + (char)('a' + i % 26)).ToArray();
31+
var sum = string.Join(" + ", names);
32+
var expanded = Run(() => $"({sum})^2".ToEntity().Expand());
33+
// Thirty squares and 435 products of two, none of which combine.
34+
Assert.Equal(465, Entity.Sumf.LinearChildren(expanded).Count);
35+
var at = expanded;
36+
for (var i = 0; i < names.Length; i++)
37+
at = at.Substitute(names[i], i + 1);
38+
Assert.Equal((Entity)(465 * 465), at.Evaled);
39+
}
40+
41+
// Each returns as it was written: the search finds nothing shorter. Under a second or
42+
// three each on a desktop, where the first did not return in ten minutes and the
43+
// third took forty-seven seconds.
44+
[Theory]
45+
[InlineData("u^4/((c*u + d*(2 - u))^2*(2 - u))")]
46+
[InlineData("u^3/((c*u + d)^2*(2 - u))")]
47+
[InlineData("((A + B*(1 - 2*c/d))*sin(h)*(1 - u^2) + B*(-1/2 + c/d - c^2/(2*d^2))*sin(h))/(c + d*(2*(1 - u^2) - 1))^2/(-sin(h))")]
48+
public void ReturnsWithTheValueItWasGiven(string written)
49+
{
50+
var expression = written.ToEntity();
51+
var simplified = Run(() => expression.Simplify(1));
52+
var compared = 0;
53+
foreach (var shift in new[] { 0.0, 0.37, 1.13 })
54+
{
55+
Entity Pinned(Entity e) => e
56+
.Substitute("A", 1.3 + shift).Substitute("B", -0.7 + shift).Substitute("c", 0.4 + shift)
57+
.Substitute("d", 1.9 - shift).Substitute("h", 0.6 + shift).Substitute("u", 0.3 + shift);
58+
var want = Pinned(expression).EvalNumerical();
59+
var got = Pinned(simplified).EvalNumerical();
60+
if (want.IsNaN)
61+
continue;
62+
compared++;
63+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
64+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
65+
$"{written} simplified to {simplified}, which is {got} where the expression is {want}");
66+
}
67+
Assert.Equal(3, compared);
68+
}
69+
70+
/// <summary>
71+
/// On a thread of its own, so that a test host whose pool is busy cannot hold it back,
72+
/// and with a wall far above the run, so that the bound is reached only by a computation
73+
/// that does not finish.
74+
/// </summary>
75+
private static Entity Run(Func<Entity> work)
76+
{
77+
var task = Task.Factory.StartNew(work, CancellationToken.None,
78+
TaskCreationOptions.LongRunning, TaskScheduler.Default);
79+
Assert.True(task.Wait(TimeSpan.FromMinutes(2)), "did not return in two minutes");
80+
return task.Result;
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)