diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 40ce09a83..67fcb552a 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -826,6 +826,19 @@ read them as nonzero. They are now decided over one bar, expanded, and at pinned
| `"e^(3*acoth(a*x))/(c-a*c*x)^3".Integrate("x")`, and over `(c - a c x)^4` | left unevaluated | an antiderivative in `sqrt((a x + 1)/(a x - 1))` |
| `"e^(2*acoth(a*x))*sqrt(c-a*c*x)/x".Integrate("x")`, and over `x^2` | left unevaluated | an antiderivative in `sqrt(c - a c x)`, by cases on the sign of `c` |
+### Two tangents of arguments a constant apart are written apart
+
+**Answers where there were none.** `tan(a + b x) tan(c + b x)` was declined, with the secants,
+cotangents and cosecants the same way and the products whose arguments add to a constant, Rubi's
+4.7.7. By the addition formulas each such product is the functions of the two arguments apart:
+`tan(A) tan(B) = cot(A - B) (tan(A) - tan(B)) - 1` and its kin, for `A - B` or `A + B` a constant
+that is not a multiple of `pi` ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"tan(a + b*x)*tan(c + b*x)".ToEntity().Integrate("x")` | `integral(...)` | `cot(a - c) (ln(cos(c + b x)) - ln(cos(a + b x)))/b - x` |
+| `"sec(c - b*x)*sec(a + b*x)".ToEntity().Integrate("x")` | `integral(...)` | `csc(a + c) (ln(cos(c - b x)) - ln(cos(a + b x)))/b` |
+
### A trigonometric function of an imaginary multiple of a logarithm is integrated in exponentials
**Answers where there were none.** `tan(a + i ln(x))` and `sin(a + ln(c x^2) sqrt(-1/4))` were
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 87e936df7..83c30026b 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -8809,6 +8809,76 @@ private static (Entity Coefficient, Entity Degree)? TheMonomial(Entity expr, Ent
return null;
}
+ ///
+ /// A product of two tangents, cotangents, secants or cosecants of linear arguments whose
+ /// difference or sum is a constant, written as the functions of each apart: with
+ /// d = A - B, tan(A) tan(B) = cot(d) (tan(A) - tan(B)) - 1.
+ ///
+ ///
+ ///
+ /// The addition formulas, read for the product: for a constant d = A - B,
+ /// tan(A) tan(B) = cot(d) (tan(A) - tan(B)) - 1,
+ /// cot(A) cot(B) = cot(d) (cot(B) - cot(A)) - 1,
+ /// sec(A) sec(B) = csc(d) (tan(A) - tan(B)) and
+ /// csc(A) csc(B) = csc(d) (cot(B) - cot(A)); for a constant s = A + B,
+ /// tan(A) tan(B) = 1 - cot(s) (tan(A) + tan(B)),
+ /// cot(A) cot(B) = 1 + cot(s) (cot(A) + cot(B)),
+ /// sec(A) sec(B) = csc(s) (tan(A) + tan(B)) and
+ /// csc(A) csc(B) = csc(s) (cot(A) + cot(B)). Each holds wherever both sides are
+ /// defined, for d or s not a multiple of pi, which a symbolic one is
+ /// taken not to be, as everywhere in this integrator. Rubi's 4.7.7 has
+ /// tan(a + b x) tan(c + b x) and the rest, and nothing read two arguments.
+ ///
+ ///
+ /// The same question in another spelling, so asked as it. A constant times the product
+ /// only, and the two functions of one kind.
+ /// https://github.com/asc-community/AngouriMath/issues/718
+ ///
+ ///
+ internal static Entity? SolveByWritingTwoFunctionsOfShiftedArgumentsApart(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ Entity constant = Number.Integer.One;
+ Entity? first = null, second = null;
+ foreach (var factor in Mulf.LinearChildren(expr))
+ {
+ if (!factor.ContainsNode(x))
+ constant = constant == Number.Integer.One ? factor : constant * factor;
+ else if (first is null)
+ first = factor;
+ else if (second is null)
+ second = factor;
+ else
+ return null;
+ }
+ if (first is null || second is null || first.GetType() != second.GetType()
+ || first is not (Tanf or Cotanf or Secantf or Cosecantf))
+ return null;
+ var a = first.DirectChildren.First();
+ var b = second.DirectChildren.First();
+ if (a == b || !TreeAnalyzer.TryGetPolyLinear(a, x, out var slopeOfA, out _) || slopeOfA.ContainsNode(x)
+ || !TreeAnalyzer.TryGetPolyLinear(b, x, out var slopeOfB, out _) || slopeOfB.ContainsNode(x))
+ return null;
+ static bool IsZero(Entity value) => value.Expand().InnerSimplified.Evaled is Number.Complex { IsZero: true };
+ var sameSlope = IsZero(slopeOfA - slopeOfB);
+ if (!sameSlope && !IsZero(slopeOfA + slopeOfB))
+ return null;
+ var shift = (sameSlope ? a - b : a + b).Expand().InnerSimplified;
+ if (shift.ContainsNode(x) || shift.Evaled is Number.Complex { IsZero: true })
+ return null;
+ Entity apart = first switch
+ {
+ Tanf => sameSlope
+ ? MathS.Cotan(shift) * (MathS.Tan(a) - MathS.Tan(b)) - 1
+ : 1 - MathS.Cotan(shift) * (MathS.Tan(a) + MathS.Tan(b)),
+ Cotanf => sameSlope
+ ? MathS.Cotan(shift) * (MathS.Cotan(b) - MathS.Cotan(a)) - 1
+ : 1 + MathS.Cotan(shift) * (MathS.Cotan(a) + MathS.Cotan(b)),
+ Secantf => MathS.Cosec(shift) * (sameSlope ? MathS.Tan(a) - MathS.Tan(b) : MathS.Tan(a) + MathS.Tan(b)),
+ _ => MathS.Cosec(shift) * (sameSlope ? MathS.Cotan(b) - MathS.Cotan(a) : MathS.Cotan(a) + MathS.Cotan(b)),
+ };
+ return Integration.ComputeAsTheSameQuestion(constant == Number.Integer.One ? apart : constant * apart, x, integrateByParts);
+ }
+
///
/// A quotient of two homogeneous polynomials in sin(u) and cos(u),
/// integrated by t = tan(u) — which turns it into a rational function of t
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index 370bcd288..364c40dfc 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -884,6 +884,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// search, which reads the quotient term by term.
if ((answer = IndefiniteIntegralSolver.SolveACosineAndASineOverAPowerOfAnother(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByWritingMultiplesOfOneLinearArgument(expr, x, integrateByParts)) is { }) return answer;
+ // Two tangents, cotangents, secants or cosecants of arguments a constant apart, written
+ // as functions of each alone by the addition formulas.
+ if ((answer = IndefiniteIntegralSolver.SolveByWritingTwoFunctionsOfShiftedArgumentsApart(expr, x, integrateByParts)) is { }) return answer;
// A constant out of a fractional power of a trigonometric factor: `sqrt(b sec(x))` is
// `sqrt(b) sqrt(sec(x))` for a positive `b`, which meets the other powers of the
// secant beside it. After the rules that answer the same shapes for any real
diff --git a/Sources/Tests/UnitTests/Calculus/TwoFunctionsOfShiftedArgumentsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/TwoFunctionsOfShiftedArgumentsIntegralTest.cs
new file mode 100644
index 000000000..db2668a32
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/TwoFunctionsOfShiftedArgumentsIntegralTest.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
+{
+ ///
+ /// Two tangents, cotangents, secants or cosecants of arguments whose difference or sum is a
+ /// constant, written as the functions of each apart by the addition formulas. Rubi's 4.7.7.
+ /// #718
+ ///
+ ///
+ /// Checked by differentiating back with a = 0.3, b = 0.9, c = 1.1, on
+ /// both sides of zero and away from the poles of every function of the three arguments.
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class TwoFunctionsOfShiftedArgumentsIntegralTest
+ {
+ [Theory]
+ [InlineData("tan(a + b*x)*tan(c + b*x)")]
+ [InlineData("tan(c - b*x)*tan(a + b*x)")]
+ [InlineData("cot(a + b*x)*cot(c + b*x)")]
+ [InlineData("cot(c - b*x)*cot(a + b*x)")]
+ [InlineData("sec(a + b*x)*sec(c + b*x)")]
+ [InlineData("sec(c - b*x)*sec(a + b*x)")]
+ [InlineData("csc(a + b*x)*csc(c + b*x)")]
+ [InlineData("csc(c - b*x)*csc(a + b*x)")]
+ public void IsWrittenApartByTheAdditionFormulas(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ Assert.DoesNotContain("integral(", integral.Stringize());
+ Entity Pinned(Entity e) => e.Substitute("a", 0.3).Substitute("b", 0.9).Substitute("c", 1.1);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ foreach (var at in new[] { -1.0, -0.8, 0.1, 0.2, 0.9, 1.0 })
+ {
+ 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}");
+ }
+ }
+ }
+}