diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index e8604ab5f..1b2b09dbe 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -790,6 +790,23 @@ poles of `tan(x/2)`, as every half-angle answer.
| `"1/(a+b*cos(x)+c*cos(x)^2)".Integrate("x")` | unevaluated | the cosine form |
| `"csc(x)^2/(a-a*sin(x)^2)".Integrate("x")` | unevaluated | `-cot(x)/a` and two terms rational in `tan(x/2)` |
+### A rational function of the sine and cosine with symbols in it is no longer simplified under the half angle
+
+**Answers where there were none.** Under `t = tan(x/2)` the integrand was simplified before it was
+integrated, and with symbols in it the simplifier's search grew past any budget:
+`sec(x)^2/(a + b cos(x))^3` spent about forty seconds in it and `sec(x)^2/(a + b cos(x))^4` more than two minutes,
+where integrating what it returned takes a tenth of a second. A rational function of `t` with a
+symbol in it is now put over one bar with its whole powers of products written as products of
+powers, and not simplified further; one without a symbol, or with a root of `t` in it, is
+simplified as before
+([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"sec(x)^2/(a + b*cos(x))^3".ToEntity().Integrate("x")` | `integral(...)` | a piecewise antiderivative in `tan(x/2)` |
+| `"sec(x)^2/(a + b*cos(x))^4".ToEntity().Integrate("x")` | `integral(...)` | the same |
+| `"sec(c + d*x)^2/(a + b*cos(c + d*x))^4".ToEntity().Integrate("x")` | `integral(...)` | the same in `tan((c + d x)/2)` |
+
### A linear over a quadratic beside the root of another quadratic is integrated
`(g + h x)/(A sqrt(B))`, `A` and `B` two different quadratics, was left unevaluated wherever a
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index b382d6f0d..ab8942487 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -23348,7 +23348,29 @@ static bool IsOneOfTheFour(Entity node, Entity argument)
// 1/(1 - sin(x)) rewrote to 2/((t^2 + 1)(1 + (-2)t/(t^2 + 1))) and stopped there, one
// distribution short of 2/(t^2 - 2t + 1), which is integrated at once.
// https://github.com/asc-community/AngouriMath/issues/1239
- var integrand = Functions.SingleQuotient.Combine(inT * 2 / (rate * (1 + tSquared))).Simplify();
+ // Once combined, a quotient with a symbol in its coefficients is not simplified: the
+ // simplifier's search grows with the symbols, and `sec(x)^2/(a + b cos(x))^3` spent 43 s
+ // in it where integrating what it returned took a tenth of a second. Two things the
+ // simplifier did are done here, since what follows reads them. Whole powers of
+ // products are written as products of powers: `((1 + t^2)(1 - t^2))^6` keeps the
+ // `1 + t^2` the division below takes off as a factor out of its sight, and
+ // `tan(x)^6/(a + b sec(x))` was declined that way. And a base of a lower degree than it
+ // is written in is written as its polynomial: `a (1 + t^2) + a (1 - t^2)` is `2 a`, and
+ // read as a quadratic its leading coefficient is zero as a value only. Only for a
+ // rational function of t: under a root the simplifier's writing of the radicand is
+ // what the radical rules read, and `1/(b cos(x) + c sin(x) - sqrt(b^2 + c^2))^(3/2)`
+ // went from a decline in three seconds to a search past two minutes without it.
+ var combined = Functions.SingleQuotient.Combine(inT * 2 / (rate * (1 + tSquared)));
+ Entity integrand;
+ if (combined.Vars.Any(symbol => symbol != t)
+ && !combined.Nodes.Any(node => node is Powf(var radicand, var power) && radicand.ContainsNode(t) && power.Evaled is not Number.Integer))
+ {
+ var (combinedAbove, combinedBelow) = Functions.SingleQuotient.Of(combined.InnerSimplified);
+ integrand = (WithDegenerateBasesLowered(WithWholePowersDistributed(combinedAbove), t)
+ / WithDegenerateBasesLowered(WithWholePowersDistributed(combinedBelow), t)).InnerSimplified;
+ }
+ else
+ integrand = combined.Simplify();
// Collapsing the nesting attaches a condition saying the denominator it cleared is
// non-zero, and that denominator is 1 + t^2 -- so 1/(1 + cos(x)) comes out as
@@ -23406,6 +23428,61 @@ static bool NoRemainder(Entity rest)
: null;
}
+ ///
+ /// 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
+ /// (1 + t^2)^6 (1 - t^2)^6.
+ ///
+ private static Entity WithWholePowersDistributed(Entity product)
+ {
+ Entity written = Number.Integer.One;
+ foreach (var factor in Mulf.LinearChildren(product))
+ {
+ if (factor is Powf(Mulf inner, Number.Integer { EInteger.Sign: > 0 } power))
+ foreach (var innerFactor in Mulf.LinearChildren(WithWholePowersDistributed(inner)))
+ written *= innerFactor is Powf(var @base, Number.Integer exponent)
+ ? MathS.Pow(@base, Number.Integer.Create(exponent.EInteger.Multiply(power.EInteger)))
+ : MathS.Pow(innerFactor, power);
+ else
+ written *= factor;
+ }
+ return written;
+ }
+
+ ///
+ /// with each base that is a sum written as its polynomial in
+ /// where that is of a lower degree than the base is written in, and
+ /// as written otherwise: a (1 + t^2) + a (1 - t^2) is 2 a.
+ ///
+ private static Entity WithDegenerateBasesLowered(Entity product, Entity.Variable t)
+ {
+ Entity written = Number.Integer.One;
+ foreach (var factor in Mulf.LinearChildren(product))
+ {
+ var (@base, power) = factor is Powf(var raised, Number.Integer exponent) ? (raised, (Entity)exponent) : (factor, Number.Integer.One);
+ if (@base is Sumf or Minusf && @base.ContainsNode(t) && TreeAnalyzer.TryGetPolynomial(@base, t, out var terms)
+ && terms.Count > 0 && terms.All(term => term.Key.Sign >= 0 && !term.Value.ContainsNode(t)))
+ {
+ var writtenDegree = @base.Nodes.Select(node => node is Powf(var raised, Number.Integer exponent) && raised == t ? exponent.EInteger
+ : node == t ? EInteger.One : EInteger.Zero).Max()!;
+ var kept = terms.Where(term => !Functions.PartialFractions.IsZeroAsAValue(term.Value)).ToList();
+ var degree = kept.Select(term => term.Key).DefaultIfEmpty(EInteger.Zero).Max()!;
+ if (degree.CompareTo(writtenDegree) < 0)
+ {
+ Entity polynomial = Number.Integer.Zero;
+ foreach (var term in kept.OrderByDescending(term => term.Key))
+ {
+ var coefficient = term.Value.InnerSimplified;
+ polynomial += term.Key.IsZero ? coefficient : coefficient * MathS.Pow(t, Number.Integer.Create(term.Key));
+ }
+ @base = polynomial.InnerSimplified;
+ }
+ }
+ written *= power == Number.Integer.One ? @base : MathS.Pow(@base, power);
+ }
+ return written;
+ }
+
///
/// with one power fewer of a factor that is
/// 1 + t^2, the rest of the product as written; null where no factor is.
diff --git a/Sources/Tests/UnitTests/Calculus/HalfAngleSubstitutionIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/HalfAngleSubstitutionIntegralTest.cs
index 25bdc66b7..e7d8a8188 100644
--- a/Sources/Tests/UnitTests/Calculus/HalfAngleSubstitutionIntegralTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/HalfAngleSubstitutionIntegralTest.cs
@@ -235,12 +235,29 @@ public void ASumTheOrdinaryRulesAnswerBetter(string integrand)
[InlineData("sec(x)^2/(a + b*cos(x))")]
[InlineData("tan(x)^4/(a + a*cos(x))")]
[InlineData("csc(x)^2/(a + a*cos(x))")]
- public void ARationalFunctionWithSymbolsForCoefficients(string integrand)
+ public void ARationalFunctionWithSymbolsForCoefficients(string integrand) => DifferentiatesBackWithSymbols(integrand);
+
+ ///
+ /// A power of a + b cos(x) past the square beside a power of the secant. The
+ /// substitution's integrand in t is not simplified where a symbol is in it:
+ /// simplified, it took 43 s at the cube and more than two minutes at the fourth power,
+ /// where integrating it takes a tenth of a second.
+ ///
+ [Theory]
+ [InlineData("sec(x)^2/(a + b*cos(x))^3")]
+ [InlineData("sec(x)^2/(a + b*cos(x))^4")]
+ [InlineData("sec(c + d*x)^2/(a + b*cos(c + d*x))^4")]
+ [InlineData("sec(x)/(a + b*cos(x))^3")]
+ [InlineData("1/(cos(x)*(a + b*cos(x))^2)")]
+ public void APowerOfALinearInTheCosineBesideASecant(string integrand) => DifferentiatesBackWithSymbols(integrand);
+
+ private static void DifferentiatesBackWithSymbols(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
- var derivative = integral.Substitute("C", 0).Differentiate("x").Substitute("a", 1.7).Substitute("b", 0.6);
- var original = integrand.ToEntity().Substitute("a", 1.7).Substitute("b", 0.6);
+ Entity Pin(Entity e) => e.Substitute("a", 1.7).Substitute("b", 0.6).Substitute("c", 0.3).Substitute("d", 1.1);
+ var derivative = Pin(integral.Substitute("C", 0).Differentiate("x"));
+ var original = Pin(integrand.ToEntity());
var compared = 0;
foreach (var at in Points)
{