diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 33abc4553..014d8b820 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -335,6 +335,24 @@ and the complex arcsine's real part moved two units away. A caller comparing a h
result to a pinned string will see the last digits differ; a caller comparing to a tolerance
will not. [#1338](https://github.com/asc-community/AngouriMath/issues/1338).
+### Whole powers of a root are written as powers of its radicand, and hold on both sides of 0
+
+**Answers where there were none.** Rubi's 1.1.3.2 `1/(x^(7/2) sqrt(a + b x^5))` and three like it, and 1.3.2's
+`sqrt(a x^23)/sqrt(1 + x^5)` and `sqrt(a x^13)/sqrt(1 + x^5)`, and 1.1.1.2's `1/(sqrt(x) sqrt(2 x - 3))`, were answered
+`provided x >= 0`. The integrand is real
+beyond `-(a/b)^(1/5)` as a product of two imaginary roots, and the answer holds there. But it spelled `x^5` as
+`((x^(1/2))^5)^2`, whose value at a negative x has a zero imaginary part, and the measurement of where the answer holds
+could not evaluate the outer root on its cut. Under `u = sqrt(x)` a whole power of `u` that is a multiple of the
+root's order is now a power of the radicand, which it is on every branch, and the condition is gone
+([#233](https://github.com/asc-community/AngouriMath/issues/233)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"1/(x^(7/2)*sqrt(a+b*x^5))".ToEntity().Integrate("x")` | `integral(...)` | 60 characters, where before this change master gave 92 for a positive x only |
+| `"1/(x^(7/2)*sqrt(1+x^5))".ToEntity().Integrate("x")` | `integral(...)` | 50 characters |
+| `"sqrt(a*x^23)/sqrt(1+x^5)".ToEntity().Integrate("x")` | `integral(...)` | 426 characters |
+| `"1/(sqrt(x)*sqrt(-3+2*x))".ToEntity().Integrate("x")` | `integral(...)` | 141 characters, where before this change master gave an answer for a positive x only |
+
### A function of an inverse sine of `sqrt(1 + b x^2)` beside its derivative keeps the sign of x
**A wrong answer, and answers where there were none.** `1/(asin(sqrt(1 + b x^2)) sqrt(1 + b x^2))` was
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 743b3329e..a710984bb 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -15817,7 +15817,12 @@ node is Powf(var radical, Number.Rational e) && e is not Number.Integer
if (Integration.ComputeIndefiniteIntegral(integrand, u, integrateByParts) is not { } result)
return null;
- var back = result.Substitute(u, MathS.Pow(radicalBase, Number.Rational.Create(1, q)));
+ // A whole power of u that is a multiple of q is a whole power of the radicand on every
+ // branch, `(B^(1/q))^(k q) = B^k`, and is written so: as `((x^(1/2))^5)^2`, the `x^5` in Rubi's
+ // `1/(x^(7/2) sqrt(a + b x^5))` was real at a negative x with a zero imaginary part, the outer
+ // root on its branch cut there, and the measurement below could not read the answer where
+ // the integrand is real, and kept `provided x >= 0` on an answer that holds on both sides.
+ var back = WithWholePowersOfARootAsPowersOfItsRadicand(result.Substitute(u, MathS.Pow(radicalBase, Number.Rational.Create(1, q))), radicalBase);
// Beside a second root the integrand can be real where this radicand is negative, and
// the condition was the whole of its domain there: the remainder parts leaves from
// Timofeev's `arcsec(x) sqrt(x^2 - 1)/x^4`, in `w = x^2`, is
@@ -48128,6 +48133,7 @@ private static bool HoldsAnEvenRootBesides(Entity expr, Entity radicand, Entity.
///
private static Entity ConditionedOnTheRadicand(Entity answer, Entity expr, Entity radicand, Entity.Variable x)
{
+ answer = WithWholePowersOfARootAsPowersOfItsRadicand(answer, radicand);
var condition = radicand >= Number.Integer.Zero;
if (!HoldsAnEvenRootBesides(expr, radicand, x))
return answer.Provided(condition);
@@ -48143,6 +48149,29 @@ when TheOneOtherRadicandOfAHalfOddPower(expr, radicand, x) is { } other:
}
}
+ ///
+ /// with every whole power of a root of that
+ /// is a multiple of the root's order written as a power of the radicand, which it is on every
+ /// branch: (B^(1/q))^(k q) = B^k, nested whole powers flattened first. Spelled
+ /// ((x^(1/2))^5)^2, the x^5 in the answer to Rubi's 1.1.3.2
+ /// 1/(x^(7/2) sqrt(a + b x^5)) was real at a negative x with a zero imaginary part, the
+ /// outer root on its cut there, which the interval and decimal evaluations rightly refuse, and
+ /// the measurement of where the answer holds kept provided x >= 0 on an answer that holds
+ /// on both sides; so did (x^(1/2))^2 in 1.1.1.2's 1/(sqrt(x) sqrt(2 x - 3)).
+ ///
+ private static Entity WithWholePowersOfARootAsPowersOfItsRadicand(Entity answer, Entity radicand)
+ => answer
+ .Replace(node => node is Powf(Powf(var inner, Number.Integer k), Number.Integer m) && IsARootOf(inner, radicand)
+ ? MathS.Pow(inner, Number.Integer.Create(k.EInteger * m.EInteger)) : node)
+ .Replace(node => node is Powf(var inner, Number.Integer { EInteger: var n }) && IsARootOf(inner, radicand)
+ && ((Powf)inner).Exponent is Number.Rational { ERational: var root } && n.Remainder(root.Denominator).IsZero
+ ? MathS.Pow(radicand, Number.Integer.Create(n.Divide(root.Denominator))) : node);
+
+ /// Whether is radicand^(1/q) for a whole q past 1.
+ private static bool IsARootOf(Entity node, Entity radicand)
+ => node is Powf(var @base, Number.Rational { ERational: var root }) && @base == radicand
+ && root.Numerator.Equals(EInteger.One) && root.Denominator.CompareTo(EInteger.One) > 0;
+
///
/// The one base other than under a power of
/// that is not whole, where every such power is half-odd; else null.
diff --git a/Sources/Tests/UnitTests/Calculus/WholePowersOfTheRootAsPowersOfTheRadicandIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/WholePowersOfTheRootAsPowersOfTheRadicandIntegralTest.cs
new file mode 100644
index 000000000..48b98bc5f
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/WholePowersOfTheRootAsPowersOfTheRadicandIntegralTest.cs
@@ -0,0 +1,51 @@
+//
+// 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
+{
+ ///
+ /// Under u = sqrt(x), a whole power of u that is a multiple of the root's order written as a
+ /// power of the radicand, (x^(1/2))^10 = x^5: spelled ((x^(1/2))^5)^2, the answer to Rubi's
+ /// 1.1.3.2 1/(x^(7/2) sqrt(a + b x^5)) could not be read at a negative x, its root on the cut,
+ /// and kept provided x >= 0, where the integrand is real beyond -(a/b)^(1/5) and the
+ /// answer holds there.
+ /// #233
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class WholePowersOfTheRootAsPowersOfTheRadicandIntegralTest
+ {
+ [Theory]
+ [InlineData("1/(x^(7/2)*sqrt(a + b*x^5))")]
+ [InlineData("1/(x^(17/2)*sqrt(1 + x^5))")]
+ [InlineData("sqrt(a*x^13)/sqrt(1 + x^5)")]
+ [InlineData("1/(sqrt(x)*sqrt(-3 + 2*x))")]
+ public void HoldsOnBothSides(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ // Beyond -(a/b)^(1/5), about -1.13, the integrand is real as a product of two imaginary roots,
+ // and `1/(sqrt(x) sqrt(2 x - 3))` is real for every negative x the same way.
+ foreach (var at in new[] { -2.3, -1.4, 0.4, 1.3, 2.2 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}");
+ Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
+ < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
+ $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ }
+ }
+ }
+}