Skip to content

Commit efd72ac

Browse files
Split a power of x beside blocks to a power only where its pieces are read (#2000)
SolveAsPolynomialTerm writes 1/P as P^(-1) and asks it a level down, and deeper still SolveByLinearRadicalSubstitution reached IntegrateOverBlocksInAPowerOfX with besideAPowerOfX at descent 3. The pieces over a block to a power are read only by IntegrateOverAPowerOfXBesideABlock, which runs at the question asked or one below, so at descent 3 every piece went round the whole search, about sixteen levels deep, before it was declined, and the 1/P spelling one level up then answered in 80 ms. The split now declines there, where a block is to a power past the first; with every block to the first power the pieces are read at any depth, and stay. Twelve of Rubi's 1.1.2.4 rows that took 5-16 s (542-544, 550-552, 562-564, 570-572) allocate 3-14 times less, 542 from 11.9 GB to 0.97, with the same answers, character for character; 303 solved rows of 1.1.2.x and 1.1.3.x answer as before, none allocating more. 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 685093c commit efd72ac

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2882,6 +2882,17 @@ private static Entity WithTheDenominatorInPowersOf(Entity value, IReadOnlyList<E
28822882
return null;
28832883
if (besideAPowerOfX && (powerOfX == 0 || blocks.Count < 2 || powerOfX > MaximumPowerOfXOverABlock))
28842884
return null;
2885+
// Beside a power of x with a block to a power, only where the pieces over that power
2886+
// are read: each is a power of x over one block, asked at this level, and
2887+
// IntegrateOverAPowerOfXBesideABlock, which reads a block to a power, answers the
2888+
// question asked or one below it. Deeper, the pieces went round the whole search to be
2889+
// declined: `1/(x^(3/2) (a + b x^2) (c + d x^2)^2)` reaches this split twice, at the
2890+
// level where it is answered and a level further down, through the reciprocal
2891+
// SolveAsPolynomialTerm writes, where it spent six of its seven seconds. Blocks to the
2892+
// first power are read at any depth, and `1/(x^(5/2) (a + b x^2)(c + d x^2))` is
2893+
// answered through the deeper split with a thirtieth of the allocation it takes without it.
2894+
if (besideAPowerOfX && !Integration.AnsweringTheQuestionAskedOrOneBelow && blocks.Any(block => block.Power > 1))
2895+
return null;
28852896
// n, the greatest common divisor of every power the blocks hold.
28862897
var n = EInteger.Zero;
28872898
foreach (var (polynomial, _) in blocks)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
/// A half-odd power of x below two blocks in <c>x^2</c>, one of them to a power, as Rubi's 1.1.2.4
16+
/// writes it. Under <c>t = sqrt(x)</c> it is a power of t beside two blocks in <c>t^4</c>, which the
17+
/// split beside a power of x answers where its pieces over the power are read, at the question
18+
/// asked or one below it. It was answered there, after a second pass through the same split a
19+
/// level further down, where those pieces are not read and each went round the whole search to be
20+
/// declined: seven seconds of <c>1/(x^(3/2) (a + b x^2) (c + d x^2)^2)</c> were six of that. With
21+
/// both blocks to the first power the deeper split is read and still taken.
22+
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
23+
/// </summary>
24+
[Trait("Area", "Calculus")]
25+
public sealed class APowerOfXBesideBlocksIsSplitWhereThePiecesAreReadIntegralTest
26+
{
27+
[Theory]
28+
[InlineData("1/(x^(3/2)*(a + b*x^2)*(c + d*x^2)^2)")]
29+
[InlineData("1/(x^(5/2)*(a + b*x^2)*(c + d*x^2)^2)")]
30+
[InlineData("1/(x^(7/2)*(a + b*x^2)*(c + d*x^2)^2)")]
31+
[InlineData("1/(x^(3/2)*(a + b*x^2)*(c + d*x^2)^3)")]
32+
[InlineData("1/(x^(5/2)*(a + b*x^2)*(c + d*x^2)^3)")]
33+
[InlineData("1/(x^(7/2)*(a + b*x^2)*(c + d*x^2)^3)")]
34+
[InlineData("1/(x^(3/2)*(a + b*x^2)^2*(c + d*x^2)^2)")]
35+
[InlineData("1/(x^(5/2)*(a + b*x^2)*(c + d*x^2))")]
36+
public void DifferentiatesBack(string integrand)
37+
{
38+
var integral = integrand.ToEntity().Integrate("x");
39+
var text = integral.Stringize();
40+
Assert.DoesNotContain("integral(", text);
41+
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.3);
42+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
43+
var original = Pinned(integrand.ToEntity());
44+
var compared = 0;
45+
foreach (var at in new[] { 0.3, 0.6, 0.9, 1.3, 1.8, 2.4 })
46+
{
47+
var want = original.Substitute("x", at).EvalNumerical();
48+
var got = derivative.Substitute("x", at).EvalNumerical();
49+
if (want.IsNaN)
50+
continue;
51+
compared++;
52+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
53+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
54+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
55+
}
56+
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)