diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 5b3f0bb95..5051710b2 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -545,6 +545,20 @@ answered too ([#1852](https://github.com/asc-community/AngouriMath/issues/1852)) | `"x^m*sin(a + ln(k*x^n)*sqrt(-(1 + m)^2/n^2))".ToEntity().Integrate("x")` | `integral(...)` | 465 characters | | `"x^2*sin(a + ln(k*x^n)*sqrt((-1)/n^2))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,873 characters | +### A power of a linear beside a quadratic's derivative over a power of the quadratic is integrated by parts + +**Answers where there were none.** `(b + 2 c x) sqrt(d + k x)/(a + b x + c x^2)^2` ran past any budget, the partial fractions +taking the symbolic quadratic apart twice over. With `u` the power of the linear and `dv` the derivative over the power +of the quadratic, parts leaves the quadratic one power lower, which is answered in under half a second +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(b + 2*c*x)*(d + k*x)^4/(a + b*x + c*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 2,215 characters | +| `"(b + 2*c*x)*sqrt(d + k*x)/(a + b*x + c*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 976 characters | +| `"(b + 2*c*x)/((a + b*x + c*x^2)^2*sqrt(d + k*x))".ToEntity().Integrate("x")` | `integral(...)` | 1,075 characters | +| `"(b + 2*c*x)*(d + k*x)^(3/2)/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 4,054 characters | + ### An odd power of a quadratic's derivative over a power of the quadratic is integrated in the quadratic **Shorter answers, and answers where there were none.** `(b + 2c x)^9/(a + b x + c x^2)^3` was answered through the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index f6bb2f8db..3a7a8a3b4 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -19342,6 +19342,82 @@ Entity TakeOut(Entity node) return inTermsOfU.Substitute(u, Number.Integer.One / x); } + /// + /// A power of a linear beside the derivative of a quadratic, over a whole power of that + /// quadratic, by parts once: Q' L^m/Q^n with u = L^m and + /// dv = Q'/Q^n dx is -L^m/((n - 1) Q^(n - 1)) + m e/(n - 1) * integral L^(m - 1)/Q^(n - 1), + /// for L = d + e x, and the power of the quadratic is one lower in what is left. + /// + /// + /// Rubi's 1.2.1.3 (b + 2 c x) sqrt(d + e x)/(a + b x + c x^2)^2 and its kin took + /// thirty seconds or ran past the budget, the partial fractions taking the symbolic + /// quadratic apart twice over; what is left over its first power is answered in a third of + /// a second -- a whole power of the linear too, `(b + 2 c x) (d + e x)^4/(a + b x + c x^2)^2` + /// ran past the budget there. The numerator is read up to a constant multiple of Q'. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveAPowerOfALinearBesideTheDerivativeOfAQuadraticOverItsPower(Entity expr, Entity.Variable x, bool integrateByParts) + { + var (numerator, denominator) = Functions.SingleQuotient.Of(expr); + Entity? quadratic = null; + var power = 0; + Entity? linear = null; + Entity exponent = Number.Integer.Zero; + Entity? derivative = null; + Entity constant = Number.Integer.One; + foreach (var (side, below) in new[] { (numerator, false), (denominator, true) }) + foreach (var factor in Mulf.LinearChildren(side)) + { + if (!factor.ContainsNode(x)) + { + constant = below ? constant / factor : constant * factor; + continue; + } + var (@base, raised) = factor is Powf(var b, var r) && !r.ContainsNode(x) ? (b, r) : (factor, (Entity)Number.Integer.One); + if (below && quadratic is null && raised is Number.Integer { EInteger: var whole } && whole.CompareTo(EInteger.FromInt32(2)) >= 0 + && whole.CanFitInInt32() && TreeAnalyzer.TryGetPolyQuadratic(@base, x, out var leading, out _, out _) && !VanishesIdentically(leading)) + { + quadratic = @base; + power = whole.ToInt32Unchecked(); + continue; + } + if (!TreeAnalyzer.TryGetPolyLinear(@base, x, out var slope, out _) || VanishesIdentically(slope)) + return null; + // The derivative once, above the bar; the linear any number of times, either side. + if (!below && derivative is null && raised == Number.Integer.One && (linear is null || linear != @base)) + { + derivative = @base; + continue; + } + if (linear is not null && linear != @base) + return null; + linear = @base; + exponent = below ? exponent - raised : exponent + raised; + } + if (quadratic is null || derivative is null || linear is null + || !TreeAnalyzer.TryGetPolyQuadratic(quadratic, x, out var a, out var bq, out _) + || !TreeAnalyzer.TryGetPolyLinear(derivative, x, out var s, out var t) + || !TreeAnalyzer.TryGetPolyLinear(linear, x, out var e, out _)) + return null; + // The derivative of Q is 2 a x + b; the numerator's linear is lambda times it. + var lambda = (s / (2 * a)).InnerSimplified; + if (!VanishesIdentically(t - lambda * bq)) + return null; + exponent = exponent.InnerSimplified; + // A negative whole power of the linear is a rational function whose power of the + // linear parts would raise: `(b + 2 c x)/((d + e x) Q^3)` went from 6,220 characters + // to 14,351, the partial fractions answering it whole as before. + if (exponent == Number.Integer.Zero || exponent is Number.Integer { EInteger.Sign: < 0 }) + return null; + var lower = power - 1; + var outside = -MathS.Pow(linear, exponent) / (lower * MathS.Pow(quadratic, Number.Integer.Create(lower))); + var left = exponent * e * MathS.Pow(linear, (exponent - 1).InnerSimplified) / (lower * MathS.Pow(quadratic, Number.Integer.Create(lower))); + if (Integration.ComputeAsAQuestionOfItsOwn(left.InnerSimplified, x, integrateByParts) is not { } rest) + return null; + var answer = (constant * lambda * (outside + rest)).InnerSimplified; + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + } + /// /// A root of a product beside a root of one of its factors, or a symbolic power of one /// beside a symbolic power of a factor, the product split: diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 8c9302082..49f6fbd56 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -666,6 +666,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // the question asked only, and the polynomial term below writes `1/u` as `u^(-1)` and // asks that a level down: `1/(e^atanh(a x) (c - c/(a x))^(3/2))` reached it nowhere. if ((answer = IndefiniteIntegralSolver.SolveByWritingEachLinearOnce(expr, x, integrateByParts)) is { }) return answer; + // A power of a linear beside the derivative of a quadratic over a whole power of it, by + // parts once, before anything takes the symbolic quadratic apart. + if ((answer = IndefiniteIntegralSolver.SolveAPowerOfALinearBesideTheDerivativeOfAQuadraticOverItsPower(expr, x, integrateByParts)) is { }) return answer; // A root or a power of a product beside a power of one of its factors, the product // split: `(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)` is `K (a + b x^2)^2/sqrt(a - b x^2)`. // Early, since what reads the product whole is slower and longer: Rubi's 1.2.1.2:2218, diff --git a/Sources/Tests/UnitTests/Calculus/TheDerivativeOfAQuadraticOverItsPowerIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/TheDerivativeOfAQuadraticOverItsPowerIntegralTest.cs new file mode 100644 index 000000000..7691ba53f --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/TheDerivativeOfAQuadraticOverItsPowerIntegralTest.cs @@ -0,0 +1,59 @@ +// +// 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 linear beside the derivative of a quadratic over a whole power of the + /// quadratic, by parts once. Rubi's 1.2.1.3 (b + 2 c x) sqrt(d + e x)/(a + b x + c x^2)^2, + /// written here with k for e, which is Euler's number. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class TheDerivativeOfAQuadraticOverItsPowerIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + // Pinned before it is differentiated, as the answers the rest of the integral writes + // carry signs and cases read where their arguments are numbers. + var pinned = integral.Substitute("C", 0); + Entity original = integrand.ToEntity(); + foreach (var (name, value) in pins) + { + pinned = pinned.Substitute(name, value); + original = original.Substitute(name, value); + } + var derivative = pinned.Differentiate("x"); + foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(difference / scale < 1e-8, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + [Theory] + [InlineData("(b+2*c*x)*sqrt(d+k*x)/(a+b*x+c*x^2)^2")] + [InlineData("(b+2*c*x)/((a+b*x+c*x^2)^2*sqrt(d+k*x))")] + [InlineData("(b+2*c*x)*(d+k*x)^4/(a+b*x+c*x^2)^2")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 2), ("b", 1.5), ("c", 1.25), ("d", 1.25), ("k", 2)); + DifferentiatesBack(integrand, ("a", -1), ("b", 2), ("c", 2), ("d", -0.5), ("k", 1.5)); + } + } +}