diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 5d77674d5..a608c6f18 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -854,6 +854,23 @@ left as it was, since the sum is real on both sides of 0 and `x^(j p)` is not. R
| `"1/(x*sqrt(b*x^(2/3) + a*x))".ToEntity().Integrate("x")` | `integral(...)` | `K` times an antiderivative in `sqrt(b + a x^(1/3))` |
| `"x/sqrt(1 + 1/(c*x)^2)".ToEntity().Integrate("x")` | `integral(...)` | `sgn(x) (x sqrt(x^2 + 1/c^2)/2 - ln(2 x + 2 sqrt(x^2 + 1/c^2))/(2 c^2))`, for any `c` but 0 |
+### A quadratic sharing a root off the real line with a linear beside it is written over that root
+
+**Answers where there were none.** `1/((a + i a tan(x)) (c + d tan(x)))`, Rubi's 4.3.2.1, is under the
+tangent substitution a rational function over `(1 + i u)(c + d u)(1 + u^2)`, and `1 + u^2` is
+`(1 + i u)(1 - i u)`: the written factors share a linear. The splits read written factors as coprime,
+and the divisors that take shared factors apart are taken over the rationals, which the imaginary unit
+is not; it was declined, and with a square of `c + d tan(x)` it ran past a minute. With a symbol in the
+denominator, a quadratic that has a root off the real line in common with a linear beside it is now
+written as its leading coefficient times the linears of its two roots, and the shared linear taken as
+one power ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"1/((a + i*a*tan(x))*(c + d*tan(x)))".ToEntity().Integrate("x")` | `integral(...)` | a quotient by `tan(x) - i` and logarithms |
+| `"1/((a + i*a*tan(x))^2*(c + d*tan(x)))".ToEntity().Integrate("x")` | `integral(...)` | the same |
+| `"1/((a + i*a*tan(x))*(c + d*tan(x))^2)".ToEntity().Integrate("x")` | `integral(...)` | the same |
+
### A rational function of a sine over a symbolic quadratic in it is integrated over the two roots
**Improvement, not silent.** `sin(x)/(a + b sin(x) + c sin(x)^2)` and everything rational in
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index d7b12536b..ece7d48b8 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -701,6 +701,17 @@ is var (multiple, leftover)
?? Integration.ComputeIndefiniteIntegral(above / below, x, integrateByParts)) is { } overWhatTheyShare)
return overWhatTheyShare;
+ // And a linear with a root off the real line that a quadratic beside it shares, which
+ // the divisors over the rationals above cannot see: `1 + i u` beside `1 + u^2`, which is
+ // `(1 + i u)(1 - i u)`, what the tangent substitution makes of Rubi's 4.3.2.1
+ // `1/((a + i a tan(x)) (c + d tan(x)))`. The quadratic is written over the linears of
+ // its roots and the shared one taken as one power, coprime and squarefree for the
+ // splits below.
+ if (OverAComplexRootSharedWithAQuadratic(denominator, x) is { } overTheSharedRoot
+ && (SolveByPartialFractions(numerator / overTheSharedRoot, x, integrateByParts)
+ ?? Integration.ComputeIndefiniteIntegral(numerator / overTheSharedRoot, x, integrateByParts)) is { } overTheComplexRoot)
+ return overTheComplexRoot;
+
// Written linear factors with symbols in their coefficients, two or more, one of
// them to a power: decomposed over the written factors, the coefficients read
// off derivatives at the roots and each a line. First, before the respellings
@@ -12779,6 +12790,72 @@ private static bool IsASumOfMonomials(Entity expr, Entity.Variable x)
return changed ? product : null;
}
+ ///
+ /// with every quadratic factor that has a root off the real line
+ /// in common with a linear factor beside it written as its leading coefficient times the
+ /// linears of its two roots, each linear with such a root written as its slope times
+ /// x - r, and the powers of one linear gathered into one;
+ /// where no quadratic shares such a root. (1 + i x)(1 + x^2) is
+ /// i (x - i)^2 (x + i).
+ ///
+ private static Entity? OverAComplexRootSharedWithAQuadratic(Entity denominator, Entity.Variable x)
+ {
+ // With numbers only, the split over the rationals and the imaginary unit answers it as
+ // it is written.
+ if (!denominator.Vars.Any(symbol => symbol != x))
+ return null;
+ var written = new List<(Entity Base, EInteger Power)>();
+ Entity constant = Number.Integer.One;
+ foreach (var factor in Mulf.LinearChildren(denominator))
+ {
+ if (!factor.ContainsNode(x))
+ {
+ constant = constant * factor;
+ continue;
+ }
+ written.Add(factor is Powf(var @base, Number.Integer n) && n.EInteger.Sign > 0 ? (@base, n.EInteger) : (factor, EInteger.One));
+ }
+ // The roots of the linears that are not real, each with the linear's slope.
+ var roots = new Dictionary();
+ foreach (var (@base, _) in written)
+ if (TreeAnalyzer.TryGetPolyLinear(@base, x, out var slope, out var offset) && !TreeAnalyzer.IsZero(slope)
+ && Functions.PartialFractions.Bare((-offset / slope).Simplify()).Evaled is Number.Complex root && root is not Number.Real)
+ roots[@base] = (root, slope);
+ if (roots.Count == 0)
+ return null;
+ var gathered = new Dictionary();
+ void Add(Entity @base, EInteger power) => gathered[@base] = gathered.TryGetValue(@base, out var before) ? before.Add(power) : power;
+ var shared = false;
+ foreach (var (@base, power) in written)
+ {
+ if (roots.TryGetValue(@base, out var linear))
+ {
+ constant = constant * MathS.Pow(linear.Slope, Number.Integer.Create(power));
+ Add((x - linear.Root).InnerSimplified, power);
+ continue;
+ }
+ if (TreeAnalyzer.TryGetPolyQuadratic(@base, x, out var a, out var b, out var c) && !TreeAnalyzer.IsZero(a)
+ && a.Evaled is Number.Complex && b.Evaled is Number.Complex && c.Evaled is Number.Complex
+ && roots.Values.FirstOrDefault(candidate => (a * candidate.Root * candidate.Root + b * candidate.Root + c).Evaled is Number.Complex { IsZero: true }) is var (root, _)
+ && root is not null)
+ {
+ shared = true;
+ var other = (-b / a - root).Evaled;
+ constant = constant * MathS.Pow(a, Number.Integer.Create(power));
+ Add((x - root).InnerSimplified, power);
+ Add((x - other).InnerSimplified, power);
+ continue;
+ }
+ Add(@base, power);
+ }
+ if (!shared)
+ return null;
+ Entity product = constant;
+ foreach (var pair in gathered)
+ product = product * (pair.Value.Equals(EInteger.One) ? pair.Key : MathS.Pow(pair.Key, Number.Integer.Create(pair.Value)));
+ return product;
+ }
+
///
/// over , the denominator's
/// written factors taken apart over their greatest common divisors -- with one another,
diff --git a/Sources/Tests/UnitTests/Calculus/ComplexRootSharedWithAQuadraticIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ComplexRootSharedWithAQuadraticIntegralTest.cs
new file mode 100644
index 000000000..e30cf1cc3
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ComplexRootSharedWithAQuadraticIntegralTest.cs
@@ -0,0 +1,53 @@
+//
+// 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 linear with a root off the real line that a quadratic beside it shares, with symbols in
+ /// the rest: 1/((1 + i u)(c + d u)(1 + u^2)), where 1 + u^2 is
+ /// (1 + i u)(1 - i u), the tangent substitution's form of Rubi's 4.3.2.1
+ /// 1/((a + i a tan(x)) (c + d tan(x))), declined or past a minute. The integrands are
+ /// complex for a real x, and compared as complex numbers.
+ /// #718
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ComplexRootSharedWithAQuadraticIntegralTest
+ {
+ [Theory]
+ [InlineData("1/((1 + i*x)*(c + d*x)*(1 + x^2))")]
+ [InlineData("1/((a + i*a*tan(x))*(c + d*tan(x)))")]
+ [InlineData("1/((a + i*a*tan(x))^2*(c + d*tan(x)))")]
+ [InlineData("1/((a + i*a*tan(x))*(c + d*tan(x))^2)")]
+ public void OverTheSharedRoot(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ Assert.DoesNotContain("integral(", integral.Stringize());
+ Assert.DoesNotContain("NaN", integral.Stringize());
+ Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("c", 1.1).Substitute("d", 0.6);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ if (want.IsNaN)
+ continue;
+ compared++;
+ 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}");
+ }
+ Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
+ }
+ }
+}