diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 6f06b1ea1..a1f9d34ed 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -396,6 +396,20 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take | `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters | | `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters | +### A symbolic power of a sine, cosine, secant or cosecant is integrated where it is the derivative of a product + +**Answers where there were none.** `sec(c + d x)^m (m - (1 + m) sec(c + d x)^2)` was declined, and so were five more +of Rubi's 4.1.4.1, 4.2.4.1 and 4.5.4.1. `f^m (alpha + beta f^2)` for `f` a sine, cosine, secant or cosecant of a linear +is the derivative of `cos sin^(m + 1)`, `sin cos^(m + 1)`, `tan sec^m` or `cot csc^m` where its bracket is a multiple of +that derivative's, and is now integrated so ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sec(c+d*x)^m*(-q*m/(1+m)+q*sec(c+d*x)^2)".ToEntity().Integrate("x")` | `integral(...)` | 75 characters | +| `"sec(c+d*x)^m*(A-A*(1+m)*sec(c+d*x)^2/m)".ToEntity().Integrate("x")` | `integral(...)` | 54 characters | +| `"sec(k+f*x)^m*(m-(1+m)*sec(k+f*x)^2)".ToEntity().Integrate("x")` | `integral(...)` | 54 characters | +| `"sin(k+f*x)^m*(1+m-(2+m)*sin(k+f*x)^2)".ToEntity().Integrate("x")` | `integral(...)` | 71 characters | + ### A power of a sine or hyperbolic sine over a binomial in a power of it is split over the binomial's roots **Answers where there were none.** `sinh(c + d x)^4/(a + b sinh(c + d x)^3)` was declined, and so were eight more of diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index e8c3bc62b..176887f8a 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -29317,6 +29317,53 @@ bool HoldsItsValue(Entity factor) return (s * logarithm - remainder).InnerSimplified; } + /// + /// f^m (alpha + beta f^2) for a symbolic m and f a sine, cosine, secant or + /// cosecant of a linear u, where it is the derivative of a product: with u' = 1, + /// (cos sin^(m + 1))' = sin^m ((m + 1) - (m + 2) sin^2), + /// (sin cos^(m + 1))' = cos^m ((m + 2) cos^2 - (m + 1)), + /// (tan sec^m)' = sec^m ((m + 1) sec^2 - m) and (cot csc^m)' = -csc^m ((m + 1) csc^2 - m). + /// + /// + /// The integrand is read as f^m times a polynomial in f with only the constant and + /// square terms, alpha + beta f^2, which matches p0 + p2 f^2 above where + /// beta p0 = alpha p2; the answer is (alpha/p0) G/u'. Rubi's 4.1.4.1, 4.2.4.1 and 4.5.4.1 + /// sec(c + d x)^m (m - (1 + m) sec(c + d x)^2) and the like were declined. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveASymbolicPowerOfATrigonometricFunctionAsTheDerivativeOfAProduct(Entity expr, Entity.Variable x) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + // The power with a symbol in its exponent, of a sine, cosine, secant or cosecant of a linear. + var power = expr.Nodes.OfType().FirstOrDefault(node => node.Exponent.Vars.Any() && !node.Exponent.ContainsNode(x) + && node.Base is Sinf or Cosf or Secantf or Cosecantf); + if (power is not Powf(var function, var m)) + return null; + var argument = function switch { Sinf(var y) => y, Cosf(var y) => y, Secantf(var y) => y, Cosecantf(var y) => y, _ => null }; + if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || Functions.PartialFractions.IsZeroAsAValue(slope)) + return null; + var s = Variable.CreateUnique(expr, "s_trig"); + // The power taken off by writing 1 for it: `s^m (...)/s^m` is not cancelled by the simplification. + var inS = expr.Substitute(power, Number.Integer.One).Substitute(function, s).InnerSimplified; + if (inS.ContainsNode(x) || !TreeAnalyzer.TryGetPolynomial(inS.Expand(), s, out var read) + || read.Keys.Any(key => !key.IsZero && !key.Equals(EInteger.FromInt32(2)))) + return null; + var alpha = read.TryGetValue(EInteger.Zero, out var a0) ? a0 : Number.Integer.Zero; + var beta = read.TryGetValue(EInteger.FromInt32(2), out var b2) ? b2 : Number.Integer.Zero; + var (p0, p2, product) = function switch + { + Sinf => (m + 1, -(m + 2), MathS.Cos(argument) * MathS.Pow(function, m + 1)), + Cosf => (-(m + 1), m + 2, MathS.Sin(argument) * MathS.Pow(function, m + 1)), + Secantf => (-m, m + 1, MathS.Tan(argument) * MathS.Pow(function, m)), + _ => (m, -(m + 1), MathS.Cotan(argument) * MathS.Pow(function, m)), + }; + if (Functions.PartialFractions.IsZeroAsAValue(p0.InnerSimplified) + || !Functions.PartialFractions.IsZeroAsAValue((beta * p0 - alpha * p2).Expand().InnerSimplified)) + return null; + return (alpha / (p0 * slope) * product).InnerSimplified; + } + /// /// A power of a constant multiple of a radicand that a logarithm holds under a root, /// written over that radicand: acosh(c x) is ln(c x + sqrt(c^2 x^2 - 1)), diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index fa128879d..39247aeed 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -664,6 +664,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A sum free of the variable that comes to a number multiplied out, as that number, // before anything reads a coefficient or an exponent in it: `2 + 2 a - 2 (1 + a)`. if ((answer = IndefiniteIntegralSolver.SolveByFoldingConstantSumsToTheirNumbers(expr, x, integrateByParts)) is { }) return answer; + // A symbolic power of a sine, cosine, secant or cosecant times `alpha + beta f^2`, where it is + // the derivative of `cos sin^(m + 1)`, `sin cos^(m + 1)`, `tan sec^m` or `cot csc^m`. + if ((answer = IndefiniteIntegralSolver.SolveASymbolicPowerOfATrigonometricFunctionAsTheDerivativeOfAProduct(expr, x)) is { }) return answer; // A power of a sine, cosine or hyperbolic one over a binomial in a power of it, over the // binomial's roots, before the rules that spent the budget on `sinh(y)^4/(a + b sinh(y)^3)`. if ((answer = IndefiniteIntegralSolver.SolveAPowerOverABinomialInAPowerOfASineOverItsRoots(expr, x)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/ASymbolicPowerOfATrigonometricFunctionAsADerivativeIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ASymbolicPowerOfATrigonometricFunctionAsADerivativeIntegralTest.cs new file mode 100644 index 000000000..592e38a03 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ASymbolicPowerOfATrigonometricFunctionAsADerivativeIntegralTest.cs @@ -0,0 +1,69 @@ +// +// 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 symbolic power of a sine, cosine, secant or cosecant times alpha + beta f^2, where it is + /// the derivative of cos sin^(m + 1), sin cos^(m + 1), tan sec^m or cot csc^m: + /// Rubi's 4.1.4.1 and 4.5.4.1. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ASymbolicPowerOfATrigonometricFunctionAsADerivativeIntegralTest + { + 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}"); + } + } + + // d with the sign that keeps the argument real at every sampled x, and |d| below 2/2.3^2 + // so that the inverse sine and cosine stay inside [-1, 1]; off it the exact checks hold too. + [Theory] + [InlineData("sec(c+d*x)^m*(-q*m/(1+m)+q*sec(c+d*x)^2)")] + [InlineData("sec(c+d*x)^m*(A-A*(1+m)*sec(c+d*x)^2/m)")] + [InlineData("sin(c+d*x)^m*(1+m-(2+m)*sin(c+d*x)^2)")] + [InlineData("cos(x)^m*(-(1+m)+(2+m)*cos(x)^2)")] + [InlineData("csc(x)^m*(m-(1+m)*csc(x)^2)")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("A", 0.6), ("q", 1.25), ("c", 0.3), ("d", 0.75), ("m", 2.5)); + DifferentiatesBack(integrand, ("A", -0.75), ("q", 0.4), ("c", -1), ("d", -0.5), ("m", 1.0 / 3)); + } + + [Fact] + public void IsNotClaimedWhereTheBracketIsNotTheDerivative() + { + // beta p0 != alpha p2: (1 + m) sec^2 - (m + 1) is not the derivative of tan sec^m. + var integral = "sec(x)^m*(1+m-(1+m)*sec(x)^2)".ToEntity().Integrate("x").Stringize(); + Assert.DoesNotContain("tan(x) * sec(x) ^ m", integral); + } + } +}