diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 2d2cbd956..34988dd16 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -541,6 +541,26 @@ quadratic with numbers in it, although beside the root each piece is a shape the | `"(2 + 5*x + x^2)*sqrt(3 + 2*x + 5*x^2)/(1 + 4*x - 7*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 5,242 characters | | `"(a + b*x + c*x^2)^(1/2)/(d + e*x + f*x^2)^2".ToEntity().Integrate("x")` | `integral(...)` | 50,758 characters, six symbols | +### A power of a perfect square with a symbolic leading coefficient is a power of its root + +**Answers where there were none.** `sqrt(c d^2 + 2 c d k x + c k^2 x^2)` was declined: the square root of +`c (d + k x)^2` was taken as `sqrt(c) |d + k x|` only for a leading coefficient whose sign was known. It +holds for every nonzero one, since `(d + k x)^2` is positive, and is taken so now. Beside a power of the +square's own linear, that linear is taken as the root, so the two powers gather into one. The reciprocal +root, which 2.5.0 answered in 411 characters and the unreleased master declined, is 61 characters now +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(c*d^2 + 2*c*d*k*x + c*k^2*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 62 characters | +| `"(c*d^2 + 2*c*d*k*x + c*k^2*x^2)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 62 characters | +| `"1/sqrt(c*d^2 + 2*c*d*k*x + c*k^2*x^2)".ToEntity().Integrate("x")` | 411 characters | 61 characters | +| `"1/(c*d^2 + 2*c*d*k*x + c*k^2*x^2)^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 74 characters | +| `"(d + k*x)^(-1 - 2*p)*(c*d^2 + 2*c*d*k*x + c*k^2*x^2)^p".ToEntity().Integrate("x")` | `integral(...)` | 103 characters | +| `"1/((d + k*x)*sqrt(b^2/(4*c) + b*x + c*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 1,277 characters | +| `"sqrt(a + a*sech(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 274 characters | +| `"asinh(sqrt(b*x^2 - 1))^3/sqrt(b*x^2 - 1)".ToEntity().Integrate("x")` | `integral(...)` | 166 characters | + ### A square of a symbolic quadratic beside two linears is answered within the budget **Answers where there were none in time.** `1/((d + k x)(f + g x)(a + b x + c x^2)^2)` was declined by diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index adeeeee44..11ef427c4 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -21417,12 +21417,17 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x) // with the answer. The other two coefficients are real or symbolic, not a // number off the line, so that `(x + h)^2` is not negative. // A whole power needs no sign of anything: (a (w + h)^2)^n is a^n (w + h)^(2n). + // And one of no known sign: for a real w + h, (w + h)^2 is positive, so + // Log(a (w + h)^2) is Log(a) + 2 ln|w + h| for every nonzero a, and + // (a (w + h)^2)^r is a^r |w + h|^(2r) -- `sqrt(c (d + k x)^2)` is + // `sqrt(c) |d + k x|` for a negative c as for a positive one. if (!whole && a.Evaled is not Number.Real { IsZero: false }) { - if (!IsPositiveForARealParameter(a, x, out var leadingAssumed, assumed) - && !IsPositiveForARealParameter((-a).InnerSimplified, x, out leadingAssumed, assumed)) + if (a.Evaled is Number.Complex and not Number.Real) return node; - assumed = leadingAssumed; + if (IsPositiveForARealParameter(a, x, out var leadingAssumed, assumed) + || IsPositiveForARealParameter((-a).InnerSimplified, x, out leadingAssumed, assumed)) + assumed = leadingAssumed; } var leading = a; if (b.Evaled is Number.Complex and not Number.Real || c.Evaled is Number.Complex and not Number.Real @@ -22058,6 +22063,21 @@ static Entity Exponent(Entity exponent) if (found is not var (square, exponent, root, leading, squareIsBelow) || above.Nodes.Concat(below.Nodes).Count(node => node == square) != 1) return null; + // A linear of the integrand that is a multiple of the root, `d + k x` beside + // `(c d^2 + 2 c d k x + c k^2 x^2)^p`, is the root itself, with the multiple's square + // taken out of the leading coefficient: its power and the square's then gather into + // one power of one linear, where `(x + d/k)^(2p) (d + k x)^(-1 - 2p)` were two. + if (root is Sumf && TreeAnalyzer.TryGetPolyLinear(root, x, out _, out var rootShift)) + foreach (var factor in Mulf.LinearChildren(above).Concat(Mulf.LinearChildren(below))) + { + var linear = factor is Powf(var @base, var power) && !power.ContainsNode(x) ? @base : factor; + if (linear == square || !TreeAnalyzer.TryGetPolyLinear(linear, x, out var slope, out var intercept) + || slope.Evaled is Number.Complex { IsZero: true } || !VanishesIdentically(intercept - slope * rootShift)) + continue; + root = linear; + leading = leading / (slope * slope); + break; + } var twice = (2 * exponent).InnerSimplified; // The root's power above the bar either way, with a negative exponent for the square // below it: `x (x + h)^(-2p)` is read below the top where `x/(x + h)^(2p)` was not. diff --git a/Sources/Tests/UnitTests/Calculus/APerfectSquareWithASymbolicLeadingCoefficientIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APerfectSquareWithASymbolicLeadingCoefficientIntegralTest.cs new file mode 100644 index 000000000..ddcbe028a --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APerfectSquareWithASymbolicLeadingCoefficientIntegralTest.cs @@ -0,0 +1,63 @@ +// +// 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 perfect square whose leading coefficient is a symbol of no known sign: + /// (c (d + k x)^2)^r is (c k^2)^r |x + d/k|^(2r) for a negative c as for a + /// positive one, since (x + d/k)^2 is positive. Rubi's 1.2.1.2 + /// sqrt(c d^2 + 2 c d e x + c e^2 x^2), written here with k for e, which is + /// Euler's number. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class APerfectSquareWithASymbolicLeadingCoefficientIntegralTest + { + 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: the answer carries `sgn(x + d/k)`, whose + // derivative is read where its argument is a number. + 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("sqrt(c*d^2+2*c*d*k*x+c*k^2*x^2)")] + [InlineData("(c*d^2+2*c*d*k*x+c*k^2*x^2)^(3/2)")] + [InlineData("1/sqrt(c*d^2+2*c*d*k*x+c*k^2*x^2)")] + [InlineData("1/(c*d^2+2*c*d*k*x+c*k^2*x^2)^(5/2)")] + [InlineData("(d+k*x)^(-1-2*p)*(c*d^2+2*c*d*k*x+c*k^2*x^2)^p")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("c", 3), ("d", 1.25), ("k", 2), ("p", 0.3)); + DifferentiatesBack(integrand, ("c", -2), ("d", -0.5), ("k", 1.5), ("p", -1.7)); + } + } +}