diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 706174fdb..c2f6fee8c 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -6216,6 +6216,24 @@ with `K = sqrt(L1) sqrt(L2)/sqrt(M)` in front of the answer, constant wherever i
Rubi's 7.1.4 and 7.1.5, all 376 problems that count: 308 to 345, no row lost, 56 timeouts to 27.
+### Half-odd powers of the two linears whose product is the radicand `arsinh` holds are paired to leave a polynomial
+
+**Answers where there were none.** `(a + b arsinh(c x)) sqrt(f - i c f x)/(d + i c d x)^(3/2)`,
+`(d + i c d x)^(3/2) (f - i c f x)^(5/2) (a + b arsinh(c x))` and ten more of Rubi's 7.1.4 were declined. With both
+linears above the bar the smaller power is now the one paired into the radicand `M = 1 + c^2 x^2`, so that the whole
+power left is above the bar too: `(d + i c d x)^(3/2) (f - i c f x)^(5/2)` is `K^3 (f - i c f x) M^(3/2)`, a polynomial
+beside the root. With the second below the bar under a power of `-3/2` or less, the whole power of the first is
+written out as monomials, each over the root, and each `x^k (a + b arsinh(c x))/M^(3/2)` is answered alone. Over
+`sqrt(M)`, and with both linears below the bar, the pairing is as before
+([#233](https://github.com/asc-community/AngouriMath/issues/233)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"(a+b*asinh(c*x))*sqrt(f-i*c*f*x)/(d+i*c*d*x)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 1,001 characters |
+| `"(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 7,148 characters |
+| `"(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))".ToEntity().Integrate("x")` | `integral(...)` | 3,624 characters |
+| `"(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))^2".ToEntity().Integrate("x")` | `integral(...)` | 7,625 characters |
+
### The radicand an inverse sine or cosine holds is read as one
`(d + c d x)^(1/2) (f - c f x)^(3/2) (a + b arcsin(c x))` ran out of time. `arcsin(u)` holds a
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index b64fa6967..d8c92eefa 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -31898,6 +31898,26 @@ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expres
/// rows this way; `(d + i c d x)^(5/2) sqrt(f - i c f x) (a + b arsinh(c x))` ran past
/// seventy seconds where the same thing over `1 + c^2 x^2` takes two thirds of one.
///
+ ///
+ ///
+ /// Both factors above the bar, `q` is the smaller power, so that the whole power left is
+ /// above the bar too: `(d + i c d x)^(3/2) (f - i c f x)^(5/2)` is `K^3 (f - i c f x) M^(3/2)`,
+ /// a polynomial beside the root. Paired the other way it was `K^5 M^(5/2)/(d + i c d x)`,
+ /// which ran past the budget, as did the same with `(a + b arsinh(c x))^2`; the polynomial
+ /// takes half a second.
+ ///
+ ///
+ /// The first factor above the bar and the second below it under a power of `-3/2` or
+ /// less, the whole power `L1^n` is written out as a sum of monomials, each over the root:
+ /// `sqrt(f - i c f x)/(d + i c d x)^(3/2)` is `K^(-3) (f - i c f x)^2/M^(3/2)`, and as that
+ /// product the rules for a power of a quadratic do not read it -- ten of Rubi's 7.1.4
+ /// rows, from `(f - i c f x)^2` to `(f - i c f x)^5` over `M^(3/2)` and `M^(5/2)`, ran past
+ /// the budget -- while each `x^k (a + b arsinh(c x))/M^(3/2)` is answered in a tenth of a
+ /// second. Over `sqrt(M)` the product is answered as it is, and is left so; and both
+ /// factors below the bar are left as they are paired.
+ /// https://github.com/asc-community/AngouriMath/issues/233
+ ///
+ ///
private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Variable x, List references,
Entity.Variable multiple, out Entity rewritten, out Entity? root)
{
@@ -31928,6 +31948,11 @@ private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Vari
var twice = (2 * half).Evaled;
if (half is not Number.Integer && twice is not Number.Integer)
continue;
+ // Both above the bar, the smaller power is the one paired, so that the whole
+ // power left over is above it too. See the remarks.
+ var whole = (Number.Integer)(p - q).Evaled;
+ if (half is not Number.Integer && !half.IsNegative && whole.IsNegative)
+ continue;
var product = (first * second).Expand();
foreach (var candidate in references)
{
@@ -31944,6 +31969,14 @@ private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Vari
together = MathS.Pow(multiple, twice) * MathS.Pow(candidate, half);
root = MathS.Sqrt(first) * MathS.Sqrt(second) / MathS.Sqrt(candidate);
}
+ // The first factor above the bar and the root of the radicand 3/2 or more
+ // below it: a sum of monomials over that root. See the remarks.
+ if (half is not Number.Integer && half < Number.Rational.Create(-1, 1) && p.Evaled is Number.Real { IsPositive: true }
+ && AsMonomialsOverARoot(first, whole, x, factors.Where((_, k) => k != i && k != j), candidate, (Number.Rational)(-half)) is { } monomials)
+ {
+ rewritten = MathS.Pow(multiple, twice) * monomials;
+ return true;
+ }
Entity result = MathS.Pow(first, (p - q).InnerSimplified) * together;
for (var k = 0; k < factors.Count; k++)
if (k != i && k != j)
@@ -31961,6 +31994,44 @@ private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Vari
Powf(var @base, var exponent) => (@base, exponent),
_ => (factor, Number.Integer.One),
};
+
+ // `L^n g/M^r` as `sum_k a_k x^k g/M^r`, each term spelled as a quotient, which is how
+ // the rules for a power of a quadratic read it; null where L^n is no polynomial.
+ static Entity? AsMonomialsOverARoot(Entity linear, Number.Integer power, Entity.Variable x,
+ IEnumerable<(Entity Base, Entity Exponent)> rest, Entity radicand, Number.Rational rootPower)
+ {
+ if (!TreeAnalyzer.TryGetPolynomial(MathS.Pow(linear, power), x, out var terms)
+ || terms.Keys.Any(key => key.Sign < 0) || terms.Values.Any(coefficient => coefficient.ContainsNode(x)))
+ return null;
+ // The other factors, a negative power of one below the bar with the radicand's root.
+ Entity? beside = null;
+ Entity under = MathS.Pow(radicand, rootPower);
+ foreach (var (@base, exponent) in rest)
+ {
+ if (exponent.Evaled is Number.Real { IsNegative: true } negative)
+ {
+ var positive = (-negative).Evaled;
+ under = under * (positive == Number.Integer.One ? @base : MathS.Pow(@base, positive));
+ continue;
+ }
+ var factor = exponent == Number.Integer.One ? @base : MathS.Pow(@base, exponent);
+ beside = beside is null ? factor : beside * factor;
+ }
+ Entity? sum = null;
+ foreach (var pair in terms.OrderBy(term => term.Key))
+ {
+ var degree = pair.Key;
+ var written = pair.Value.InnerSimplified;
+ if (VanishesIdentically(written))
+ continue;
+ Entity term = degree.IsZero ? written : written * (degree.Equals(EInteger.One) ? x : MathS.Pow(x, Number.Integer.Create(degree)));
+ if (beside is not null)
+ term = term * beside;
+ term = term / under;
+ sum = sum is null ? term : sum + term;
+ }
+ return sum;
+ }
}
/// Set while asks its product.
diff --git a/Sources/Tests/UnitTests/Calculus/APairOfLinearsOverARadicandAsMonomialsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APairOfLinearsOverARadicandAsMonomialsIntegralTest.cs
new file mode 100644
index 000000000..d0d4a1934
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/APairOfLinearsOverARadicandAsMonomialsIntegralTest.cs
@@ -0,0 +1,61 @@
+//
+// 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
+{
+ ///
+ /// Two linears whose product is a multiple of the radicand of arsinh(c x),
+ /// (d + i c d x)(f - i c f x) = d f (1 + c^2 x^2), under powers a whole number apart:
+ /// with the root of the radicand 3/2 or more below the bar the whole power above it is
+ /// written out as monomials over the root, and with both above the bar the smaller power is
+ /// paired. Each ran past the budget. Rubi's 7.1.4.
+ /// #233
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class APairOfLinearsOverARadicandAsMonomialsIntegralTest
+ {
+ [Theory]
+ [InlineData("(a+b*asinh(c*x))*sqrt(f-i*c*f*x)/(d+i*c*d*x)^(3/2)")]
+ [InlineData("(f-i*c*f*x)^(3/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(3/2)")]
+ [InlineData("(f-i*c*f*x)^(3/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(5/2)")]
+ [InlineData("(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(3/2)")]
+ [InlineData("(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(5/2)")]
+ [InlineData("(d+i*c*d*x)^(5/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(3/2)")]
+ [InlineData("(d+i*c*d*x)^(3/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(3/2)")]
+ [InlineData("(a+b*asinh(c*x))*sqrt(d+i*c*d*x)/(f-i*c*f*x)^(3/2)")]
+ [InlineData("(d+i*c*d*x)^(5/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(5/2)")]
+ [InlineData("(d+i*c*d*x)^(3/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(5/2)")]
+ [InlineData("(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))")]
+ [InlineData("(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))^2")]
+ public void AnsweredAndDifferentiatesBack(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Entity Pinned(Entity e) => e.Substitute("a", 0.9).Substitute("b", 1.7).Substitute("c", 0.6).Substitute("d", -1.3).Substitute("f", 0.8);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ var compared = 0;
+ foreach (var at in new[] { -1.1, -0.4, 0.3, 0.7, 1.2, 2.1 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ if (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-9, $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ }
+ Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
+ }
+ }
+}