diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 06ad30b60..71c1b2b42 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -608,6 +608,19 @@ no numeric root finder applies, and its discriminant vanishes identically. Such | `"1/(x^3-3*b*x+2*b^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 122 characters | | `"1/(-9*b*x+9*x^3+2*b^(3/2)*sqrt(3))".ToEntity().Integrate("x")` | `integral(...)` | 176 characters | +### An exponential of an arctangent beside a power of its quadratic is answered where it is an exact derivative + +**Answers where there were none.** `e^(i n arctan(a x)) x^2 (c + a^2 c x^2)^(-1 - n^2/2)` was left unevaluated: no rule +reads a polynomial beside the exponential and a symbolic power of `c + a^2 c x^2`. Where the integrand is the +derivative of `e^(N arctan(a x)) (α + β x) (c + a^2 c x^2)^(p + 1)` for a quadratic polynomial beside it, the two +coefficients are solved for and that is the answer, checked by differentiating it back +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"e^(i*n*atan(a*x))*x^2*(c+a^2*c*x^2)^(-1-n^2/2)".ToEntity().Integrate("x")` | `integral(...)` | 148 characters | +| `"e^(2*atan(x))*(3+8/3*x+5/3*x^2)*(1+x^2)^(-2/3)".ToEntity().Integrate("x")` | `integral(...)` | `e ^ (2 * arctan(x)) * (1 + x) * (1 + x ^ 2) ^ (1/3) + C` | + ### An exponential of i arctan below the bar is read as a negative multiple **Answers where there were none.** `x^2/(e^(i arctan(a x)) (c + a^2 c x^2)^(3/2))` was left unevaluated: the exponential diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 8f143a53c..e41ab1075 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -9746,6 +9746,69 @@ node is Powf(var @base, Number.Rational power) && power is not Number.Integer && return ratio; } + /// + /// An exponential of a multiple of arctan(a x) times a quadratic polynomial times a + /// power of c + a^2 c x^2, where it is an exact derivative: + /// (e^(N arctan(a x)) (alpha + beta x) Q^s)' is + /// c e^(N arctan(a x)) Q^(s - 1) (a N alpha + beta + (a N beta + 2 s a^2 alpha) x + a^2 beta (1 + 2 s) x^2) + /// for Q = c + a^2 c x^2, since Q^s/(1 + a^2 x^2) = c Q^(s - 1). The + /// constant and squared coefficients give alpha and beta; the linear one is + /// the condition. Rubi's 5.3.6:504, e^(i n arctan(a x)) x^2 (c + a^2 c x^2)^(-1 - n^2/2), + /// meets it exactly, and was left unevaluated: as powers of the two linears its exponents + /// do not sum to a whole number. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveAnExactDerivativeOfAnExponentialOfAnArctangent(Entity expr, Entity.Variable x) + { + Entity? multiple = null, slope = null, quadratic = null, power = null; + Entity polynomial = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(expr)) + { + if (factor is Powf(var e, var exponent) && e == MathS.e && exponent.ContainsNode(x)) + { + if (multiple is not null) + return null; + var inverse = exponent.Nodes.OfType().FirstOrDefault(); + if (inverse is null || !TreeAnalyzer.TryGetPolyLinear(inverse.Argument, x, out var a, out var b) || !TreeAnalyzer.IsZero(b)) + return null; + var placeholder = Variable.CreateUnique(exponent, "u_atan"); + var inThePlaceholder = exponent.Replace(node => node == inverse ? placeholder : node); + if (inThePlaceholder.ContainsNode(x) || !TreeAnalyzer.TryGetPolyLinear(inThePlaceholder, placeholder, out var n, out var constantPart) + || !TreeAnalyzer.IsZero(constantPart) || TreeAnalyzer.IsZero(n)) + return null; + (multiple, slope) = (n, a); + } + else if (factor is Powf(var q, var p) && !p.ContainsNode(x) && p is not Number.Integer && q.ContainsNode(x)) + { + if (quadratic is not null) + return null; + (quadratic, power) = (q, p); + } + else + polynomial *= factor; + } + if (multiple is null || slope is null || quadratic is null || power is null) + return null; + if (!TreeAnalyzer.TryGetPolyQuadratic(quadratic, x, out var c2, out var c1, out var c0) || !TreeAnalyzer.IsZero(c1) || TreeAnalyzer.IsZero(c0) + || !VanishesIdentically((c2 - MathS.Sqr(slope) * c0).Expand())) + return null; + if (!TreeAnalyzer.TryGetPolyQuadratic(polynomial, x, out var p2, out var p1, out var p0)) + return null; + var s = (power + 1).Simplify(); + var twice = (1 + 2 * s).Simplify(); + if (TreeAnalyzer.IsZero(twice) || TreeAnalyzer.IsZero(p2)) + return null; + var beta = (p2 / (c0 * MathS.Sqr(slope) * twice)).Simplify(); + var alpha = ((p0 / c0 - beta) / (slope * multiple)).Simplify(); + if (!VanishesIdentically((c0 * (slope * multiple * beta + 2 * s * MathS.Sqr(slope) * alpha) - p1).Simplify())) + return null; + var exponential = Mulf.LinearChildren(expr).First(factor => factor is Powf(var e, _) && e == MathS.e && factor.ContainsNode(x)); + var candidate = exponential * (alpha + beta * x) * MathS.Pow(quadratic, s); + if (!AreProportionalAtSampledPoints(candidate.Differentiate(x), expr, x)) + return null; + return candidate; + } + /// /// An exponential of a symbolic multiple of i arctan(L), L linear, beside /// powers of multiples of 1 + L^2, written as powers of the two linears: diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 075450f19..7204719a2 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -670,6 +670,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // An exponential of i times an inverse trigonometric function written algebraically, at the // question asked, before the rules that spent the budget: `e^(3 i atan(a + b x))/x^2` ran out of // time where, rewritten, it is answered in half a second. It is asked again in its place below. + // An exact derivative among them first, closed: `e^(i n arctan(a x)) x^2 (c + a^2 c x^2)^(-1 - n^2/2)`. + if ((answer = IndefiniteIntegralSolver.SolveAnExactDerivativeOfAnExponentialOfAnArctangent(expr, x)) is { }) return answer; if (AnsweringTheQuestionAsked && (answer = IndefiniteIntegralSolver.SolveByWritingAnExponentialOfAnInverseAlgebraically(expr, x, integrateByParts)) is { }) return answer; // The same for a symbolic multiple of the arctangent, beside powers of its quadratic. if (AnsweringTheQuestionAsked && (answer = IndefiniteIntegralSolver.SolveAnExponentialOfASymbolicMultipleOfAnArctangentAsPowersOfTheLinears(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/AnExactDerivativeOfAnExponentialOfAnArctangentIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnExactDerivativeOfAnExponentialOfAnArctangentIntegralTest.cs new file mode 100644 index 000000000..f6c5e09d6 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnExactDerivativeOfAnExponentialOfAnArctangentIntegralTest.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 +{ + /// + /// An exponential of a multiple of arctan(a x) times a quadratic times a power of + /// c + a^2 c x^2, where it is an exact derivative: Rubi's 5.3.6 + /// e^(i n arctan(a x)) x^2 (c + a^2 c x^2)^(-1 - n^2/2), which was left unevaluated. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AnExactDerivativeOfAnExponentialOfAnArctangentIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + 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("e^(i*n*atan(a*x))*x^2*(c+a^2*c*x^2)^(-1-n^2/2)")] + [InlineData("e^(2*atan(x))*(3+8/3*x+5/3*x^2)*(1+x^2)^(-2/3)")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 1.3), ("c", 0.7), ("n", 0.4)); + DifferentiatesBack(integrand, ("a", -0.5), ("c", -1.5), ("n", 2.3)); + } + } +}