From d3e7610bdd07b8457d0dc41434879eb680f188f1 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 11 Oct 2026 10:57:38 +0000 Subject: [PATCH 1/2] A power of x below the bar beside a root of a binomial is rationalised by dividing by the root Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 18 +++++++ .../Integration/IndefiniteIntegralSolver.cs | 23 +++++++-- ...heBarBesideARootOfABinomialIntegralTest.cs | 47 +++++++++++++++++++ 3 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/APowerOfXBelowTheBarBesideARootOfABinomialIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index efd821edf..b4b9a3639 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -194,6 +194,24 @@ back after. Each answer is in three cases by the sign of `4 a/c - (b/c)^2` | `"x^5/(a + b*x + c*x^2)^5".ToEntity().Integrate("x")` | `integral(...)` | 4,066 characters | | `"x^7/(a + b*x + c*x^2)^5".ToEntity().Integrate("x")` | `integral(...)` | 5,602 characters | +### A power of x below the bar beside a root of a binomial is rationalised by dividing by the root + +**Answers where there were none.** `(a + b x^3)^(1/3)/(x^2 (c + d x^3))` was declined, and so were sixteen more of Rubi's +1.1.3.4 with `x^2`, `x^5`, `x^8`, `x^11` or `x^14` below the bar beside `(a + b x^3)^(1/3)`, `^(4/3)` or `^(-2/3)`. +`u = x/(a + b x^3)^(1/3)` makes each rational, and was used where the power of x stood above the bar only: +`x^(-2)` is `x` times `x^(-3)`, and the second is a function of `x^3` like the rest +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(a + b*x^3)^(1/3)/(x^2*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 857 characters | +| `"(a + b*x^3)^(1/3)/(x^11*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 4,974 characters | +| `"(a + b*x^3)^(4/3)/(x^2*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,583 characters | +| `"(a + b*x^3)^(4/3)/(x^14*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 6,092 characters | +| `"1/(x^2*(a + b*x^3)^(2/3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,586 characters | +| `"(a + b*x^3)^(1/3)/(x^2*(a*d - b*d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 773 characters | +| `"1/(x^2*(1 - x^3)^(2/3)*(1 + x^3))".ToEntity().Integrate("x")` | `integral(...)` | 471 characters | + ### `a + i a tan` beside the tangent's differential is lowered over its conjugate **Answers where there were none.** `sqrt(a + i a tan(x))/(c + d tan(x))^(3/2)` ran past thirty seconds: diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index f45dd9882..84c34d8bd 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -5479,7 +5479,10 @@ Entity Carried(int numerator, int denominator, int nextSine, int nextCosine) /// u^j times the same in u, since x^j (c + d x^n)^(-j/n) is /// u^j. Rubi's 1.1.3.4: x/((a + b x^3)^(2/3) (c + d x^3)) is /// u/(c - (b c - a d) u^3), and x^4 (a + b x^3)^(1/3)/(c + d x^3) is the - /// same with x^3 beside it; each was declined. + /// same with x^3 beside it; each was declined. A power of x below the bar is read + /// the same way, with the multiple of n it leaves going into the rational function: + /// (a + b x^3)^(1/3)/(x^2 (c + d x^3)) is x times 1/(x^3 (c + d x^3)) + /// times (a + b x^3)^(1 - 2/3), Rubi's 1.1.3.4:763. /// /// https://github.com/asc-community/AngouriMath/issues/718 /// @@ -5528,20 +5531,30 @@ Entity Carried(int numerator, int denominator, int nextSine, int nextCosine) // stand-in for x^n where every power of x is a multiple of n, and declined otherwise. if (j != 0) { - // Out of the polynomial above the bar, each of whose powers is then a multiple of n. + // Out of the polynomial above the bar together with the power of x below it, each + // power then a multiple of n, below zero too: `x^(-2)` is `x` times `x^(-3)`. var (restAbove, restBelow) = Functions.SingleQuotient.Of(rest); if (!TreeAnalyzer.TryGetPolynomial(restAbove, x, out var aboveTerms) || aboveTerms.Count == 0) return null; + var belowX = 0; + Entity belowOtherwise = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(restBelow)) + if (factor == x) + belowX++; + else if (factor is Powf(var xAgain, Number.Integer power) && xAgain == x && power.EInteger.Sign > 0 && power.EInteger.CanFitInInt32()) + belowX += power.EInteger.ToInt32Checked(); + else + belowOtherwise = belowOtherwise == Number.Integer.One ? factor : belowOtherwise * factor; Entity lowered = Number.Integer.Zero; foreach (var term in aboveTerms) { - var power = term.Key.Subtract(EInteger.FromInt32(j)); - if (power.Sign < 0 || term.Value.ContainsNode(x)) + var power = term.Key.Subtract(EInteger.FromInt32(j + belowX)); + if ((belowX == 0 && power.Sign < 0) || term.Value.ContainsNode(x)) return null; var monomial = power.IsZero ? term.Value : term.Value * MathS.Pow(x, Number.Integer.Create(power)); lowered = lowered == Number.Integer.Zero ? monomial : lowered + monomial; } - rest = restBelow == Number.Integer.One ? lowered : lowered / restBelow; + rest = belowOtherwise == Number.Integer.One ? lowered : lowered / belowOtherwise; } var xToTheN = Variable.CreateUnique(expr, "x_n"); var inXToTheN = rest.Replace(node => diff --git a/Sources/Tests/UnitTests/Calculus/APowerOfXBelowTheBarBesideARootOfABinomialIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerOfXBelowTheBarBesideARootOfABinomialIntegralTest.cs new file mode 100644 index 000000000..ef221ba63 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerOfXBelowTheBarBesideARootOfABinomialIntegralTest.cs @@ -0,0 +1,47 @@ +// +// 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 x below the bar beside a root of a binomial in x^n and a rational function + /// of x^n, rationalised by u = x/(a + b x^n)^(1/n): x^(-2) is x + /// times x^(-3). Rubi's 1.1.3.4. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class APowerOfXBelowTheBarBesideARootOfABinomialIntegralTest + { + [Theory] + [InlineData("(a + b*x^3)^(1/3)/(x^2*(c + d*x^3))", "0.35 0.6 0.85 1.4")] + [InlineData("(a + b*x^3)^(1/3)/(x^5*(c + d*x^3))", "0.35 0.6 0.85 1.4")] + [InlineData("(a + b*x^3)^(4/3)/(x^2*(c + d*x^3))", "0.35 0.6 0.85 1.4")] + [InlineData("1/(x^2*(a + b*x^3)^(2/3)*(c + d*x^3))", "0.35 0.6 0.85 1.4")] + [InlineData("(a + b*x^3)^(1/3)/(x^2*(a*d - b*d*x^3))", "0.35 0.6 1.4 1.8")] + [InlineData("1/(x^2*(1 - x^3)^(2/3)*(1 + x^3))", "0.35 0.6 0.8 0.95")] + public void DifferentiatesBack(string integrand, string points) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 2).Substitute("b", 3).Substitute("c", 5).Substitute("d", 7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in points.Split(' ')) + { + var want = original.Substitute("x", at.ToEntity()).EvalNumerical(); + var got = derivative.Substitute("x", at.ToEntity()).EvalNumerical(); + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +} From 7ced914461e9119b7fb4857878280f0b6bdf7be4 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 11 Oct 2026 13:01:59 +0000 Subject: [PATCH 2/2] Re-measure an entry row on the merged master Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index ef84439f6..1880f261b 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -206,7 +206,7 @@ back after. Each answer is in three cases by the sign of `4 a/c - (b/c)^2` |---|---|---| | `"(a + b*x^3)^(1/3)/(x^2*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 857 characters | | `"(a + b*x^3)^(1/3)/(x^11*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 4,974 characters | -| `"(a + b*x^3)^(4/3)/(x^2*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,583 characters | +| `"(a + b*x^3)^(4/3)/(x^2*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,562 characters | | `"(a + b*x^3)^(4/3)/(x^14*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 6,092 characters | | `"1/(x^2*(a + b*x^3)^(2/3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,586 characters | | `"(a + b*x^3)^(1/3)/(x^2*(a*d - b*d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 773 characters |