diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 062394597..87961970a 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -975,6 +975,23 @@ the variable: `(a + i a tan(z))/(q - i q tan(z))^(3/2)`, from 4.3.2.1, ran past
| `"cot(c + d*x)^2*(k + q*tan(c + d*x))/(a + i*a*tan(c + d*x))^4".ToEntity().Integrate("x")` | `integral(...)` | powers and logarithms of `a + i a tan(c + d x)`, of `a - i a tan(c + d x)` and of `tan(c + d x)` |
| `"(a + i*a*tan(c + d*x))/(q - i*q*tan(c + d*x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | `-2 i a (q - i q tan(c + d x))^(-3/2)/(3d)`, written longer |
+### Half-odd powers of two conjugate tangent sums are integrated as the exponential they make
+
+**Answers in time where they were not.** `(a + i a tan(e + f x))^(7/2) (A + B tan(e + f x))/(c - i c tan(e + f x))^(9/2)`
+ran past the budget, with the rest of Rubi's 4.3.2.1 and 4.3.3.1 that put half-odd powers on both sums: the
+rule for one of the sums integrates in it beside a whole power of the other, and with both half-odd neither
+is. `a + i a tan(z)` is `a sec(z) e^(i z)` on the real line and `c - i c tan(z)` is `c sec(z) e^(-i z)`, so
+two powers of them adding up to a whole number `k` are a constant on every interval where they are
+continuous times `sec(z)^k e^(i (p - q) z)`, rational in `w = e^(i z)` beside any function of the tangent,
+the secant, the cosine or the sine of `z`. They are integrated in `w` now, and the answer is the integrand
+times the antiderivative in `w` over what that differentiates back to
+([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"(a+i*a*tan(pe+f*x))^(7/2)*(A+B*tan(pe+f*x))/(c-i*c*tan(pe+f*x))^(9/2)".ToEntity().Integrate("x")` | `integral(...)`; past 20 s on the unreleased master | in a tenth of a second |
+| `"1/((a+i*a*tan(pe+f*x))^(7/2)*(c-i*c*tan(pe+f*x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)`; past 20 s on the unreleased master | the same |
+
### A symbolic power of one of two conjugate tangent sums is integrated in that sum
**Answers where there were none.** `(a + i a tan(c + d x))^m (q - i q tan(c + d x))^4` and the rest
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index f9acaedcb..31ec42351 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -26706,6 +26706,107 @@ private static bool TryReadAnImaginaryTangent(Entity sum, Entity.Variable x, out
return true;
}
+ ///
+ /// Powers of the two conjugate sums a + i a tan(z) and c - i c tan(z), not both
+ /// whole and adding up to a whole number k, beside a function of the tangent, the
+ /// secant or the cosine of the same argument, integrated as the exponential they are:
+ /// a + i a tan(z) is a sec(z) e^(i z) on the real line and
+ /// c - i c tan(z) is c sec(z) e^(-i z), so the pair is a constant on every
+ /// interval where it is continuous times sec(z)^k e^(i (p - q) z), and in
+ /// w = e^(i z) the whole is a rational function of w times a power of it.
+ ///
+ ///
+ ///
+ /// (a + i a tan(x))^(7/2) (A + B tan(x))/(c - i c tan(x))^(9/2) is
+ /// e^(8 i x) (A cos(x) + B sin(x)) up to that constant, and ran past the budget with
+ /// the rest of Rubi's 4.3.2.1 and 4.3.3.1 that put half-odd powers on both sums: the rule for
+ /// one of them integrates in it beside a whole power of the other, and with both half-odd
+ /// neither is.
+ ///
+ ///
+ /// The constant is not written, as for one sum beside the secant: the answer is the integrand
+ /// times the antiderivative in w over what that differentiates back to, a quotient
+ /// constant wherever it is continuous. The antiderivative in w is checked there, where
+ /// it is the only thing computed; checked at sampled x, the quotient's constant need
+ /// not be the same on both sides of the points, and right answers were declined.
+ /// https://github.com/asc-community/AngouriMath/issues/718
+ ///
+ ///
+ internal static Entity? SolveAConjugatePairOfImaginaryTangentSums(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ if (!expr.Nodes.Any(node => node is Tanf))
+ return null;
+ Entity? argument = null;
+ Entity? plusPower = null, minusPower = null;
+ Entity constant = Number.Integer.One;
+ Entity varying = Number.Integer.One;
+ Entity rest = Number.Integer.One;
+ foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
+ {
+ if (!factor.ContainsNode(x))
+ {
+ constant = underneath ? constant / factor : constant * factor;
+ continue;
+ }
+ varying = underneath ? varying / factor : varying * factor;
+ var (@base, power) = factor is Powf(var b, var p) && !p.ContainsNode(x)
+ ? (b, p.Evaled is Number.Rational r ? r : p)
+ : (factor, (Entity)Number.Integer.One);
+ if (underneath)
+ power = power is Number.Rational numeric ? -numeric : (-power).InnerSimplified;
+ if (TryReadAnImaginaryTangent(@base, x, out var tangentOf, out var isPlus))
+ {
+ if (argument is not null && argument != tangentOf || (isPlus ? plusPower : minusPower) is not null)
+ return null;
+ argument = tangentOf;
+ if (isPlus)
+ plusPower = power;
+ else
+ minusPower = power;
+ continue;
+ }
+ rest = underneath ? rest / factor : rest * factor;
+ }
+ if (plusPower is null || minusPower is null || argument is null
+ || plusPower is Number.Integer && minusPower is Number.Integer
+ || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope))
+ return null;
+ var sum = (plusPower + minusPower).InnerSimplified;
+ if (sum is not Number && sum.Complexity <= 40)
+ sum = sum.Simplify();
+ if (sum is not Number.Integer { EInteger: var whole } || !whole.CanFitInInt32())
+ return null;
+ var k = whole.ToInt32Checked();
+ var phase = (plusPower - minusPower).InnerSimplified;
+ if (phase is not Number && phase.Complexity <= 40)
+ phase = phase.Simplify();
+ // The rest in w = e^(i z): a function of the tangent, the secant, the cosine and the sine
+ // of the argument alone, and rational in w once they are written so.
+ var w = Variable.CreateUnique(expr, "w_exp");
+ var secantInW = 2 * w / (MathS.Sqr(w) + 1);
+ var tangentInW = -MathS.i * (MathS.Sqr(w) - 1) / (MathS.Sqr(w) + 1);
+ var restInW = rest.Replace(node => node switch
+ {
+ Tanf(var inner) when inner == argument => tangentInW,
+ Secantf(var inner) when inner == argument => secantInW,
+ Cosf(var inner) when inner == argument => 1 / secantInW,
+ Sinf(var inner) when inner == argument => tangentInW / secantInW,
+ _ => node,
+ });
+ if (restInW.ContainsNode(x))
+ return null;
+ // sec(z)^k e^(i p z) R dz is (2 w/(w^2 + 1))^k w^(p - 1) R(w) dw/i, with dz = dw/(i w).
+ var inW = Functions.SingleQuotient.Combine(
+ (k == 0 ? Number.Integer.One : MathS.Pow(secantInW, k)) * MathS.Pow(w, (phase - 1).InnerSimplified) * restInW).InnerSimplified;
+ if (Integration.ComputeAsAQuestionOfItsOwn(inW, w, integrateByParts) is not { } inWAnswer
+ || inWAnswer.Nodes.Any(node => node == MathS.NaN)
+ || !Functions.PartialFractions.HoldsAtSampledPoints(inWAnswer.Differentiate(w), inW, w))
+ return null;
+ var exponential = MathS.Pow(MathS.e, MathS.i * argument);
+ var differentiatesBackTo = (k == 0 ? Number.Integer.One : MathS.Pow(MathS.Sec(argument), k)) * MathS.Pow(exponential, phase) * rest;
+ return constant * varying * inWAnswer.Substitute(w, exponential) / (MathS.i * slope * differentiatesBackTo);
+ }
+
///
/// A cos(y) + i A sin(y) below the bar, written as the exponential it is:
/// A e^(i y), and A cos(y) - i A sin(y) as A e^(-i y). Beside a power
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index 05c4b58ba..20c6b4da5 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -675,6 +675,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// product and takes it apart, and so answers (c sec)^(5/2)/(a + i a tan)^(5/2) with the
// wrong constant wherever the cosine is negative.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAnImaginaryTangentBesideAPowerOfTheSecant(expr, x, integrateByParts)) is { }) return answer;
+ // And powers of the two conjugate sums, both not whole, as the exponential they make.
+ if ((answer = IndefiniteIntegralSolver.SolveAConjugatePairOfImaginaryTangentSums(expr, x, integrateByParts)) is { }) return answer;
// A rational function of the tangent beside a power of a + i a tan(z), in that sum.
if ((answer = IndefiniteIntegralSolver.SolveInTheImaginarySumOfAConstantAndATangent(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginaryTangentAsAnExponential(expr, x, integrateByParts)) is { }) return answer;
diff --git a/Sources/Tests/UnitTests/Calculus/ConjugatePairOfImaginaryTangentSumsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ConjugatePairOfImaginaryTangentSumsIntegralTest.cs
new file mode 100644
index 000000000..3bc968e70
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ConjugatePairOfImaginaryTangentSumsIntegralTest.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
+{
+ ///
+ /// Powers of the conjugate sums a + i a tan(x) and c - i c tan(x), both half-odd,
+ /// beside a function of the tangent: a + i a tan(x) is a sec(x) e^(i x) and
+ /// c - i c tan(x) is c sec(x) e^(-i x), so the pair is a constant times
+ /// sec(x)^k e^(i (p - q) x), rational in e^(i x). Rubi's 4.3.2.1 and 4.3.3.1. The
+ /// integrands are complex for a real x, and compared as complex numbers.
+ /// #718
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ConjugatePairOfImaginaryTangentSumsIntegralTest
+ {
+ [Theory]
+ [InlineData("(a + i*a*tan(x))^(7/2)*(A + B*tan(x))/(c - i*c*tan(x))^(9/2)")]
+ [InlineData("(a + i*a*tan(x))^(3/2)*(A + B*tan(x))/(c - i*c*tan(x))^(3/2)")]
+ [InlineData("(a + i*a*tan(x))^(3/2)/(c - i*c*tan(x))^(3/2)")]
+ [InlineData("1/((a + i*a*tan(x))^(7/2)*(c - i*c*tan(x))^(3/2))")]
+ [InlineData("(A + B*tan(x))/((a + i*a*tan(x))^(3/2)*(c - i*c*tan(x))^(3/2))")]
+ public void AsTheExponentialTheyMake(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ Assert.DoesNotContain("integral(", integral.Stringize());
+ Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("c", 0.7).Substitute("A", 0.4).Substitute("B", 1.1);
+ 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();
+ 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}");
+ }
+ }
+}