diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index c953e08fc..1bec563d1 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -505,6 +505,20 @@ two more of Rubi's 4.5.2.3. The two sums multiply to `-a c tan^2`, so in `u = a
| `"sec(e + f*x)*(a + a*sec(e + f*x))^m*(c - c*sec(e + f*x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 469 characters |
| `"sec(e + f*x)*(a + a*sec(e + f*x))^m*sqrt(c - c*sec(e + f*x))".ToEntity().Integrate("x")` | `integral(...)` | 268 characters |
+### A secant and a tangent in one sum below the bar are integrated in the half angle
+
+**Answers where there were none.** `1/(a + c sec(x) + b tan(x))` was declined, and so was
+`1/(a + b cot(x) + c csc(x))`, Rubi's 4.7.7. Combined over the cosine, the half-angle tangent `t` gives a denominator
+with `1 + t^2` and `1 - t^2` in every term, which was read as a quartic with a symbol in every coefficient. Those
+shared factors are now taken out of the sum first, so the numerator cancels them and a quadratic beside `1 + t^2` is
+left ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"1/(a + c*sec(x) + b*tan(x))".ToEntity().Integrate("x")` | `integral(...)` | 2,211 characters |
+| `"1/(a + b*cot(x) + c*csc(x))".ToEntity().Integrate("x")` | `integral(...)` | 2,738 characters |
+| `"1/(a + c*sec(2*x + 1) + b*tan(2*x + 1))".ToEntity().Integrate("x")` | `integral(...)`; past 290 s on the unreleased master | 2,890 characters |
+
### `a cos + i a sin` beside powers of the secant is written over the cosine
**Answers where there were none, and shorter ones.** `sec(c + d x)^5/(a cos(c + d x) + i a sin(c + d x))^2` was declined,
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 5f5414950..8a9d3ef73 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -29308,7 +29308,7 @@ static bool IsOneOfTheFour(Entity node, Entity argument)
{
var (combinedAbove, combinedBelow) = Functions.SingleQuotient.Of(combined.InnerSimplified);
integrand = (WithDegenerateBasesLowered(WithWholePowersDistributed(combinedAbove), t)
- / WithDegenerateBasesLowered(WithWholePowersDistributed(combinedBelow), t)).InnerSimplified;
+ / WithDegenerateBasesLowered(WithTheSharedFactorsOutOfEachSum(WithWholePowersDistributed(combinedBelow), t), t)).InnerSimplified;
}
else
integrand = combined.Simplify();
@@ -29369,6 +29369,96 @@ static bool NoRemainder(Entity rest)
: null;
}
+ ///
+ /// with each factor that is a sum of products written as the
+ /// factors every term shares, with in them, times the sum of what
+ /// is left: A (1 + t^2)(1 - t^2) + 2 b t (1 + t^2)(1 - t^2) is
+ /// (1 + t^2)(1 - t^2)(A + 2 b t).
+ ///
+ ///
+ /// 1/(a + c sec(x) + b tan(x)) combines over cos(x) into a denominator
+ /// with both of the half angle's factors in each term, and read as a quartic with a
+ /// symbol in every coefficient it was declined after five seconds. Its numerator is
+ /// 2 (1 - t^2)^2: with the factors out, the division below takes off the
+ /// 1 + t^2, the partial fractions cancel the 1 - t^2, and what is left
+ /// is a quadratic beside 1 + t^2.
+ ///
+ private static Entity WithTheSharedFactorsOutOfEachSum(Entity product, Entity.Variable t)
+ {
+ Entity written = Number.Integer.One;
+ foreach (var factor in Mulf.LinearChildren(product))
+ written *= factor is Sumf ? WithTheSharedFactorsOut(factor, t) : factor;
+ return written;
+ }
+
+ ///
+ /// as the factors with in them that every
+ /// term has, each to the least power any term has it in, times the sum of what is left;
+ /// as written where no term shares one.
+ ///
+ private static Entity WithTheSharedFactorsOut(Entity sum, Entity.Variable t)
+ {
+ static Dictionary Powers(Entity term, Entity.Variable t)
+ {
+ var powers = new Dictionary();
+ foreach (var factor in Mulf.LinearChildren(term))
+ {
+ var (@base, power) = factor is Powf(var raised, Number.Integer { EInteger.Sign: > 0 } exponent)
+ ? (raised, exponent.EInteger) : (factor, EInteger.One);
+ if (@base.ContainsNode(t))
+ powers[@base] = powers.TryGetValue(@base, out var before) ? before.Add(power) : power;
+ }
+ return powers;
+ }
+ // Inner sums first: in `(a (1 + t^2) 2 t + b (1 - t^2)(1 + t^2)) 2 t + c (1 + t^2)^2 2 t`,
+ // which `1/(a + b cot(x) + c csc(x))` combines into, the first term has its
+ // `1 + t^2` inside a sum of its own.
+ var terms = Sumf.LinearChildren(sum).Select(term => WithTheSharedFactorsOutOfEachSum(term, t)).ToList();
+ if (terms.Count < 2)
+ return sum;
+ var shared = Powers(terms[0], t);
+ foreach (var term in terms.Skip(1))
+ {
+ var powers = Powers(term, t);
+ foreach (var @base in shared.Keys.ToList())
+ if (powers.TryGetValue(@base, out var power))
+ shared[@base] = EInteger.Min(shared[@base], power);
+ else
+ shared.Remove(@base);
+ }
+ // A bare `t` shared by every term is a root at zero, which the partial fractions
+ // find on their own; only a sum is worth taking out.
+ if (!shared.Keys.Any(@base => @base is Sumf or Minusf))
+ return terms.Aggregate((left, right) => left + right);
+ Entity common = Number.Integer.One;
+ foreach (var pair in shared)
+ common *= pair.Value.Equals(EInteger.One) ? pair.Key : MathS.Pow(pair.Key, Number.Integer.Create(pair.Value));
+ Entity rest = Number.Integer.Zero;
+ foreach (var term in terms)
+ {
+ var left = new Dictionary(shared);
+ Entity kept = Number.Integer.One;
+ foreach (var factor in Mulf.LinearChildren(term))
+ {
+ var (@base, power) = factor is Powf(var raised, Number.Integer { EInteger.Sign: > 0 } exponent)
+ ? (raised, exponent.EInteger) : (factor, EInteger.One);
+ if (left.TryGetValue(@base, out var owed) && owed.Sign > 0)
+ {
+ var taken = EInteger.Min(owed, power);
+ left[@base] = owed.Subtract(taken);
+ power = power.Subtract(taken);
+ if (power.IsZero)
+ continue;
+ kept *= power.Equals(EInteger.One) ? @base : MathS.Pow(@base, Number.Integer.Create(power));
+ }
+ else
+ kept *= factor;
+ }
+ rest += kept;
+ }
+ return common * rest;
+ }
+
///
/// with each whole power of a product written as the product
/// of the powers, the rest as written: ((1 + t^2)(1 - t^2))^6 is
diff --git a/Sources/Tests/UnitTests/Calculus/ASecantAndATangentInOneSumIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ASecantAndATangentInOneSumIntegralTest.cs
new file mode 100644
index 000000000..194e47629
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ASecantAndATangentInOneSumIntegralTest.cs
@@ -0,0 +1,46 @@
+//
+// 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
+{
+ ///
+ /// 1/(a + c sec + b tan) and 1/(a + b cot + c csc) in the half angle's tangent,
+ /// with the factors every term of the combined denominator shares taken out. Rubi's 4.7.7.
+ /// #718
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ASecantAndATangentInOneSumIntegralTest
+ {
+ [Theory]
+ [InlineData("1/(a + c*sec(x) + b*tan(x))", 4000)]
+ [InlineData("1/(a + b*cot(x) + c*csc(x))", 4000)]
+ [InlineData("csc(x)/(a + b*cot(x) + c*csc(x))", 4000)]
+ [InlineData("1/(a + c*sec(2*x + 1) + b*tan(2*x + 1))", 4000)]
+ public void DifferentiatesBack(string integrand, int atMost)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Assert.True(text.Length < atMost, $"{text.Length} characters of answer for {integrand}");
+ Entity Pinned(Entity e) => e.Substitute("a", 2).Substitute("b", 3).Substitute("c", "5/4".ToEntity());
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ foreach (var at in new[] { "0.35", "0.6", "1.1", "1.4" })
+ {
+ var want = original.Substitute("x", at.ToEntity()).EvalNumerical();
+ var got = derivative.Substitute("x", at.ToEntity()).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)),
+ $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ }
+ }
+ }
+}