diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 671eff440..f17cfdfe0 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -3189,6 +3189,25 @@ were read as numbers only. In `w = e^(i z)` the integral is a power of `w` besid
| `"(k*sec(c + d*x))^(-4 - n)*(a + i*a*tan(c + d*x))^n".ToEntity().Integrate("x")` | `integral(...)` | the integrand times powers of `e^(i (c + d x))` from `n - 4` to `n + 4`, each over its exponent |
| `"(a + i*a*tan(c + d*x))^n/(k*sec(c + d*x))^n".ToEntity().Integrate("x")` | `integral(...)` | the integrand over `i d n` |
+### A rational function of the tangent beside roots of linears in it is integrated over the linears of `1 + tan^2`
+
+**Answers where there were none.** `(c + d tan(z))^(5/2) (A + B tan(z) + C tan(z)^2)/sqrt(a + b tan(z))` and six more of
+Rubi's 4.3.4.2, and `1/((a + i a tan(z))^3 (c + d tan(z))^(3/2))` and two more of 4.3.2.1, were declined. In
+`t = tan(z)`, under the root of the quotient of the two linears or the root of the one, the `1 + t^2` that `dz` puts
+below the bar is a quartic with symbols in every coefficient. It is now written as its linears `(1 + i t)(1 - i t)` and
+the rational part split over them, so that each term is the roots over one power of a linear, which the rules for a
+root of a linear over a linear answer; the sum is checked by its derivative in `t`. Only two roots of linears with
+distinct roots, beside a polynomial or a linear below the bar, or one root below the bar beside a whole power of
+`a ± i a tan(z)`. Their neighbours that the unreleased master answered come through it as well, shorter and sooner
+([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"(c + d*tan(g + h*x))^(5/2)*(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/sqrt(a + b*tan(g + h*x))".ToEntity().Integrate("x")` | `integral(...)` | 4,315 characters |
+| `"(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/((a + b*tan(g + h*x))^(3/2)*(c + d*tan(g + h*x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 11,405 characters |
+| `"1/((a + i*a*tan(g + h*x))^3*(c + d*tan(g + h*x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 6,498 characters |
+| `"(c + d*tan(g + h*x))^(5/2)*(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/(a + b*tan(g + h*x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)`; 41,892 characters, in 10 s, on the unreleased master | 29,265 characters, in 1.4 s |
+
### A rational function of the tangent beside a power of `a + i a tan` is integrated in that sum
**Answers where there were none.** `(A + B tan(c + d x))/sqrt(a + i a tan(c + d x))` and the rest of
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index c91066d1e..8edbb879d 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -33505,6 +33505,174 @@ static bool IsAWholeNumberOfSteps(ERational difference)
return Integration.ComputeAsAQuestionOfItsOwn(expanded.Expand().InnerSimplified, x, integrateByParts);
}
+ ///
+ /// A rational function of tan(z) beside roots of linears in it, integrated in
+ /// t = tan(z) term by term over the partial fractions of its rational part, with the
+ /// 1 + t^2 that dz = dt/(f (1 + t^2)) puts below the bar written as the linears
+ /// of its roots, (1 + i t)(1 - i t): each term is the roots over one power of a linear,
+ /// which the rules for a root of a linear over a linear answer.
+ ///
+ ///
+ ///
+ /// Rubi's 4.3.4.2 (c + d tan(z))^(5/2) (A + B tan(z) + C tan(z)^2)/sqrt(a + b tan(z)) and
+ /// 4.3.2.1 1/((a + i a tan(z))^3 (c + d tan(z))^(3/2)) ran past the budget, as their
+ /// neighbours with smaller powers did not: in t, under the root of the quotient of the two
+ /// linears or the root of the one, 1 + t^2 is a quartic with symbols in every coefficient.
+ /// Over its linears the same integrands are three terms, or a few more, of a second or less each.
+ ///
+ ///
+ /// Only two roots of linears with distinct roots beside a polynomial or a linear below the
+ /// bar, or one root below the bar beside a whole power of a +- i a tan(z) below it, which
+ /// shares a root with 1 + t^2; neither root a root of t or of 1 +- i t, which
+ /// the rules for a root of the tangent and for the imaginary sum read, and below the bar linears
+ /// only. Two roots alone, and a root above the bar beside the imaginary sum, the substitution in
+ /// the root answers in as few terms and as soon, and are left to it. The decomposition is exact,
+ /// its coefficients constants in the symbols, and the sum is checked by its derivative in
+ /// t before it is returned: each term's answer is its own rule's.
+ /// https://github.com/asc-community/AngouriMath/issues/718
+ ///
+ ///
+ internal static Entity? SolveARationalFunctionOfTheTangentBesideRootsOfLinearsInIt(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ Entity? argument = null;
+ foreach (var node in expr.Nodes)
+ {
+ if (!node.ContainsNode(x))
+ continue;
+ if (node is Sinf or Cosf or Secantf or Cosecantf)
+ return null;
+ if (node is not (Tanf or Cotanf))
+ continue;
+ var inner = node.DirectChildren.First();
+ if (argument is null)
+ argument = inner;
+ else if (argument != inner)
+ return null;
+ }
+ if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var rate, out _)
+ || rate.ContainsNode(x) || VanishesIdentically(rate))
+ return null;
+ var t = Variable.CreateUnique(expr, "t_tangent");
+ var inT = expr.Replace(node => node switch
+ {
+ Tanf(var y) when y == argument => t,
+ Cotanf(var y) when y == argument => 1 / t,
+ _ => node,
+ });
+ if (inT.ContainsNode(x))
+ return null;
+
+ // `A + c t` with `c = +-i A`, which vanishes at a root of `1 + t^2`.
+ static bool IsAnImaginarySum(Entity slope, Entity intercept)
+ {
+ var ratio = Functions.PartialFractions.Bare((slope / intercept).InnerSimplified);
+ if (ratio.Evaled is not Number.Complex)
+ ratio = Functions.PartialFractions.Bare(ratio.Simplify());
+ return ratio.Evaled == MathS.i.Evaled || ratio.Evaled == (-MathS.i).Evaled;
+ }
+ // The integrand read as a constant, roots of linears, whole powers of polynomials above the
+ // bar and of linears below it.
+ Entity constant = Number.Integer.One;
+ var above = new List();
+ var below = new List();
+ var degreeBelow = 0;
+ var imaginarySumBelow = false;
+ var roots = new List<(Entity Linear, Entity Slope, Entity Intercept, Number.Rational Power)>();
+ bool Read(Entity node, EInteger times)
+ {
+ switch (node)
+ {
+ case Mulf(var left, var right):
+ return Read(left, times) && Read(right, times);
+ case Divf(var dividend, var divisor):
+ return Read(dividend, times) && Read(divisor, times.Negate());
+ case Powf(var @base, Number.Integer whole) when @base.ContainsNode(t):
+ return Read(@base, times.Multiply(whole.EInteger));
+ case Powf(var @base, Number.Rational power) when @base.ContainsNode(t):
+ {
+ var exponent = power.ERational.Multiply(ERational.FromEInteger(times));
+ if (exponent.Denominator.Equals(EInteger.One))
+ return Read(@base, exponent.Numerator);
+ if (!TreeAnalyzer.TryGetPolyLinear(@base, t, out var slope, out var intercept)
+ || slope.ContainsNode(t) || intercept.ContainsNode(t)
+ || VanishesIdentically(slope) || VanishesIdentically(intercept) || IsAnImaginarySum(slope, intercept))
+ return false;
+ roots.Add((@base, slope, intercept, Number.Rational.Create(exponent)));
+ return true;
+ }
+ case var free when !free.ContainsNode(t):
+ constant *= MathS.Pow(free, Number.Integer.Create(times));
+ return true;
+ default:
+ if (!TreeAnalyzer.TryGetPolynomial(node, t, out var monomials)
+ || monomials.Values.Any(coefficient => coefficient.ContainsNode(t)) || monomials.Keys.Any(degree => degree.Sign < 0)
+ || !times.CanFitInInt32())
+ return false;
+ if (times.Sign > 0)
+ {
+ above.Add(times.Equals(EInteger.One) ? node : MathS.Pow(node, Number.Integer.Create(times)));
+ return true;
+ }
+ if (!TreeAnalyzer.TryGetPolyLinear(node, t, out var linearSlope, out var linearIntercept)
+ || linearSlope.ContainsNode(t) || linearIntercept.ContainsNode(t) || VanishesIdentically(linearSlope))
+ return false;
+ var multiplicity = times.Negate();
+ // `(A + i A t)^k` is `A^k (1 + i t)^k`, a whole power: with the constant out
+ // in front the split is over numbers, and its terms' answers a few lines each,
+ // where with `A` in the linear they came to ten times the length.
+ if (!VanishesIdentically(linearIntercept) && IsAnImaginarySum(linearSlope, linearIntercept))
+ {
+ imaginarySumBelow = true;
+ constant /= MathS.Pow(linearIntercept, Number.Integer.Create(multiplicity));
+ node = (1 + Functions.PartialFractions.Bare((linearSlope / linearIntercept).Simplify()) * t).InnerSimplified;
+ }
+ below.Add(multiplicity.Equals(EInteger.One) ? node : MathS.Pow(node, Number.Integer.Create(multiplicity)));
+ degreeBelow += multiplicity.ToInt32Checked();
+ return true;
+ }
+ }
+ if (!Read(inT, EInteger.One) || degreeBelow > 12)
+ return null;
+ // Two roots beside a polynomial or a linear below, or one root below the bar beside a power
+ // of the imaginary sum below it: two roots alone over `1 + t^2`, and a root above the bar,
+ // the root's substitution answers as soon, and in the same few terms.
+ if (roots.Count == 2
+ ? above.Count == 0 && below.Count == 0
+ || VanishesIdentically(roots[0].Slope * roots[1].Intercept - roots[1].Slope * roots[0].Intercept)
+ : roots.Count != 1 || !imaginarySumBelow || roots[0].Power.ERational.Sign > 0)
+ return null;
+
+ // Divided by `1 + t^2` and the linears as written, and the proper rest split over the
+ // linears with `1 + t^2` written as its own two.
+ static Entity Product(IEnumerable factors) =>
+ factors.Aggregate((Entity?)null, (all, factor) => all is null ? factor : all * factor) ?? Number.Integer.One;
+ var numerator = Product(above);
+ var written = Product(below.Append(1 + MathS.Sqr(t)));
+ var overTheLinears = Product(below.Append(1 + MathS.i * t).Append(1 - MathS.i * t));
+ if (OverEachLinear(numerator, written, overTheLinears, degreeBelow + 2, t) is not { } terms)
+ return null;
+ var ofTheRoots = Product(roots.Select(root => MathS.Pow(root.Linear, root.Power)));
+ Entity total = Number.Integer.Zero;
+ foreach (var term in terms)
+ {
+ if (term.Evaled is Number.Complex { IsZero: true })
+ continue;
+ if (Integration.ComputeAsAQuestionOfItsOwn((term * ofTheRoots).InnerSimplified, t, integrateByParts) is not { } integrated
+ || integrated.Nodes.Any(node => node == MathS.NaN))
+ return null;
+ total += integrated;
+ }
+ // Checked by its derivative, and where the intervals cannot tell -- the arctangent of a
+ // complex argument that the answer to `(c + d t)^(5/2)/((a + b t)^(5/2) (1 + i t))` holds,
+ // right to ninety digits at every point -- beside two roots refused only where it
+ // differs at a point where the integrand, then real, is.
+ var integrandInT = numerator / written * ofTheRoots;
+ if (!Functions.PartialFractions.DerivativeHoldsAtSampledPoints(total, integrandInT, t)
+ && (roots.Count == 1 || Functions.PartialFractions.DerivativeDiffersWhereReal(total, integrandInT, t)))
+ return null;
+ return (constant / rate * total).Substitute(t, MathS.Tan(argument));
+ }
+
///
/// A rational function of tan(z) beside a power of S = a + c tan(z) with
/// c = ±i a, below the bar or not whole, integrated in S: tan(z) is
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index d55feb5a3..6f987d3d7 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -817,6 +817,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// And an exponential of a hyperbolic sine or cosine of a linear beside a function of it
// over its derivative, under u = twice that sine or cosine, which arrive as exponentials.
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfAHyperbolicFunctionBesideItsDerivative(expr, x, integrateByParts)) is { }) return answer;
+ // A rational function of the tangent beside two roots of linears in it, or one beside a
+ // whole power of a + i a tan(z) below the bar, over the linears of 1 + tan^2: in the root
+ // of the linears' quotient 1 + tan^2 is a symbolic quartic, and a search past the budget.
+ if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOfTheTangentBesideRootsOfLinearsInIt(expr, x, integrateByParts)) is { }) return answer;
// `A + i A tan(z)` is `A e^(i z)/cos(z)`, which beside a polynomial is a shape the
// closed rules answer, where the imaginary unit in the coefficient is read by none.
// The same identity beside a power of the secant, where the powers are not whole and add
diff --git a/Sources/Tests/UnitTests/Calculus/ARationalFunctionOfTheTangentBesideRootsOfLinearsInItIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ARationalFunctionOfTheTangentBesideRootsOfLinearsInItIntegralTest.cs
new file mode 100644
index 000000000..dc33ba2e7
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ARationalFunctionOfTheTangentBesideRootsOfLinearsInItIntegralTest.cs
@@ -0,0 +1,59 @@
+//
+// 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 beside two roots of linears in it, or one root below the
+ /// bar beside a whole power of a + i a tan, integrated in t = tan(g + h x) over the
+ /// partial fractions of its rational part with 1 + t^2 written as (1 + i t)(1 - i t).
+ /// In the root of the linears' quotient 1 + t^2 is a symbolic quartic, and every one of
+ /// these ran past the budget. Rubi's 4.3.4.2 and 4.3.2.1.
+ /// #718
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ARationalFunctionOfTheTangentBesideRootsOfLinearsInItIntegralTest
+ {
+ [Theory]
+ [InlineData("(c + d*tan(g + h*x))^(5/2)*(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/sqrt(a + b*tan(g + h*x))")]
+ [InlineData("(c + d*tan(g + h*x))^(5/2)*(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/(a + b*tan(g + h*x))^(3/2)")]
+ [InlineData("(a + b*tan(g + h*x))^(5/2)*(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/(c + d*tan(g + h*x))^(5/2)")]
+ [InlineData("(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/((a + b*tan(g + h*x))^(3/2)*(c + d*tan(g + h*x))^(3/2))")]
+ [InlineData("(A + B*tan(g + h*x) + C*tan(g + h*x)^2)/((a + b*tan(g + h*x))^(3/2)*(c + d*tan(g + h*x))^(5/2))")]
+ [InlineData("1/((a + i*a*tan(g + h*x))^3*(c + d*tan(g + h*x))^(3/2))")]
+ [InlineData("1/((a + i*a*tan(g + h*x))^2*(c + d*tan(g + h*x))^(5/2))")]
+ [InlineData("1/((a + i*a*tan(g + h*x))^3*(c + d*tan(g + h*x))^(5/2))")]
+ public void OverTheLinearsOfOnePlusTheTangentSquared(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Assert.True(text.Length < 30000, $"{text.Length} characters of answer for {integrand}");
+ Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 2.1).Substitute("d", 0.4)
+ .Substitute("A", 0.9).Substitute("B", 1.7).Substitute("C", 0.6).Substitute("g", 0.2).Substitute("h", 1.1);
+ var derivative = Pinned(integral).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { -0.9, -0.5, 0.1, 0.4, 0.7, 1.0 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ if (want.IsNaN)
+ continue;
+ compared++;
+ 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}");
+ }
+ Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
+ }
+ }
+}