From 9c935eaa92ee0bb48d227fd8f736118e7c5d3023 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 10 Oct 2026 02:38:05 +0000 Subject: [PATCH] A symbol a numerator sum's coefficients share comes out in front of the partial fractions Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 12 +++++ .../Integration/IndefiniteIntegralSolver.cs | 15 ++++++ .../ContentOutOfANumeratorSumIntegralTest.cs | 48 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/ContentOutOfANumeratorSumIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index fc26529d2..539126d84 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -193,6 +193,18 @@ of a linear over a linear, which is answered in a second | `"1/(sqrt(a + i*a*tan(x))*(c + d*tan(x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 2,217 characters | | `"1/((a + i*a*tan(x))^(3/2)*(c + d*tan(x))^(5/2))".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 3,333 characters | +### A symbol shared by a numerator sum's coefficients is taken out before the partial fractions + +**Answers where there were none.** `(b d + 2c d x)^9/(a + b x + c x^2)^3` ran past a minute, where +`(b + 2c x)^9/(a + b x + c x^2)^3` was answered in two seconds: the shared `d` stood in every coefficient the +partial fractions divide. It is taken out in front of the integral first +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(b*d + 2*c*d*x)^9/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 76,249 characters, the length the content-free one already had | +| `"(b*d + 2*c*d*x)^8/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 65,236 characters | + ### A symbolic quadratic with a square discriminant is split into its linears before the division **Answers where there were none.** `(d + e x)^8/(a d e + (c d^2 + a e^2) x + c d e x^2)^2` ran past a minute, with diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 76c5c9b42..610b093d5 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -701,6 +701,21 @@ Entity Back(Entity mapped) if (!TryReadAsQuotient(expr, out var numerator, out var denominator)) return null; + // A power of a sum in the numerator whose coefficients share a symbol, `(b d + 2c d x)^9`, + // is that symbol's power times one of a sum without it: written so, it is a power of the + // denominator's derivative, and the division of the improper fraction as written ran past + // a minute where `(b + 2c x)^9/(a + b x + c x^2)^3` is answered in two seconds. + // The content comes out in front of the integral, not into the numerator, where its + // symbols would stand in every coefficient the rules below divide. + if (WithTheContentOutOfEachSumFactor(numerator, x) is { } numeratorWithoutContent + && Mulf.LinearChildren(numeratorWithoutContent).ToList() is var parts + && parts.Aggregate((Entity)Number.Integer.One, (product, part) => part.ContainsNode(x) ? product : product * part) is var content + && parts.Aggregate((Entity)Number.Integer.One, (product, part) => part.ContainsNode(x) ? product * part : product) is var contentFree + && Patterns.GatherPowersOfOneBase(contentFree / denominator) is var overTheContentFree + && (SolveByPartialFractions(overTheContentFree, x, integrateByParts) + ?? Integration.ComputeIndefiniteIntegral(overTheContentFree, x, integrateByParts)) is { } withoutTheContent) + return content * withoutTheContent; + // A quotient with x below a bar inside it, `1/(a + b/x)`, written over one bar: every // rule below reads the numerator and the denominator as polynomials, and `a + b/x` is // not one, where `x/(a x + b)` is read at once. Once: what one bar gives has none. diff --git a/Sources/Tests/UnitTests/Calculus/ContentOutOfANumeratorSumIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ContentOutOfANumeratorSumIntegralTest.cs new file mode 100644 index 000000000..f856036ab --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ContentOutOfANumeratorSumIntegralTest.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A power of a sum in the numerator whose coefficients share a symbol, with that symbol's power + /// taken out in front of the integral before the partial fractions. Rubi's 1.2.1.2. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ContentOutOfANumeratorSumIntegralTest + { + [Theory] + [InlineData("(b*d + 2*c*d*x)^8/(a + b*x + c*x^2)^3")] + public void WithTheContentInFront(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 100000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.4).Substitute("c", 1.9).Substitute("d", 1.6).Substitute("f", 0.7).Substitute("g", 0.45); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -2.1, -0.4, 0.3, 0.6, 1.6, 2.5 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } +}