diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 40ce09a83..5527d2d78 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -984,6 +984,23 @@ written in `u = c + d x` first. Rubi's 1.1.3.2, 1.2.3.2 and 1.3.1 write whole se | `"(c*m + d*m*x)^3/(a + b*(c + d*x)^3)^2".ToEntity().Integrate("x")` | `integral(...)` | `m^3` times a rational function of `c + d x`, the logarithms and the arctangent | | `"(c + d*x)^4/(a + b*(c + d*x)^2 + k*(c + d*x)^4)^2".ToEntity().Integrate("x")` | `integral(...)` | a rational function of `c + d x` and arctangents | +### A trigonometric function times its reciprocal is integrated + +**Answers where there were none, and shorter ones where 2.5.0 had them.** `sin(x) csc(x)` and +`tan(x) cot(x)` were declined: the rule for products of powers of the six functions reads them as +powers of the sine and cosine, and refused a product whose powers cancel. That product is the +constant wherever it is defined, and its integral the constant times `x`. `cos(x)^2 sec(x)^2`, +`csc(x)^2 (1 + sin(x)^2)` and Rubi's 4.7.7 `(1 + cos(x)^2) sec(x)^2`, which 2.5.0 answered at length +and the unreleased master declined, are answered again, more briefly +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sin(x)*csc(x)".ToEntity().Integrate("x")` | `integral(...)` | `x` | +| `"tan(x)*cot(x)".ToEntity().Integrate("x")` | `integral(...)` | `x` | +| `"cos(x)^2*sec(x)^2".ToEntity().Integrate("x")` | `cos(x)^2 tan(x) + x - sin(2x)/2` | `x` | +| `"(1 + cos(x)^2)*sec(x)^2".ToEntity().Integrate("x")` | `cos(x)^2 tan(x) + x - sin(2x)/2 + tan(x)` | `x + tan(x)` | + ### A root of a square inside a sum is integrated on each side of the square's zero **Answers where there were none.** `1/(1 + (x^2)^(3/2))` is `1/(1 + x^3)` for a positive `x` and diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 87e936df7..b4bc1decd 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -4428,11 +4428,17 @@ private static (Entity Constant, bool Plus, bool IsSine)? ReadAsOnePlusMinusAFun { if (!asked && !Integration.AnsweringTheQuestionAsked) return null; - if (!TryReadSineCosinePowers(expr, out var argument, out var sinePower, out var cosinePower, out var factor, out var throughAProduct)) + if (!TryReadSineCosinePowers(expr, out var argument, out var sinePower, out var cosinePower, out var factor, out var throughAProduct, + aConstantProduct: true)) return null; if (!TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _) || rate.Evaled == 0) return null; - if (IntegrateAPowerOfSineTimesAPowerOfCosine(argument, sinePower, cosinePower, factor, rate) is not { } integral) + // Powers that cancel, `cos(x)^2 sec(x)^2`, are the constant wherever the integrand is + // defined, and its integral is the constant times x. + var integral = sinePower.IsZero && cosinePower.IsZero + ? factor * x + : IntegrateAPowerOfSineTimesAPowerOfCosine(argument, sinePower, cosinePower, factor, rate); + if (integral is null) return null; if (!throughAProduct) return integral; @@ -7580,8 +7586,14 @@ private static bool TryReadSineCosinePowers( /// and whether a fractional power of a product was read as the product of the powers: /// true only up to a constant on each interval where the factors keep their signs. /// + /// + /// A product whose powers cancel, sin(u) csc(u), is read only where + /// asks for it: it is the constant where it is + /// defined, and the readers that go on to divide by a power have nothing to divide. + /// private static bool TryReadSineCosinePowers( - Entity expr, out Entity argument, out ERational sinePower, out ERational cosinePower, out Entity factor, out bool throughAProduct) + Entity expr, out Entity argument, out ERational sinePower, out ERational cosinePower, out Entity factor, out bool throughAProduct, + bool aConstantProduct = false) { Entity? common = null; var sine = ERational.Zero; @@ -7594,7 +7606,7 @@ private static bool TryReadSineCosinePowers( cosinePower = cosine; factor = constant; throughAProduct = distributed; - return read && common is not null && !(sine.IsZero && cosine.IsZero); + return read && common is not null && (aConstantProduct || !(sine.IsZero && cosine.IsZero)); bool Agrees(Entity candidate) { diff --git a/Sources/Tests/UnitTests/Calculus/TrigonometricTimesItsReciprocalIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/TrigonometricTimesItsReciprocalIntegralTest.cs new file mode 100644 index 000000000..9ac18249d --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/TrigonometricTimesItsReciprocalIntegralTest.cs @@ -0,0 +1,52 @@ +// +// 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 product of trigonometric powers whose powers of the sine and cosine cancel, + /// cos(x)^2 sec(x)^2, is the constant wherever it is defined, and its integral the + /// constant times x: it was declined, and with it (1 + cos(x)^2) sec(x)^2, Rubi's 4.7.7. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class TrigonometricTimesItsReciprocalIntegralTest + { + [Theory] + [InlineData("sin(x)*csc(x)")] + [InlineData("cos(x)^2*sec(x)^2")] + [InlineData("tan(x)*cot(x)")] + [InlineData("(1 + cos(x)^2)*sec(x)^2")] + [InlineData("csc(x)^2*(1 + sin(x)^2)")] + public void IsTheConstantTimesX(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + foreach (var at in new[] { -2.3, -1.1, -0.4, 0.4, 1.1, 2.3 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + /// The constant times x itself, not a function that is x only on one interval. + [Theory] + [InlineData("cos(x)^2*sec(x)^2")] + [InlineData("tan(x)*cot(x)")] + public void IsX(string integrand) + => Assert.Equal(MathS.Var("x"), integrand.ToEntity().Integrate("x").Substitute("C", 0).InnerSimplified); + } +}