diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 27016b206..80e69aa15 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -1058,6 +1058,21 @@ secant too now, and the integrand is `sgn(cos(x)) (1 + u^2)^(3/2)/(a + b u)^2` i
| `"sec(x)^7/(a + b*tan(x))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,253 characters |
| `"sec(x)/(a + b*cot(x))".ToEntity().Integrate("x")` | `integral(...)` | 571 characters |
+### A root of a multiple of the radicand beside Euler's sum is the same root
+
+**Answers where there were none.** `sqrt(g Q) (d + e x + f sqrt(Q))^n` was declined, with the root of `g Q` below the
+bar, and so was the sum with its quadratic written over a constant, `sqrt((a f^2 + e x (2 d + e x))/f^2)`, as Rubi's
+1.3.2 writes it. The substitution read one radicand, and a quadratic only as a polynomial. `sqrt(g Q)/sqrt(Q)` is
+constant wherever both roots are defined, for either sign of `g`, so it is named while the question is asked and put
+back after; and a quadratic over a constant is read through the constant
+([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"(d + e*x + f*sqrt((a*f^2 + e*x*(2*d + e*x))/f^2))^n".ToEntity().Integrate("x")` | `integral(...)` | 227 characters |
+| `"(a*g + 2*d*e*g*x/f^2 + e^2*g*x^2/f^2)^(1/2)*(d + e*x + f*sqrt(a + 2*d*e*x/f^2 + e^2*x^2/f^2))^n".ToEntity().Integrate("x")` | `integral(...)` | 539 characters |
+| `"(d + e*x + f*sqrt(a + 2*d*e*x/f^2 + e^2*x^2/f^2))^n/(a*g + 2*d*e*g*x/f^2 + e^2*g*x^2/f^2)^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | 253 characters |
+
### A whole power of the radicand under Euler's substitution is read as one
**Answers where there were none, and none where they were wrong.** Euler's substitution writes the
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 5399e53b5..557c5f1ba 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -18870,6 +18870,76 @@ private static bool VanishesIdentically(Entity expr)
return Functions.PartialFractions.Bare(inner.Simplify()).Evaled is Number.Complex { IsZero: true };
}
+ ///
+ /// as a x^2 + b x + c, read through a constant below the
+ /// bar as well: Rubi writes (a f^2 + e x (2 d + e x))/f^2 for
+ /// a + 2 d e x/f^2 + e^2 x^2/f^2, and the first was not read as a quadratic.
+ ///
+ private static bool TryReadAQuadraticOverAConstant(Entity radicand, Entity.Variable x,
+ [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Entity? a,
+ [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Entity? b,
+ [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Entity? c)
+ {
+ if (TreeAnalyzer.TryGetPolyQuadratic(radicand, x, out a, out b, out c))
+ return true;
+ if (radicand is Divf(var over, var constant) && !constant.ContainsNode(x)
+ && TreeAnalyzer.TryGetPolyQuadratic(over, x, out var overA, out var overB, out var overC))
+ {
+ (a, b, c) = ((overA / constant).InnerSimplified, (overB / constant).InnerSimplified, (overC / constant).InnerSimplified);
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// with every square root of a constant multiple of one quadratic
+ /// Q written as a named constant times sqrt(Q), and the names' values; null
+ /// where there is one radicand, or radicands that are not multiples of one.
+ ///
+ ///
+ /// sqrt(g Q) (d + e x + f sqrt(Q))^n: the ratio sqrt(g Q)/sqrt(Q) is constant
+ /// wherever both roots are defined, whatever the sign of g, so it comes out of the
+ /// integral named and goes back in after. The radicand kept is the one a power of a sum
+ /// holds, the substitution's own. Rubi's 1.3.2.
+ /// https://github.com/asc-community/AngouriMath/issues/718
+ ///
+ private static (Entity Rewritten, List<(Entity.Variable Name, Entity Value)> Ratios)? WithTheRootsOfMultiplesAsOne(Entity expr, Entity.Variable x)
+ {
+ static bool IsARoot(Entity node, Entity.Variable x)
+ => node is Powf(var @base, Number.Rational r) && r is not Number.Integer
+ && r.ERational.Denominator.Equals(EInteger.FromInt32(2)) && @base.ContainsNode(x);
+ var radicands = expr.Nodes.Where(node => IsARoot(node, x)).Select(node => ((Powf)node).Base).Distinct().ToList();
+ if (radicands.Count < 2)
+ return null;
+ var kept = expr.Nodes
+ .OfType()
+ .Where(power => !power.Exponent.ContainsNode(x) && !IsARoot(power, x) && power.Base.Nodes.Any(inner => IsARoot(inner, x)))
+ .SelectMany(power => power.Base.Nodes.Where(inner => IsARoot(inner, x)).Select(inner => ((Powf)inner).Base))
+ .FirstOrDefault() ?? radicands[0];
+ if (!TryReadAQuadraticOverAConstant(kept, x, out var a, out var b, out var c) || VanishesIdentically(a))
+ return null;
+ var ratios = new List<(Entity.Variable, Entity)>();
+ var byRadicand = new Dictionary();
+ foreach (var other in radicands)
+ {
+ if (other == kept)
+ continue;
+ if (!TryReadAQuadraticOverAConstant(other, x, out var oa, out var ob, out var oc))
+ return null;
+ var factor = (oa / a).InnerSimplified;
+ if (factor.ContainsNode(x) || !VanishesIdentically(ob - factor * b) || !VanishesIdentically(oc - factor * c))
+ return null;
+ var name = Variable.CreateUnique(expr + ratios.Aggregate((Entity)Number.Integer.Zero, (sum, pair) => sum + pair.Item1), "k_ratio");
+ ratios.Add((name, MathS.Sqrt(other) / MathS.Sqrt(kept)));
+ byRadicand[other] = name;
+ }
+ var rewritten = expr.Replace(node =>
+ node is Powf(var @base, Number.Rational r) && byRadicand.TryGetValue(@base, out var ratio) && IsARoot(node, x)
+ ? MathS.Pow(ratio, Number.Integer.Create(r.ERational.Numerator)) * MathS.Pow(kept, r)
+ : node);
+ return (rewritten, ratios);
+ }
+
///
/// 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.
@@ -18921,6 +18991,15 @@ private static bool VanishesIdentically(Entity expr)
///
internal static Entity? SolveByEulerSubstitution(Entity expr, Entity.Variable x, bool onlyBySumItWrites = false)
{
+ // Roots of constant multiples of one quadratic are one root: see the method below.
+ if (WithTheRootsOfMultiplesAsOne(expr, x) is var (asOne, ratios))
+ {
+ if (SolveByEulerSubstitution(asOne, x, onlyBySumItWrites) is not { } inTheRatios)
+ return null;
+ foreach (var (name, value) in ratios)
+ inTheRatios = inTheRatios.Substitute(name, value);
+ return inTheRatios;
+ }
// One square root of a quadratic in x, and otherwise a rational function of x --
// or, besides those, powers of `x + sqrt(Q)` and `sqrt(Q) - x` to any exponent,
@@ -18961,7 +19040,7 @@ private static bool VanishesIdentically(Entity expr)
else if (radicand != innerBase) return null;
}
if (radicand is null
- || !TreeAnalyzer.TryGetPolyQuadratic(radicand, x, out var a, out var b, out var c)
+ || !TryReadAQuadraticOverAConstant(radicand, x, out var a, out var b, out var c)
|| a.Evaled is Number.Complex { IsZero: true })
return null;
// A missing linear term is the trigonometric substitution's and answered more shortly there.
diff --git a/Sources/Tests/UnitTests/Calculus/SumOfALinearAndARootAsEulersVariableIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/SumOfALinearAndARootAsEulersVariableIntegralTest.cs
index 842afc071..823f4a8f2 100644
--- a/Sources/Tests/UnitTests/Calculus/SumOfALinearAndARootAsEulersVariableIntegralTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/SumOfALinearAndARootAsEulersVariableIntegralTest.cs
@@ -35,12 +35,25 @@ public sealed class SumOfALinearAndARootAsEulersVariableIntegralTest
// And beside powers of the radicand, whole and not, before the split expands them.
[InlineData("(a + 2*d*k*x/f^2 + k^2*x^2/f^2)^2*(d + k*x + f*sqrt(a + 2*d*k*x/f^2 + k^2*x^2/f^2))^n")]
[InlineData("(a + 2*d*k*x/f^2 + k^2*x^2/f^2)^(3/2)*(d + k*x + f*sqrt(a + 2*d*k*x/f^2 + k^2*x^2/f^2))^n")]
- public void IsIntegratedInTheSum(string integrand)
+ public void IsIntegratedInTheSum(string integrand) => DifferentiatesBack(integrand, 2.5);
+
+ // The radicand written over `f^2` as Rubi writes it, and the root of a constant multiple
+ // of it beside the sum: `sqrt(g Q)` is `sqrt(Q)` times a ratio constant wherever both are
+ // defined, named while the question is asked, and so for either sign of `g`.
+ [Theory]
+ [InlineData("(d + k*x + f*sqrt((a*f^2 + k*x*(2*d + k*x))/f^2))^n", 2.5)]
+ [InlineData("(d + k*x + f*sqrt((a*f^2 + k*x*(2*d + k*x))/f^2))^n/((a*f^2 + k*x*(2*d + k*x))/f^2)^(1/2)", 2.5)]
+ [InlineData("(a*g + 2*d*k*g*x/f^2 + k^2*g*x^2/f^2)^(1/2)*(d + k*x + f*sqrt(a + 2*d*k*x/f^2 + k^2*x^2/f^2))^n", 2.5)]
+ [InlineData("(a*g + 2*d*k*g*x/f^2 + k^2*g*x^2/f^2)^(1/2)*(d + k*x + f*sqrt(a + 2*d*k*x/f^2 + k^2*x^2/f^2))^n", -2.5)]
+ [InlineData("(d + k*x + f*sqrt((a*f^2 + k*x*(2*d + k*x))/f^2))^n/((a*f^2*g + k*g*x*(2*d + k*x))/f^2)^(1/2)", -2.5)]
+ public void WrittenOverItsDenominatorOrBesideTheRootOfAMultiple(string integrand, double g) => DifferentiatesBack(integrand, g);
+
+ private static void DifferentiatesBack(string integrand, double g)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.6).Substitute("d", 0.7)
- .Substitute("f", 1.1).Substitute("k", 0.9).Substitute("n", 0.37);
+ .Substitute("f", 1.1).Substitute("k", 0.9).Substitute("n", 0.37).Substitute("g", g);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -2.5, -1.2, 0.3, 0.8, 1.4 })