Skip to content

Commit a15d60e

Browse files
A power of x beside two blocks in one power of x is split over the blocks first (#1848)
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 0608d5e commit a15d60e

3 files changed

Lines changed: 124 additions & 5 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,23 @@ takes 6 s for 265,499 now ([#718](https://github.com/asc-community/AngouriMath/i
294294
| `"(d + f*x + g*x^2 + h*x^3 + k*x^4 + m*x^5 + n*x^8 + p*x^11)/(a + b*x^2 + c*x^4)^3".ToEntity().Integrate("x")` | `integral(...)` | 77,389 characters |
295295
| `"csc(c + d*x)^2/(a - b*sin(c + d*x)^4)^3".ToEntity().Integrate("x")` | `integral(...)` | 157,787 characters |
296296

297+
### A power of x beside two blocks in one power of x is split over the blocks first
298+
299+
**Shorter answers, and sooner.** `1/(x^7 (a + b x^3)(c + d x^3))` came to 265,499 characters on the unreleased
300+
master: the Hermite reduction read `x^7` as a repeated factor, and what it left went to the split at the power of x,
301+
whose rest over both blocks had coefficients pages long. The blocks are split in `u = x^3` first now, with the power
302+
of x kept outside, and each piece is a power of x over one block: 351 characters. The other `1/(x^m (a + b x^n)(c + d x^n))`
303+
in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take milliseconds
304+
([#718](https://github.com/asc-community/AngouriMath/issues/718)).
305+
306+
| Input | Was (2.5.0) | Now |
307+
|---|---|---|
308+
| `"1/(x*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 185 characters |
309+
| `"1/(x^4*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 259 characters |
310+
| `"1/(x^7*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 351 characters |
311+
| `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters |
312+
| `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters |
313+
297314
### One linear twice is not two linear powers
298315

299316
**Answers that had no value.** `(a c + b c x)^(-3 - 2 p) (f + g x) (a^2 + 2 a b x + b^2 x^2)^p` was answered

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

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,14 @@ is var (multiple, leftover)
10571057
?? Integration.ComputeIndefiniteIntegral(numerator / overTheConjugates, x, integrateByParts)) is { } overConjugates)
10581058
return overConjugates;
10591059

1060+
// A power of x beside two blocks or more in one power of x, `1/(x^7 (a + b x^3)(c + d x^3))`:
1061+
// split in `u = x^3` over the blocks, each fraction then a power of x over one block.
1062+
// Before the Hermite reduction, which reads the power of x as a repeated factor: what it
1063+
// leaves went to the split at the power of x, whose rest over both blocks had coefficients
1064+
// pages long, and the answer was 265,499 characters.
1065+
if (IntegrateOverBlocksInAPowerOfX(numerator, denominator, x, integrateByParts, besideAPowerOfX: true) is { } besideBlocksInAPower)
1066+
return besideBlocksInAPower;
1067+
10601068
// A denominator with a written repeated factor takes the Hermite reduction first:
10611069
// the rational part of the answer in one linear solve, and what is left is a proper
10621070
// fraction over a squarefree denominator for the splits below. `(1 + x^2)/(x (1 + x^3)^2)`
@@ -1622,19 +1630,27 @@ static Entity Binomial(int n, int k)
16221630
/// <c>n >= 2</c>, linear or quadratic in it, with a symbol among them: split in
16231631
/// <c>u = x^n</c> by <see cref="Functions.PartialFractions.TrySplitOverWrittenFactors"/>,
16241632
/// one residue of the numerator's powers modulo <c>n</c> at a time, each fraction then
1625-
/// over one block in x.
1633+
/// over one block in x. With <paramref name="besideAPowerOfX"/>, a power of x beside two
1634+
/// blocks or more as well, and only then.
16261635
/// </summary>
16271636
/// <remarks>
16281637
/// <c>1/((a + b x^3)(c + d x^3))</c> was declined: over the rationals it does not factor,
16291638
/// and the split over written factors reads a block of the third degree as nothing. In
16301639
/// <c>u = x^3</c> it is <c>1/((a + b u)(c + d u))</c>, two linear factors. A numerator
16311640
/// term <c>x^r M(x^n)</c> keeps its <c>x^r</c> outside the split, which is exact since
16321641
/// the blocks are polynomials in <c>x^n</c> alone.
1642+
/// <para>
1643+
/// A power of x beside the blocks stays outside the split: <c>1/(x^7 (a + b x^3)(c + d x^3))</c>
1644+
/// is <c>(b/(x^7 (a + b x^3)) - d/(x^7 (c + d x^3)))/(b c - a d)</c>, a power of x over one
1645+
/// block twice. Split at the power of x first, the rest over both blocks had coefficients
1646+
/// that were pages of symbols, and the answer came to 265,499 characters.
1647+
/// </para>
16331648
/// https://github.com/asc-community/AngouriMath/issues/718
16341649
/// </remarks>
1635-
private static Entity? IntegrateOverBlocksInAPowerOfX(Entity numerator, Entity denominator, Entity.Variable x, bool integrateByParts, bool oneBlockAtItsRoots = false)
1650+
private static Entity? IntegrateOverBlocksInAPowerOfX(Entity numerator, Entity denominator, Entity.Variable x, bool integrateByParts, bool oneBlockAtItsRoots = false, bool besideAPowerOfX = false)
16361651
{
16371652
var blocks = new List<(Dictionary<EInteger, Entity> Polynomial, int Power)>();
1653+
var powerOfX = 0;
16381654
Entity constant = Number.Integer.One;
16391655
foreach (var factor in Mulf.LinearChildren(denominator))
16401656
{
@@ -1646,6 +1662,13 @@ static Entity Binomial(int n, int k)
16461662
var (@base, power) = factor is Powf(var b, Number.Integer e) && e.EInteger.Sign > 0 && e.EInteger.CanFitInInt32()
16471663
? (b, e.EInteger.ToInt32Unchecked())
16481664
: (factor, 1);
1665+
if (@base == x)
1666+
{
1667+
if (!besideAPowerOfX)
1668+
return null;
1669+
powerOfX += power;
1670+
continue;
1671+
}
16491672
if (!TreeAnalyzer.TryGetPolynomial(@base, x, out var polynomial) || polynomial.Count < 2)
16501673
return null;
16511674
foreach (var term in polynomial)
@@ -1660,6 +1683,8 @@ static Entity Binomial(int n, int k)
16601683
}
16611684
if (blocks.Count == 0 || !(numerator + denominator).Vars.Any(symbol => symbol != x))
16621685
return null;
1686+
if (besideAPowerOfX && (powerOfX == 0 || blocks.Count < 2 || powerOfX > MaximumPowerOfXOverABlock))
1687+
return null;
16631688
// n, the greatest common divisor of every power the blocks hold.
16641689
var n = EInteger.Zero;
16651690
foreach (var (polynomial, _) in blocks)
@@ -1678,6 +1703,8 @@ static Entity Binomial(int n, int k)
16781703
foreach (var term in above)
16791704
if (term.Key.Sign < 0 || !term.Key.CanFitInInt32() || term.Value.ContainsNode(x))
16801705
return null;
1706+
if (besideAPowerOfX && above.Values.Any(coefficient => coefficient is not (Number or Variable)))
1707+
return null;
16811708

16821709
var u = Entity.Variable.CreateUnique(numerator + denominator, "u");
16831710
Entity InU(Dictionary<EInteger, Entity> polynomial)
@@ -1735,11 +1762,33 @@ Entity InU(Dictionary<EInteger, Entity> polynomial)
17351762
}
17361763
if (!Functions.PartialFractions.TrySplitOverWrittenFactors(inU, below, u, out var decomposition))
17371764
return null;
1738-
var outside = residue.Key == 0 ? (Entity)Number.Integer.One : residue.Key == 1 ? x : MathS.Pow(x, residue.Key);
1739-
foreach (var fraction in Sumf.LinearChildren(decomposition))
1765+
// The power of x below stays outside the split, so each fraction is a power of x
1766+
// over one block, which the split at a power of x beside a block reads.
1767+
var outside = (residue.Key - powerOfX) switch
17401768
{
1769+
0 => (Entity)Number.Integer.One,
1770+
1 => x,
1771+
var power => MathS.Pow(x, power),
1772+
};
1773+
// The split comes as a sum over a constant. Beside a power of x it is taken term by
1774+
// term: whole, it is the power of x over both blocks again, for the split at that
1775+
// power, whose coefficients run to `a^54 b^6 c^46 d^14`.
1776+
var (terms, scale) = besideAPowerOfX && decomposition is Divf(var sum, var divisor) && !divisor.ContainsNode(u)
1777+
? (sum, divisor)
1778+
: (decomposition, (Entity)Number.Integer.One);
1779+
foreach (var term in Sumf.LinearChildren(terms))
1780+
{
1781+
if (term == Number.Integer.Zero)
1782+
continue;
1783+
var fraction = scale == Number.Integer.One ? term : term / scale;
17411784
var inX = outside * fraction.Substitute(u, MathS.Pow(x, step));
1742-
if (Integration.ComputeIndefiniteIntegral(inX, x, integrateByParts) is not { } integrated)
1785+
// Beside a power of x, each piece is a part of the question asked, over one block
1786+
// fewer, and answered at its level: the split at the power of x that reads it
1787+
// answers only the question asked or one below.
1788+
var integrated = besideAPowerOfX
1789+
? Integration.ComputeAsTheSameQuestion(inX, x, integrateByParts)
1790+
: Integration.ComputeIndefiniteIntegral(inX, x, integrateByParts);
1791+
if (integrated is null)
17431792
return null;
17441793
total += integrated;
17451794
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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 power of x beside two blocks in one power of x is split over the blocks in
16+
/// <c>u = x^n</c> with the power of x kept outside: <c>1/(x^7 (a + b x^3)(c + d x^3))</c> is a
17+
/// power of x over one block twice. Rubi's 1.1.3.4.
18+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
19+
/// </summary>
20+
[Trait("Area", "Calculus")]
21+
public sealed class APowerOfXBesideBlocksInAPowerOfXIntegralTest
22+
{
23+
[Theory]
24+
[InlineData("1/(x*(a + b*x^3)*(c + d*x^3))")]
25+
[InlineData("1/(x^4*(a + b*x^3)*(c + d*x^3))")]
26+
[InlineData("1/(x^7*(a + b*x^3)*(c + d*x^3))")]
27+
[InlineData("1/(x^6*(a + b*x^4)*(c + d*x^4))")]
28+
public void DifferentiatesBack(string integrand)
29+
{
30+
var integral = integrand.ToEntity().Integrate("x");
31+
Assert.DoesNotContain("integral(", integral.Stringize());
32+
Entity Pinned(Entity e) => e.Substitute("a", 2).Substitute("b", 3).Substitute("c", 5).Substitute("d", 7);
33+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
34+
var original = Pinned(integrand.ToEntity());
35+
foreach (var at in new[] { 0.35, 0.6, 0.85, 1.4 })
36+
{
37+
var want = original.Substitute("x", at).EvalNumerical();
38+
var got = derivative.Substitute("x", at).EvalNumerical();
39+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
40+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)),
41+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
42+
}
43+
}
44+
45+
/// <summary>
46+
/// Split at the power of x first, the rest over both blocks had coefficients pages long,
47+
/// and the answer was 265,499 characters; over the blocks first it is 351.
48+
/// </summary>
49+
[Fact]
50+
public void TheAnswerIsTheSizeOfItsPieces()
51+
=> Assert.True("1/(x^7*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x").Stringize().Length < 2000);
52+
}
53+
}

0 commit comments

Comments
 (0)