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);
+ }
+}