diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 914d0be9a..ae9e5c7fd 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -1009,6 +1009,23 @@ rule that does the same for a polynomial under a square root, whose answers are
| `"1/sqrt(a*cot(x)^2)".Integrate("x")` | `-sgn(tan(x)) ln(1/sqrt(1 + abs(tan(x))^2))/sqrt(a)` | `sgn(cot(x)) ln(1 + tan(x)^2)/(2 sqrt(a))` |
| `"(b*tan(x)^2)^(5/2)".Integrate("x")` | `sgn(tan(x)) b^(5/2) ((abs(tan(x))^2)^2/2 - abs(tan(x))^2 + ln(abs(tan(x))^2 + 1))/2` | the same with `tan(x)` for `abs(tan(x))` |
+### A power of the cotangent that is not whole below the bar is integrated beside the tangent
+
+**Answers where there were none.** `1/(cot(x)^(7/2) (a + b tan(x))^(3/2))` was declined while
+`tan(x)^(7/2)/(a + b tan(x))^(3/2)` was answered: the substitution `u = tan(x)` wrote the cotangent
+as `1/u`, and nothing read a power of `1/u` that is not whole below the bar. Such a power is written
+as the tangent's above it now, `1/cot(x)^p = tan(x)^p/K`, with `K = cot(x)^p tan(x)^p` dividing the
+answer: `K` is 1 where the tangent is positive and a constant on every interval where it keeps its
+sign, so the answer holds where the tangent is negative too. Only below the bar and beside the
+tangent; a power of the cotangent above it, or alone, keeps its answer. Rubi's 4.3.2.1 and 4.3.3.1
+([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"1/(cot(x)^(7/2)*(a + b*tan(x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `sqrt(tan(x))` and `sqrt(a + b tan(x))`, over `K` |
+| `"1/(cot(x)^(5/2)*(a + i*a*tan(x))^(5/2))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `sqrt(tan(x))/sqrt(1 + i tan(x))`, over `K` |
+| `"(A + B*tan(x))/(cot(x)^(3/2)*(a + i*a*tan(x)))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `sqrt(tan(x))`, over `K` |
+
### A fractional power of a product of trigonometric functions keeps its sign
**Wrong answers, silent.** The `sin^p cos^q` reader took a power of a product with an even
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 17ade5dd3..f1658aa8f 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -8795,6 +8795,51 @@ private static bool TryReadAHomogeneousTrigonometricPolynomial(
return true;
}
+ ///
+ /// A power of the cotangent that is not whole below the bar of an integrand with the
+ /// tangent of the same argument in it, written as a power of the tangent above it:
+ /// 1/cot(z)^p = K tan(z)^p with K = 1/(cot(z)^p tan(z)^p), which is 1
+ /// wherever the tangent is positive and a constant on every interval where it keeps its
+ /// sign, so it stands in front of the answer, the way Rubi writes it.
+ ///
+ ///
+ /// 1/(cot(x)^(7/2) (a + b tan(x))^(3/2)) was declined while
+ /// tan(x)^(7/2)/(a + b tan(x))^(3/2) was answered: the tangent substitution
+ /// writes the cotangent as 1/tan, and a power of 1/u that is not whole below
+ /// the bar is read by no rule. Only below the bar: above it the substitution's
+ /// (1/u)^p is read, and cot(x)^(3/2)/(a + b tan(x)), answered in four
+ /// seconds so, ran past a minute as tan(x)^(-3/2), while
+ /// cot(x)^(7/2) sqrt(a + b tan(x)), declined in a second, took a hundred to be.
+ /// And only beside the tangent; a power of the cotangent alone keeps the answer it had.
+ /// Rubi's 4.3.2.1 and 4.3.3.1.
+ /// https://github.com/asc-community/AngouriMath/issues/718
+ ///
+ internal static Entity? SolveByWritingAPowerOfTheCotangentInTheTangent(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ if (!expr.Nodes.Any(node => node is Cotanf))
+ return null;
+ Entity? power = null;
+ Entity? argument = null;
+ foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
+ if (underneath && factor is Powf(Cotanf(var cotangentArgument), var exponent) && cotangentArgument.ContainsNode(x)
+ && exponent.Evaled is Number.Rational fraction && fraction is not Number.Integer)
+ {
+ if (power is not null)
+ return null;
+ (power, argument) = (factor, cotangentArgument);
+ }
+ if (power is not Powf(var cotangent, var p) || argument is null)
+ return null;
+ // The cotangent nowhere else, and the tangent somewhere: the power is then all of
+ // the cotangent there is, and the integrand is the tangent substitution's.
+ var tangent = MathS.Tan(argument);
+ if (expr.Nodes.Count(node => node == cotangent) != 1 || !expr.ContainsNode(tangent))
+ return null;
+ var rewritten = expr.Replace(node => node == power ? MathS.Pow(tangent, (-p).InnerSimplified) : node);
+ var constant = power * MathS.Pow(tangent, p);
+ return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts)?.Pipe(answer => answer / constant);
+ }
+
///
/// An integrand that is a function of tan(x) and of nothing else, integrated by
/// the substitution u = tan(x), under which dx is du/(1 + u^2).
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index 087ac67fb..f3b1bb3e2 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -860,6 +860,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// the pair of exponents they are. After the reduction, because a power of the secant
// alone is both rules' and the reduction's answer for it is shorter.
if ((answer = IndefiniteIntegralSolver.SolveByTrigonometricPowerSubstitution(expr, x)) is { }) return answer;
+ // A power of the cotangent that is not whole beside the tangent, written as the
+ // tangent's with the constant that takes in front, which the substitution below reads.
+ if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfTheCotangentInTheTangent(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByTangentSubstitution(expr, x, integrateByParts)) is { }) return answer;
// A root of a quadratic in the tangent with a linear term, rotated until it has
// none: `1/sqrt(a + b tan(x) + c tan(x)^2)` is `1/sqrt(A + C tan(y)^2)` under
diff --git a/Sources/Tests/UnitTests/Calculus/TangentSubstitutionIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/TangentSubstitutionIntegralTest.cs
index f11a00f71..e049bca32 100644
--- a/Sources/Tests/UnitTests/Calculus/TangentSubstitutionIntegralTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/TangentSubstitutionIntegralTest.cs
@@ -234,6 +234,37 @@ public void SymbolsArePinnedBeforeTheParityIsDecided()
[InlineData("tan(x) ^ 3")]
public void APowerOfTheTangent(string integrand) => DifferentiatesBack(integrand);
+ ///
+ /// A power of the cotangent that is not whole below the bar beside the tangent, which is
+ /// the tangent's power above it over cot(x)^p tan(x)^p, a constant on every
+ /// interval where the tangent keeps its sign. Compared where the tangent is positive and
+ /// the integrand real, and where it is negative and the integrand imaginary, which is
+ /// where the constant is what keeps the answer right: it is -1 there for an odd number of
+ /// halves and i for a quarter. Rubi's 4.3.2.1 and 4.3.3.1.
+ ///
+ [Theory]
+ [InlineData("1/(cot(x)^(7/2)*(a + b*tan(x))^(3/2))")]
+ [InlineData("1/(cot(x)^(5/2)*(a + i*a*tan(x))^(5/2))")]
+ [InlineData("(A + B*tan(x))/(cot(x)^(3/2)*(a + i*a*tan(x)))")]
+ [InlineData("1/(cot(x)^(1/4)*(a + b*tan(x)))")]
+ public void APowerOfTheCotangentBelowTheBarBesideTheTangent(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ Assert.DoesNotContain("integral(", integral.Stringize());
+ Entity Pin(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("A", 0.9).Substitute("B", 1.4);
+ var derivative = Pin(integral.Substitute("C", 0).Differentiate("x"));
+ var original = Pin(integrand.ToEntity());
+ foreach (var at in new[] { -1.1, -0.4, 0.3, 0.8, 1.2 })
+ {
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ var want = original.Substitute("x", at).EvalNumerical();
+ var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
+ var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
+ Assert.True(difference / scale < 1e-9,
+ $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ }
+ }
+
///
/// What the neighbours still answer, and must go on answering the same way: the tangent
/// itself has a rule, which is reached before this and gives the shorter form.