diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 875f47412..abfa92ca4 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -491,6 +491,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 power of a multiple of a quadratic's derivative beside a power of the quadratic is a binomial + +**Improvement, not silent.** `(b d + 2 c d x)^m (a + b x + c x^2)^p`, with a power that is not whole, +was declined or ran out of time. Under `t = b d + 2 c d x` the quadratic is +`(t^2/d^2 - (b^2 - 4 a c))/(4 c)`, so the integrand is a binomial in `t`. That binomial is asked with +its constant term named by a symbol of its own, since with `a - b^2/(4 c)` written in it the rules for +a binomial ran out of time too. Rubi's 1.2.1.2 +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(a+b*x+c*x^2)^(3/2)/(b*d+2*c*d*x)^3".Integrate("x")` | `integral(...)` | the antiderivative | +| `"(a+b*x+c*x^2)^(1/2)/(b*d+2*c*d*x)^7".Integrate("x")` | `integral(...)` | the antiderivative | +| `"(b*d+2*c*d*x)^(5/2)/(a+b*x+c*x^2)^3".Integrate("x")` | `integral(...)` | the antiderivative | + ### A polynomial over a power of a quadratic beside the root of another is integrated `P/(A^k sqrt(B))`, `A` and `B` two different quadratics and `k` at least two, was left diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 8fc728331..031d3595f 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -14072,6 +14072,88 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x) return assumed == Entity.Boolean.True ? answer : answer.Provided(assumed); } + /// + /// A power of a linear that is a constant multiple of a quadratic's derivative, beside a + /// power of the quadratic, under the linear as the variable: with t = λ (b + 2 c x) + /// the quadratic a + b x + c x^2 is (t^2/λ^2 - Δ)/(4 c), with + /// Δ = b^2 - 4 a c, so t^m Q^p is a binomial in t. Rubi's 1.2.1.2, + /// (b d + 2 c d x)^m (a + b x + c x^2)^p, which it substitutes the same way. + /// + /// + /// The substitution is affine, so the integrand in t is the integrand exactly. The + /// quadratic is written in t from its coefficients rather than by substituting: + /// substituted, it keeps a linear term that is zero as a value and not as written, and the + /// rules for a binomial read α + β t^2. Only where a power is not whole: a rational + /// function is the partial fractions'. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveByTheDerivativeOfAQuadraticAsTheVariable(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAskedOrOneBelow) + return null; + Entity constant = Number.Integer.One; + (Entity Base, Entity Exponent)? linear = null, quadratic = null; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (!factor.ContainsNode(x)) + { + constant = underneath ? constant / factor : constant * factor; + continue; + } + var (@base, exponent) = factor is Powf(var b, var e) && !e.ContainsNode(x) ? (b, e) : (factor, (Entity)Number.Integer.One); + if (underneath) + exponent = (-exponent).InnerSimplified; + if (!TreeAnalyzer.TryGetPolynomial(@base, x, out var read) || read.Count == 0 || read.Keys.Any(k => k.Sign < 0) + || read.Values.Any(coefficient => coefficient.ContainsNode(x))) + return null; + var degree = read.Keys.Max()!; + if (degree.Equals(EInteger.One) && linear is null) + linear = (@base, exponent); + else if (degree.Equals(EInteger.FromInt32(2)) && quadratic is null) + quadratic = (@base, exponent); + else + return null; + } + if (linear is not var (l, m) || quadratic is not var (q, p) || m is Number.Integer && p is Number.Integer) + return null; + if (!TreeAnalyzer.TryGetPolyLinear(l, x, out var l1, out var l0) + || !TreeAnalyzer.TryGetPolyQuadratic(q, x, out var c2, out var c1, out var c0) + || VanishesIdentically(c2) || VanishesIdentically(l1) + // With no linear term the integrand is a binomial in x already, and taken as one in + // t it would be this question again with t scaled. + || VanishesIdentically(c1) + // The linear is a multiple of 2 c x + b where its root is the vertex's. + || !VanishesIdentically(2 * c2 * l0 - c1 * l1)) + return null; + var lambda = Reduced(l1 / (2 * c2)); + // Q = (t^2/λ^2 - Δ)/(4 c) = α + β t^2, each constant named by a symbol of its own where + // it is a compound of the coefficients, and written back into the answer: the rules for + // a binomial answer `t^(-7) sqrt(c t^2 + k)` in milliseconds, and with + // `a - b^2/(4 c)` for `k` they ran out of time. + var alpha = Reduced(-(c1 * c1 - 4 * c2 * c0) / (4 * c2)); + var beta = Reduced(1 / (4 * c2 * lambda * lambda)); + var t = Variable.CreateUnique(expr, "t_deriv"); + var named = new List<(Variable Name, Entity Value)>(); + Entity Named(Entity value) + { + if (value is Number or Variable || value is Mulf(Number, Variable) || value is Powf(Variable, Number)) + return value; + var name = Variable.CreateUnique(expr + t + named.Aggregate((Entity)Number.Integer.Zero, (sum, pair) => sum + pair.Name), "k_named"); + named.Add((name, value)); + return name; + } + var inT = Reduced(constant / l1) * MathS.Pow(t, m) * MathS.Pow(Named(alpha) + Named(beta) * MathS.Sqr(t), p); + if (Integration.ComputeAsAQuestionOfItsOwn(inT, t, integrateByParts) is not { } answer + || answer.Nodes.Any(node => node == MathS.NaN)) + return null; + foreach (var (name, value) in named) + answer = answer.Substitute(name, value); + return answer.Substitute(t, l); + + static Entity Reduced(Entity constant) + => constant.Vars.Any() ? Functions.PartialFractions.InLowestTermsOverTheSymbols(constant) : constant.InnerSimplified; + } + /// /// A whole power of a product in which the variable stands beside a constant, written /// as the product of the powers: (c x)^2 is c^2 x^2, exact for a whole diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index dec7b78a0..5c9f91770 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -662,6 +662,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => if ((answer = IndefiniteIntegralSolver.SolveAReciprocalOfAnInverseTrigonometricFunction(expr, x, integrateByParts)) is { }) return answer; // A fractional power of a perfect square is the power of the modulus, sgn(P) P^(2r). if ((answer = IndefiniteIntegralSolver.SolveByTakingARootOfAPerfectSquare(expr, x, integrateByParts)) is { }) return answer; + // A power of a multiple of a quadratic's derivative beside a power of the quadratic is + // a binomial in the derivative: `(b d + 2 c d x)^m (a + b x + c x^2)^p`. + if ((answer = IndefiniteIntegralSolver.SolveByTheDerivativeOfAQuadraticAsTheVariable(expr, x, integrateByParts)) is { }) return answer; // x^(n - 1) g(x^n) with a symbolic n is g(u)/n under u = x^n. if ((answer = IndefiniteIntegralSolver.SolveByAPowerOfTheVariableTimesAFunctionOfItsPower(expr, x, integrateByParts)) is { }) return answer; // A whole power of a product of a constant and the variable, as the product of diff --git a/Sources/Tests/UnitTests/Calculus/DerivativeOfAQuadraticAsTheVariableTest.cs b/Sources/Tests/UnitTests/Calculus/DerivativeOfAQuadraticAsTheVariableTest.cs new file mode 100644 index 000000000..80e02dbbe --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/DerivativeOfAQuadraticAsTheVariableTest.cs @@ -0,0 +1,56 @@ +// +// 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 multiple of a quadratic's derivative beside a power of the quadratic, Rubi's + /// 1.2.1.2 (b d + 2 c d x)^m (a + b x + c x^2)^p: under t = b d + 2 c d x the + /// quadratic is (t^2/d^2 - (b^2 - 4 a c))/(4 c), and the integrand a binomial in t. + /// #718 + /// + /// + /// Each was declined, or ran past the corpus's budget; the binomial with a compound constant + /// term ran out of time too, and is asked with the constant named by a symbol of its own. + /// Checked by differentiating back with the symbols pinned, at points on both sides of the + /// derivative's root where the integrand is real. + /// + [Trait("Area", "Calculus")] + public sealed class DerivativeOfAQuadraticAsTheVariableTest + { + [Theory] + [InlineData("(a + b*x + c*x^2)^(3/2)/(b*d + 2*c*d*x)^3")] + [InlineData("(a + b*x + c*x^2)^(1/2)/(b*d + 2*c*d*x)^7")] + [InlineData("(a + b*x + c*x^2)^(5/2)/(b*d + 2*c*d*x)^5")] + [InlineData("(b*d + 2*c*d*x)^(5/2)/(a + b*x + c*x^2)^3")] + public void IsABinomialInTheDerivative(string integrand) + { + var integral = integrand.ToEntity().Integrate("x").Substitute("C", 0); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pin(Entity e) => e.Substitute("a", 2.3).Substitute("b", 1.1).Substitute("c", 0.7).Substitute("d", 1.9); + var derivative = Pin(integral).Differentiate("x"); + var original = Pin(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -3.1, -1.4, 0.3, 0.9, 1.7 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + if (want.IsNaN || Math.Abs((double)want.ImaginaryPart) > 1e-12 * Math.Max(1, Math.Abs((double)want.RealPart))) + continue; + var got = derivative.Substitute("x", at).EvalNumerical(); + compared++; + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + Assert.True(difference < 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}"); + } + Assert.True(compared >= 3, $"only {compared} points where {integrand} is real"); + } + } +}