diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 22579126a..2d782b045 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -396,6 +396,22 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take
| `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters |
| `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters |
+### Powers of multiples of powers of one linear are gathered into one power of it
+
+**Answers where there were none.** `1/(sqrt(-2 - b x) sqrt(2 + b x))` was declined, and so were four more of Rubi's
+1.1.1.2, 1.1.1.3, 1.2.2.2 and 1.3.1. A power `B^p` that is not whole, of a polynomial `B` that is a constant times
+`L^k` for one linear `L`, is now written `(B^p/L^(k p)) L^(k p)`, and the powers of one linear are integrated as one.
+The quotient is constant on each interval where it is continuous, so it stays in the answer as written
+([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"1/(sqrt(-2-b*x)*sqrt(2+b*x))".ToEntity().Integrate("x")` | `integral(...)` | 73 characters |
+| `"sqrt(a+b*x)/(x*sqrt(-a-b*x))".ToEntity().Integrate("x")` | `integral(...)` | 56 characters |
+| `"(a+b*x)^n/(x*(-a-b*x)^n)".ToEntity().Integrate("x")` | `integral(...)` | 52 characters |
+| `"(a^3+3*a^2*b*x+3*a*b^2*x^2+b^3*x^3)^p".ToEntity().Integrate("x")` | `integral(...)` | 187 characters |
+| `"1/sqrt(2+2*a-2*(1+a)+c*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 119 characters |
+
### A linear below the cube root of a quadratic is integrated in closed form
**Answers where there were none.** `1/((d + e x) (d^2 + 3 e^2 x^2)^(1/3))` was declined, and so were six more of Rubi's
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index d7dab662e..2a6e908e1 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -20065,6 +20065,96 @@ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expres
return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null;
}
+ ///
+ /// Powers that are not whole of polynomials that are constant multiples of powers of one
+ /// linear, gathered into one power of it: B^p = (B^p/L^(k p)) L^(k p) for
+ /// B = lambda L^k, so sqrt(-2 - b x) sqrt(2 + b x) is a constant times
+ /// 2 + b x and (a^3 + 3 a^2 b x + 3 a b^2 x^2 + b^3 x^3)^p one times
+ /// (a + b x)^(3 p).
+ ///
+ ///
+ /// B^p/L^(k p) is constant wherever it is continuous: its logarithmic derivative is
+ /// p B'/B - k p L'/L, which is zero for B = lambda L^k. It is kept in the
+ /// answer as written. Rubi's 1.1.1.2 1/(sqrt(-2 - b x) sqrt(2 + b x)), 1.1.1.3
+ /// (a + b x)^n/(x (-a - b x)^n) and 1.3.1 (a^3 + 3 a^2 b x + 3 a b^2 x^2 + b^3 x^3)^p
+ /// were declined.
+ /// https://github.com/asc-community/AngouriMath/issues/718
+ ///
+ internal static Entity? SolvePowersOfOneLinearAsOnePower(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ if (!Integration.AnsweringTheQuestionAsked)
+ return null;
+ // Without a leading `1 *`, which is another spelling to the rules that read the result.
+ static Entity Times(Entity a, Entity b) => a == Number.Integer.One ? b : a * b;
+ static bool IsWhole(Entity power) => power is Number.Integer;
+ // The root r and degree k of a polynomial that is c (x - r)^k, or null.
+ (Entity Root, int Degree)? AsAPowerOfALinear(Entity polynomial)
+ {
+ if (!TreeAnalyzer.TryGetPolynomial(polynomial, x, out var read) || read.Keys.Any(key => key.Sign < 0))
+ return null;
+ var present = read.Where(term => !VanishesIdentically(term.Value.Expand())).ToList();
+ if (present.Count == 0 || present.Max(term => term.Key) is not { } top || top.Sign <= 0 || !top.CanFitInInt32())
+ return null;
+ var k = top.ToInt32Unchecked();
+ var lead = read[top];
+ var next = read.TryGetValue(top - EInteger.One, out var n0) ? n0 : Number.Integer.Zero;
+ var root = (-next / (k * lead)).InnerSimplified;
+ if (k > 1 && !VanishesIdentically((polynomial - lead * MathS.Pow(x - root, Number.Integer.Create(k))).Expand()))
+ return null;
+ return (root, k);
+ }
+ var (numerator, denominator) = Functions.SingleQuotient.Of(expr);
+ var members = new List<(Entity Base, Entity Power, int Degree, Entity Root)>();
+ Entity rest = Number.Integer.One;
+ foreach (var (side, below) in new[] { (numerator, false), (denominator, true) })
+ foreach (var factor in Mulf.LinearChildren(side))
+ {
+ if (factor is Powf(var @base, var power) && !IsWhole(power) && !power.ContainsNode(x)
+ && AsAPowerOfALinear(@base) is var (root, degree))
+ members.Add((@base, below ? -power : power, degree, root));
+ else
+ rest = below ? rest / factor : Times(rest, factor);
+ }
+ // Gathered by root; worth doing where a group has two members or a power of a linear.
+ var groups = new List>();
+ foreach (var member in members)
+ {
+ if (groups.FirstOrDefault(group => VanishesIdentically((group[0].Root - member.Root).Expand().InnerSimplified)) is { } group)
+ group.Add(member);
+ else
+ groups.Add(new() { member });
+ }
+ if (!groups.Any(group => group.Count > 1 || group.Any(member => member.Degree > 1)))
+ return null;
+ Entity constant = Number.Integer.One;
+ Entity gathered = rest;
+ foreach (var group in groups)
+ {
+ var linear = group.FirstOrDefault(member => member.Degree == 1).Base ?? x - group[0].Root;
+ Entity total = Number.Integer.Zero;
+ foreach (var member in group)
+ {
+ var exponent = member.Degree * member.Power;
+ // The linear itself is its own power, with nothing to carry in front.
+ if (member.Base != linear)
+ constant = Times(constant, MathS.Pow(member.Base, member.Power) / MathS.Pow(linear, exponent));
+ total += exponent;
+ }
+ total = total.InnerSimplified;
+ if (VanishesIdentically(total.Expand()))
+ continue;
+ // Spelled as a quotient where the power is negative, as the rules read it.
+ gathered = total is Number.Real { IsNegative: true } || (total is Mulf(Number.Real { IsNegative: true }, _))
+ ? gathered / MathS.Pow(linear, (-total).InnerSimplified)
+ : Times(gathered, MathS.Pow(linear, total));
+ }
+ if (Integration.ComputeAsAQuestionOfItsOwn(gathered, x, integrateByParts) is not { } joined)
+ return null;
+ if (!Functions.PartialFractions.DerivativeHoldsAtSampledPoints(joined, gathered, x))
+ return null;
+ return constant * joined;
+ }
+
///
/// A power of a linear that is not whole, times a linear, over a whole power of a quadratic,
/// with the power of the quadratic lowered one at a time:
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index dc25a94de..820ef3d59 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -1319,6 +1319,11 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// function of x and of sin(x) and cos(x) together. `x^2/(x cos(x) - sin(x))^2` is
// `((x sin(x) + cos(x))/(x cos(x) - sin(x)))'`.
if ((answer = IndefiniteIntegralSolver.SolveByTrigonometricTowerAnsatz(expr, x)) is { }) return answer;
+ // Powers of polynomials that are multiples of powers of one linear, as one power of it,
+ // the quotient constant in front. Late, so that a rule answering the powers as written
+ // goes first: asked before the rules for two linears, it wrote `((a + b x)^3)^(1/3)` in
+ // 111 characters where they write 25.
+ if ((answer = IndefiniteIntegralSolver.SolvePowersOfOneLinearAsOnePower(expr, x, integrateByParts)) is { }) return answer;
// Last of the rewrites, because it is the only one that fires on an integrand nothing
// is wrong with -- it clears a parameter rather than a shape -- so everything that
// can answer the problem as written gets to try first.
diff --git a/Sources/Tests/UnitTests/Calculus/PowersOfOneLinearAsOnePowerIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/PowersOfOneLinearAsOnePowerIntegralTest.cs
new file mode 100644
index 000000000..290e201ce
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/PowersOfOneLinearAsOnePowerIntegralTest.cs
@@ -0,0 +1,71 @@
+//
+// 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 that are not whole of polynomials that are constant multiples of powers of one
+ /// linear, gathered into one power of it with the constant quotient in front: Rubi's 1.1.1.2
+ /// 1/(sqrt(-2 - b x) sqrt(2 + b x)), 1.1.1.3 (a + b x)^n/(x (-a - b x)^n) and 1.3.1
+ /// (a^3 + 3 a^2 b x + 3 a b^2 x^2 + b^3 x^3)^p. Sampled on both sides of the linear's root.
+ /// #718
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class PowersOfOneLinearAsOnePowerIntegralTest
+ {
+ private static void DifferentiatesBack(string integrand, params (string, double)[] pins)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ Assert.DoesNotContain("integral(", integral.Stringize());
+
+ var pinned = integral.Substitute("C", 0);
+ Entity original = integrand.ToEntity();
+ foreach (var (name, value) in pins)
+ {
+ pinned = pinned.Substitute(name, value);
+ original = original.Substitute(name, value);
+ }
+ var derivative = pinned.Differentiate("x");
+ foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 })
+ {
+ 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-8,
+ $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ }
+ }
+
+ [Theory]
+ [InlineData("1/(sqrt(-2-b*x)*sqrt(2+b*x))")]
+ [InlineData("sqrt(a+b*x)/(x*sqrt(-a-b*x))")]
+ [InlineData("(a+b*x)^n/(x*(-a-b*x)^n)")]
+ [InlineData("(a^3+3*a^2*b*x+3*a*b^2*x^2+b^3*x^3)^n")]
+ public void WithSymbolsIsAnsweredAndDifferentiatesBack(string integrand)
+ {
+ DifferentiatesBack(integrand, ("a", 0.6), ("b", 1.5), ("n", 1.0 / 3));
+ DifferentiatesBack(integrand, ("a", -0.75), ("b", -0.7), ("n", 2.3));
+ }
+
+ [Theory]
+ [InlineData("((a+b*x)^3)^(1/3)", 40)]
+ [InlineData("x*(-a-b*x)^(3/2)/(a+b*x)^(3/2)", 80)]
+ public void LeavesWhatTheRulesBeforeItAnswer(string integrand, int length)
+ {
+ // Asked before them, the rule wrote these in 111 and 106 characters, where they write
+ // 25 and 61.
+ var integral = integrand.ToEntity().Integrate("x").Stringize();
+ Assert.DoesNotContain("integral(", integral);
+ Assert.True(integral.Length < length, integral);
+ }
+ }
+}