diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 9efef8955..ddc0fdac4 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -762,6 +762,21 @@ taken apart into pieces each closed the same way | `"(2 + x)/((2 + 4*x - 3*x^2)*(1 + 3*x + 2*x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative | | `"1/((x^2 + 1)*sqrt(x^2 + x + 1))".ToEntity().Integrate("x")` | `integral(...)` | a logarithm and an arctangent | +### A rational function of `x^n` beside a power of `x` and the root of a binomial is integrated + +**Answers where there were none.** `x/((a + b x^3)^(2/3) (c + d x^3))` and `x^4 (a + b x^3)^(1/3)/(c + d x^3)`, +Rubi's 1.1.3.4, were declined, while the same without the power of `x` in front was answered under +`u = x/(a + b x^n)^(1/n)`. That substitution takes `x^j (a + b x^n)^(k - (j + 1)/n)` beside a rational +function of `x^n` to `u^j` times a rational function of `u`, for any `j` below `n`, and is taken for +those now. Each answer holds on both sides of zero, an odd root of a negative being real here +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x/((a + b*x^3)^(2/3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | logarithms and an arctangent of `x (a + b x^3)^(-1/3)` | +| `"x^4*(a + b*x^3)^(1/3)/(c + d*x^3)".ToEntity().Integrate("x")` | `integral(...)` | the same, beside a rational function of it | +| `"x/((1 + x^3)^(2/3)*(2 + x^3))".ToEntity().Integrate("x")` | `integral(...)` | the same with numbers | + ### A rational function of `x^n` beside a power of a symbolic binomial is integrated without a search **Answers where there were none.** A rational function of `x^n` beside `(c + d x^n)^(k - 1/n)` is a diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 858c776e3..130c68cd1 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -4185,6 +4185,14 @@ Entity Carried(int numerator, int denominator, int nextSine, int nextCosine) /// The identity holds wherever the root is real, which is where the answer is /// asked; u goes back in as x (c + d x^n)^(-1/n). /// + /// + /// And beside a power of x that is not one of x^n: x^j times a + /// rational function of x^n times (c + d x^n)^(k - (j + 1)/n) is + /// 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. + /// /// https://github.com/asc-community/AngouriMath/issues/718 /// internal static Entity? SolveByDividingByTheRoot(Entity expr, Entity.Variable x) @@ -4195,6 +4203,8 @@ Entity Carried(int numerator, int denominator, int nextSine, int nextCosine) Entity? c = null, d = null; var n = 0; var k = EInteger.Zero; + // The power of x beside it, below n: the exponent is `k - (j + 1)/n`. + var j = 0; Entity rest = Number.Integer.One; var (above, below) = Functions.SingleQuotient.Of(expr); foreach (var (side, isBelow) in new[] { (above, false), (below, true) }) @@ -4206,15 +4216,18 @@ Entity Carried(int numerator, int denominator, int nextSine, int nextCosine) if (radicand is not null || !TryReadAsABinomialIn(@base, x, out var readC, out var readD, out var readN) || readN < 2 || !signed.Denominator.Equals(EInteger.FromInt32(readN))) return null; - // The numerator of the exponent is k n - 1. - var shifted = signed.Numerator.Add(EInteger.One); - if (!shifted.Remainder(EInteger.FromInt32(readN)).IsZero) - return null; + // The numerator of the exponent is k n - j - 1, for j from 0 to n - 1. + var nAsEInteger = EInteger.FromInt32(readN); + var residue = signed.Numerator.Add(EInteger.One).Remainder(nAsEInteger); + if (residue.Sign < 0) + residue = residue.Add(nAsEInteger); + var readJ = residue.IsZero ? 0 : readN - residue.ToInt32Checked(); radicand = @base; c = readC; d = readD; n = readN; - k = shifted.Divide(EInteger.FromInt32(readN)); + j = readJ; + k = signed.Numerator.Add(EInteger.FromInt32(readJ + 1)).Divide(nAsEInteger); continue; } if (factor.ContainsNode(x) && !IsRationalIn(factor, x)) @@ -4223,8 +4236,25 @@ Entity Carried(int numerator, int denominator, int nextSine, int nextCosine) } if (radicand is null || c is null || d is null || TreeAnalyzer.IsZero(c) || TreeAnalyzer.IsZero(d)) return null; - // The rest as a rational function of x^n: written in a stand-in for x^n where every - // power of x is a multiple of n, and declined otherwise. + // The rest as a rational function of x^n, x^j taken out of it first: written in a + // 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. + var (restAbove, restBelow) = Functions.SingleQuotient.Of(rest); + if (!TreeAnalyzer.TryGetPolynomial(restAbove, x, out var aboveTerms) || aboveTerms.Count == 0) + return null; + 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)) + 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; + } var xToTheN = Variable.CreateUnique(expr, "x_n"); var inXToTheN = rest.Replace(node => node is Powf(var xAgain, Number.Integer power) && xAgain == x && power.EInteger.Remainder(EInteger.FromInt32(n)).IsZero @@ -4243,8 +4273,11 @@ node is Powf(var xAgain, Number.Integer power) && xAgain == x && power.EInteger. // base is its polynomial in u first, which collects what the substitution writes twice -- // `1 - (-u^2) - u^2` is 1 under a secant's half angle -- and the factors stay as they are // written, which the rules after this read better than their product expanded. + var restInU = inXToTheN.Substitute(xToTheN, c * uToTheN / oneMinusDUToTheN); + if (j != 0) + restInU = MathS.Pow(u, j) * restInU; var integrand = WithTheFactorsWrittenOnBothSidesCancelled(u, Functions.SingleQuotient.Combine( - inXToTheN.Substitute(xToTheN, c * uToTheN / oneMinusDUToTheN) + restInU * (k.IsZero ? Number.Integer.One : MathS.Pow(binomialInU, Number.Integer.Create(k))) / oneMinusDUToTheN).InnerSimplified); if (integrand is Providedf(var inner, _)) diff --git a/Sources/Tests/UnitTests/Calculus/DividedByTheRootIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/DividedByTheRootIntegralTest.cs index 6c95653a8..2ef444919 100644 --- a/Sources/Tests/UnitTests/Calculus/DividedByTheRootIntegralTest.cs +++ b/Sources/Tests/UnitTests/Calculus/DividedByTheRootIntegralTest.cs @@ -65,5 +65,20 @@ public void TimofeevsQuarticBesideAFourthRoot() [InlineData("1/((a + b*x^2)^(9/2)*(1 + x^2))")] public void AHalfOddPowerOfASymbolicQuadratic(string integrand) => DifferentiatesBack(integrand, new[] { -1.3, -0.4, 0.3, 0.9, 1.7 }, ("a", 1.3), ("b", 0.7)); + + /// + /// Beside a power of x that is not one of x^n: x^j times a rational function of + /// x^n times (a + b x^n)^(k - (j + 1)/n) is u^j times the same in + /// u. Rubi's 1.1.3.4; each was declined. On both sides of zero, the cube root of a + /// negative being real. + /// + [Theory] + [InlineData("x/((a + b*x^3)^(2/3)*(c + d*x^3))")] + [InlineData("x^4/((a + b*x^3)^(2/3)*(c + d*x^3))")] + [InlineData("x^7/((a + b*x^3)^(2/3)*(c + d*x^3))")] + [InlineData("x^4*(a + b*x^3)^(1/3)/(c + d*x^3)")] + [InlineData("x/((1 + x^3)^(2/3)*(2 + x^3))")] + public void BesideAPowerOfX(string integrand) + => DifferentiatesBack(integrand, new[] { -2.7, -1.3, -0.6, 0.4, 1.3, 2.6 }, ("a", 2.3), ("b", 0.7), ("c", 1.3), ("d", 1.7)); } }