diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index e8604ab5f..ec3ab26df 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -826,6 +826,21 @@ those now. Each answer holds on both sides of zero, an odd root of a negative be
| `"x^4*(a + b*x^3)^(1/3)/(c + d*x^3)".ToEntity().Integrate("x")` | `integral(...)` | the same, beside a rational function of it |
| `"x/((1 + x^3)^(2/3)*(2 + x^3))".ToEntity().Integrate("x")` | `integral(...)` | the same with numbers |
+### A rational function of the tangent of a linear argument with symbols in it is not simplified under the linear substitution
+
+**Answers where there were none.** The substitution search reaches `tan(g + h x)` through `u = g + h x`,
+and simplified the quotient of the integrand by `h` in `u` before asking for its integral: with the
+nine symbols of Rubi's 4.3.4.2 in it that took about half a minute, for an expression that is the
+integrand with its argument renamed and holds nothing to simplify. Under a linear candidate, where
+the integrand in `u` is a function of the tangent alone and holds no imaginary unit, the quotient
+is handed on as it is
+([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"(a + b*tan(g + h*x))*(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/(c + d*tan(g + h*x))^2".ToEntity().Integrate("x")` | `integral(...)` after 33 s | the antiderivative, in 4.5 s |
+| `"(c + d*tan(g + h*x))^3*(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/(a + b*tan(g + h*x))^3".ToEntity().Integrate("x")` | no answer within a minute and a half | the antiderivative, in 18 s |
+
### A rational function of `x^n` beside a power of a symbolic binomial is integrated without a search
**Answers where there were none.** A rational function of `x^n` beside `(c + d x^n)^(k - 1/n)` is a
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index b382d6f0d..8bf2e2a27 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -23649,6 +23649,17 @@ private static bool HoldsAnEvenRootBesides(Entity expr, Entity radicand, Entity.
return false;
}
+ ///
+ /// Whether every function of in is its tangent
+ /// or its cotangent: sums, products, quotients and powers of those, and of anything free of
+ /// .
+ ///
+ private static bool IsAFunctionOfTheTangentAlone(Entity expr, Entity.Variable u)
+ => expr.Nodes.All(node => node is Sumf or Minusf or Mulf or Divf or Powf or Number or Variable
+ || !node.ContainsNode(u)
+ || node is Tanf(var argument) && argument == u
+ || node is Cotanf(var cotangentArgument) && cotangentArgument == u);
+
internal static Entity? SolveBySubstitution(Entity expr, Entity.Variable x, bool integrateByParts = true)
{
// A rational function over written linear factors with symbols in their
@@ -23800,7 +23811,20 @@ private static bool HoldsAnEvenRootBesides(Entity expr, Entity radicand, Entity.
&& expr.Complexity <= (expr.Vars.Any(v => v != x) ? LargestSymbolicIntegrandCollected : LargestIntegrandOfferedSums)
? WithThePowersOfXCollected(Functions.SingleQuotient.Combine(expr / duDx), x)
: source / duDx;
- integrandInU = SimplifiedWithoutTheImaginaryUnit(InTermsOf(quotient, u, uSub, x), expr);
+ // Under a linear candidate the quotient is the integrand with its argument
+ // renamed, over a constant, and where it is a function of the tangent alone
+ // there is nothing in it for the simplifier to find:
+ // `(a + b tan(e + f x))(A + B tan(e + f x) + C tan(e + f x)^2)/(c + d tan(e + f x))^2`
+ // under `u = e + f x` spent 28 of its 32 s being simplified, nine symbols and
+ // nothing to cancel. Only there: beside a sine, a secant, a logarithm or the
+ // imaginary unit the simplified form is what the rules after this read, and
+ // `cot(c + d x)^8/(a + a sin(c + d x))` went from one second to seventy without it.
+ var inTermsOfU = InTermsOf(quotient, u, uSub, x);
+ integrandInU = !inTermsOfU.ContainsNode(x)
+ && TreeAnalyzer.TryGetPolyLinear(u, x, out var slope, out var offset) && !slope.ContainsNode(x) && !offset.ContainsNode(x)
+ && IsAFunctionOfTheTangentAlone(inTermsOfU, uSub) && !HoldsTheImaginaryUnit(inTermsOfU)
+ ? Functions.PartialFractions.Bare(inTermsOfU.InnerSimplified)
+ : SimplifiedWithoutTheImaginaryUnit(inTermsOfU, expr);
// A factor written on both sides of the bar cancelled, where x survived:
// the one-level simplification leaves `u/((a w + b)^2 p u)` as it is, and
// the candidate was refused for the u it did not cancel.
diff --git a/Sources/Tests/UnitTests/Calculus/LinearArgumentSubstitutionTest.cs b/Sources/Tests/UnitTests/Calculus/LinearArgumentSubstitutionTest.cs
new file mode 100644
index 000000000..73c596001
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/LinearArgumentSubstitutionTest.cs
@@ -0,0 +1,54 @@
+//
+// 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 rational function of the tangent of a linear argument with symbols for its
+ /// coefficients, Rubi's 4.3.3.1 and 4.3.4.2. The substitution search reaches the tangent
+ /// through u = g + h x, and under a linear candidate the quotient by its derivative is
+ /// the integrand with its argument renamed over a constant: there is nothing in it to
+ /// simplify, and simplifying it with nine symbols in it was most of the time each of these
+ /// took.
+ /// #718
+ ///
+ ///
+ /// Checked by differentiating back with the symbols pinned after the integral is taken, at
+ /// points where the tangent and every factor below the bar stay away from their poles and
+ /// zeros.
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class LinearArgumentSubstitutionTest
+ {
+ private static readonly double[] Points = { 0.2, 0.5, 0.8 };
+
+ [Theory]
+ [InlineData("(a + b*tan(g + h*x))*(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/(c + d*tan(g + h*x))^2")]
+ [InlineData("(a + b*tan(g + h*x))*(A + B*tan(g + h*x))/(c + d*tan(g + h*x))^2")]
+ [InlineData("(A + B*tan(g + h*x) + K*tan(g + h*x)^2)/((a + b*tan(g + h*x))*(c + d*tan(g + h*x)))")]
+ public void ARationalFunctionOfTheTangentOfALinear(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ Assert.DoesNotContain("integral(", integral.Stringize());
+ Entity Pin(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 2.1).Substitute("d", 0.9)
+ .Substitute("g", 0.3).Substitute("h", 1.1).Substitute("A", 0.5).Substitute("B", 1.7).Substitute("K", 0.4);
+ var derivative = Pin(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pin(integrand.ToEntity());
+ foreach (var point in Points)
+ {
+ var expected = original.Substitute("x", point).EvalNumerical().RealPart.EDecimal.ToDouble();
+ var actual = derivative.Substitute("x", point).EvalNumerical().RealPart.EDecimal.ToDouble();
+ Assert.True(Math.Abs(expected - actual) < 1e-8 * Math.Max(1, Math.Abs(expected)),
+ $"d/dx of the antiderivative of {integrand} is {actual} at x = {point}, where the integrand is {expected}");
+ }
+ }
+ }
+}