From 682780345cd24e74011cc4dfab26618a664b52b3 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 18:00:38 +0000 Subject: [PATCH 1/2] A trigonometric function times its reciprocal is integrated The rule for products of powers of the six trigonometric functions reads them as sin^p cos^q, and refused a product whose powers cancel: sin(x) csc(x), cos(x)^2 sec(x)^2 and tan(x) cot(x) were declined. That product is the constant wherever it is defined, and its integral the constant times x. The reader takes such a product where the rule asks for it; its other readers, which divide by a power, still decline it. (1 + cos(x)^2) sec(x)^2, Rubi's 4.7.7, is answered again. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 15 ++++++ .../Integration/IndefiniteIntegralSolver.cs | 20 +++++-- ...onometricTimesItsReciprocalIntegralTest.cs | 52 +++++++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/TrigonometricTimesItsReciprocalIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dc39a1fe6..97e010724 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -818,6 +818,21 @@ 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.** `sin(x) csc(x)`, `cos(x)^2 sec(x)^2` 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`; `(1 + cos(x)^2) sec(x)^2`, Rubi's 4.7.7, answered by +2.5.0 and declined on the unreleased master, is 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` | +| `"(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 875a31f1c..87e575361 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -4000,11 +4000,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; @@ -7152,8 +7158,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; @@ -7166,7 +7178,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); + } +} From e607c7c7ac194274cd9728d935e4daae9586d781 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 23:02:48 +0000 Subject: [PATCH 2/2] The breaking-changes entry says which of the products 2.5.0 answered 2.5.0 answered cos(x)^2 sec(x)^2, csc(x)^2 (1 + sin(x)^2) and (1 + cos(x)^2) sec(x)^2 at length, and declined only sin(x) csc(x) and tan(x) cot(x); the unreleased master declined all five. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 97e010724..13dd42488 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -820,17 +820,19 @@ written in `u = c + d x` first. Rubi's 1.1.3.2, 1.2.3.2 and 1.3.1 write whole se ### A trigonometric function times its reciprocal is integrated -**Answers where there were none.** `sin(x) csc(x)`, `cos(x)^2 sec(x)^2` 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`; `(1 + cos(x)^2) sec(x)^2`, Rubi's 4.7.7, answered by -2.5.0 and declined on the unreleased master, is answered again, more briefly +**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