diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 4f6eb215f..277e44c3b 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -607,6 +607,23 @@ left ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
| `"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 root of a product beside a root of one of its factors is split
+
+**Answers where there were none.** `(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)` was declined: nothing read a radicand as
+another radicand times a cofactor. `(P Q)^r` is `K P^r Q^r` with `K = (P Q)^r/(P^r Q^r)`, constant wherever it is
+defined, and the powers of `P` then gather into `K (a + b x^2)^2/sqrt(a - b x^2)`
+([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+The answers are as large as the split integrands' answers, which are piecewise in the signs of `a` and `b`.
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"(a + b*x^2)^(5/2)/sqrt(a^2 - b^2*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 2,937 characters |
+| `"(a + b*x^2)^(1/2)/sqrt(a^2 - b^2*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 562 characters |
+| `"1/((a + b*x^2)^(3/2)*sqrt(a^2 - b^2*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 4,191 characters |
+| `"(a - b*x^2)^(3/2)/sqrt(a^2 - b^2*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 6,543 characters |
+| `"1/((a - b*x^2)^(1/2)*sqrt(a^2 - b^2*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 877 characters |
+
### A secant or a cosecant beside a power of a sum of them is read as a power of the cosine or the sine
**Answers where there were none.** `sec(x) sqrt(sec(x) + tan(x))` was declined, with the rest of Rubi's 4.7.7
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index c9d3c9d81..dfca3fe23 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -19120,6 +19120,76 @@ Entity TakeOut(Entity node)
/// Set while asks its question.
[System.ThreadStatic] private static bool takingTheWholePowersOut;
+ ///
+ /// A root of a product beside a root of one of its factors, the product split:
+ /// (P Q)^r is K P^r Q^r, where K = (P Q)^r/(P^r Q^r) is constant wherever
+ /// it is defined, for the same reason as in
+ /// . The powers of P
+ /// then gather: (a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4) is
+ /// K (a + b x^2)^2/sqrt(a - b x^2).
+ ///
+ ///
+ /// Rubi's 1.2.2.3:282-293 were declined: nothing reads a radicand as a product of another
+ /// radicand and a cofactor. Only factors of the integrand are read, and at the question
+ /// asked only. https://github.com/asc-community/AngouriMath/issues/718
+ ///
+ internal static Entity? SolveBySplittingARootOfAProductBesideARootOfAFactor(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ if (!Integration.AnsweringTheQuestionAsked || splittingARootOfAProduct)
+ return null;
+ var roots = new List<(Entity Node, Entity Base, Number.Rational Exponent)>();
+ Gather(expr);
+ if (roots.Count < 2)
+ return null;
+ foreach (var product in roots)
+ foreach (var factor in roots)
+ {
+ if (ReferenceEquals(product.Node, factor.Node) || product.Base == factor.Base
+ || ExactlyDivided(product.Base, factor.Base, x) is not { } cofactor || !cofactor.ContainsNode(x))
+ continue;
+ var multiple = Variable.CreateUnique(expr, "k_split");
+ var written = MathS.Pow(factor.Base, product.Exponent) * MathS.Pow(cofactor, product.Exponent);
+ var constant = product.Node / written;
+ var rewritten = expr.Replace(node => ReferenceEquals(node, product.Node) ? multiple * written : node).InnerSimplified;
+ Entity? answer;
+ splittingARootOfAProduct = true;
+ try
+ {
+ answer = Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts);
+ }
+ finally
+ {
+ splittingARootOfAProduct = false;
+ }
+ if (answer is null)
+ return null;
+ answer = answer.Substitute(multiple, constant);
+ return answer.ContainsNode(multiple) ? null : answer;
+ }
+ return null;
+
+ void Gather(Entity node)
+ {
+ switch (node)
+ {
+ case Mulf(var left, var right):
+ Gather(left);
+ Gather(right);
+ break;
+ case Divf(var above, var below):
+ Gather(above);
+ Gather(below);
+ break;
+ case Powf(var @base, Number.Rational exponent) when exponent is not Number.Integer && @base.ContainsNode(x):
+ roots.Add((node, @base, exponent));
+ break;
+ }
+ }
+ }
+
+ /// Set while asks its question.
+ [System.ThreadStatic] private static bool splittingARootOfAProduct;
+
///
/// A rational function of x and one square root of a quadratic in x,
/// rationalised by an Euler substitution and handed to the rational integrator.
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index f7b5b5a8b..b4c2725ba 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -1203,6 +1203,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// And a power of a product with a factor under a whole power, that power taken out in
// front of a constant: `(c sin(a + b x)^3)^(1/3)` is `K sin(a + b x)`.
if ((answer = IndefiniteIntegralSolver.SolveByTakingTheWholePowersOutOfAPowerOfAProduct(expr, x, integrateByParts)) is { }) return answer;
+ // And a root of a product beside a root of one of its factors, the product split:
+ // `(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)` is `K (a + b x^2)^2/sqrt(a - b x^2)`.
+ if ((answer = IndefiniteIntegralSolver.SolveBySplittingARootOfAProductBesideARootOfAFactor(expr, x, integrateByParts)) is { }) return answer;
// Product-to-sum among the rewrites rather than before them, because a product of
// trigonometric functions of *equal* arguments is a power and wants a different tool;
// this only fires where the arguments differ, which is exactly what every substitution
diff --git a/Sources/Tests/UnitTests/Calculus/ARootOfAProductSplitBesideARootOfAFactorIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ARootOfAProductSplitBesideARootOfAFactorIntegralTest.cs
new file mode 100644
index 000000000..76afdb560
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ARootOfAProductSplitBesideARootOfAFactorIntegralTest.cs
@@ -0,0 +1,72 @@
+//
+// 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
+{
+ ///
+ /// A root of a product beside a root of one of its factors, the product split in front of a
+ /// constant: (a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4) is K (a + b x^2)^2/sqrt(a - b x^2).
+ /// #718
+ ///
+ ///
+ /// Rubi's 1.2.2.3:282-293 were declined. Each answer is differentiated back with b of
+ /// either sign, so that each factor of the radicand is negative somewhere it is sampled.
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ARootOfAProductSplitBesideARootOfAFactorIntegralTest
+ {
+ private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ Assert.DoesNotContain("integral(", integral.Stringize());
+ Assert.DoesNotContain("NaN", integral.Stringize());
+
+ var derivative = integral.Substitute("C", 0).Differentiate("x");
+ Entity original = integrand.ToEntity();
+ foreach (var (name, value) in pins)
+ {
+ derivative = derivative.Substitute(name, value);
+ original = original.Substitute(name, value);
+ }
+
+ var compared = 0;
+ foreach (var at in points)
+ {
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ var want = original.Substitute("x", at).EvalNumerical();
+ if (got.IsNaN || want.IsNaN)
+ continue;
+ compared++;
+ 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}");
+ }
+ Assert.True(compared >= 3,
+ $"only {compared} of {points.Length} points were comparable for {integrand}");
+ }
+
+ [Theory]
+ [InlineData("(a+b*x^2)^(5/2)/sqrt(a^2-b^2*x^4)")]
+ [InlineData("(a+b*x^2)^(1/2)/sqrt(a^2-b^2*x^4)")]
+ [InlineData("1/((a+b*x^2)^(3/2)*sqrt(a^2-b^2*x^4))")]
+ [InlineData("(a-b*x^2)^(3/2)/sqrt(a^2-b^2*x^4)")]
+ [InlineData("1/((a-b*x^2)^(5/2)*sqrt(a^2-b^2*x^4))")]
+ public void ARootOfADifferenceOfSquaresBesideARootOfAFactor(string integrand)
+ {
+ var points = new[] { -1.3, -0.4, 0.3, 0.7, 1.6 };
+ DifferentiatesBack(integrand, points, ("a", 2), ("b", 3));
+ DifferentiatesBack(integrand, points, ("a", 2), ("b", -3));
+ }
+ }
+}