diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index edd19a65c..740222944 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -1572,6 +1572,21 @@ Rubi's 1.2.1.4, all 632 problems that count: 250 to 409, no row lost, 61 timeout
new timeouts have a radicand of four symbols beside a power of `x`, and went from a quick decline
to one past the budget.
+### Two proportional linears under roots are read as one radical
+
+`sin(a + b (c + d x)^(1/3))/(c e + d e x)^(1/3)` ran past two minutes. The two linears under the
+roots are proportional, `c e + d e x = e (c + d x)`, and the rule for roots of two different
+linears, which rationalises by `t = ((a x + b)/(c x + d))^(1/q)`, has nothing to rationalise when
+their determinant `a d - b c` is zero. A power of the second is now written as the ratio's power
+times the same power of the first, the generic reading of a root of a product, and the integrand
+is the one-linear question; a ratio known to be negative under an even root is not taken, since
+there the two differ by a sign
+([#1386](https://github.com/asc-community/AngouriMath/issues/1386)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"sin(a+b*(c+d*x)^(1/3))/(c*g+d*g*x)^(1/3)".Integrate("x")` | ran past two minutes | `3 ((c + d x)^(1/3) (-cos(a + b (c + d x)^(1/3)))/b + sin(a + b (c + d x)^(1/3))/b^2)/(d g^(1/3))` |
+
### A whole power of a perfect square in a power of the variable is written as a power of its root
`1/(a^2 + 2 a b x^2 + b^2 x^4)^2` was left unevaluated. The quartic is `b^2 (x^2 + a/b)^2`, so a
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index a9a3024a9..7d8cd2cdb 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -7042,15 +7042,19 @@ node is Powf(var radical, Number.Rational e) && e is not Number.Integer
return null;
if (!TreeAnalyzer.TryGetPolyLinear(first, x, out var a, out var b) || !TreeAnalyzer.TryGetPolyLinear(second, x, out var c, out var d))
return null;
- // Two proportional linears are one radical with a constant in it, and not this
- // rule's: their determinant a d - b c is zero, the second linear in t is 0/(a - c t^q)
- // and everything it multiplies vanished -- Rubi's
- // `sin(a + b (c + d x)^(1/3))/(c e + d e x)^(1/3)` came back as `0 provided ...`.
+ // Two proportional linears are one radical with a constant in it: their determinant
+ // a d - b c is zero, and the second linear in t would be 0/(a - c t^q), everything it
+ // multiplies vanishing -- Rubi's `sin(a + b (c + d x)^(1/3))/(c e + d e x)^(1/3)`
+ // came back as `0 provided ...`. With `c x + d = k (a x + b)`, a power of the second
+ // is `k^r` times the same power of the first, the generic reading of a root of a
+ // product; a known-negative k under an even root is not taken, since there the two
+ // differ by a sign wherever the first linear is negative. The integrand is then the
+ // one-linear question, asked again.
// https://github.com/asc-community/AngouriMath/issues/1386
var determinant = (a * d - b * c).InnerSimplified;
if (determinant.Evaled is Number.Complex { IsZero: true }
|| determinant.Vars.Any() && Functions.PartialFractions.Bare(determinant.Simplify()).Evaled is Number.Complex { IsZero: true })
- return null;
+ return AsOneLinearRadical(expr, x, first, second, Functions.PartialFractions.Bare((c / a).Simplify()), integrateByParts);
var t = Variable.CreateUnique(expr, "t_rad");
var w = Variable.CreateUnique(expr, "w_rad");
@@ -7179,6 +7183,34 @@ static Entity WithWholePowersOfTheSecond(List<(EInteger Power, Entity Coefficien
}
}
+ ///
+ /// with every fractional power of , which is
+ /// times , written as the ratio's power
+ /// times the same power of , and asked again; null where the ratio
+ /// is known negative under an even root, or the rewrite leaves the question as it was.
+ ///
+ private static Entity? AsOneLinearRadical(Entity expr, Entity.Variable x, Entity first, Entity second, Entity ratio, bool integrateByParts)
+ {
+ if (ratio.ContainsNode(x))
+ return null;
+ var negative = ratio.Evaled is Number.Real { IsNegative: true };
+ var refused = false;
+ var rewritten = expr.Replace(node =>
+ {
+ if (node is not Powf(var @base, Number.Rational exponent) || exponent is Number.Integer || @base != second)
+ return node;
+ if (negative && exponent.ERational.Denominator.IsEven)
+ refused = true;
+ return MathS.Pow(ratio, exponent) * MathS.Pow(first, exponent);
+ });
+ if (refused || rewritten == expr)
+ return null;
+ // The same question, respelled: a rule scoped to the question asked is owed it, as in
+ // SolveByCancellingWithFunctionsAsIndeterminates, and with one linear left under a
+ // root this rule does not see it again.
+ return Integration.ComputeAsTheSameQuestion(rewritten.InnerSimplified, x, integrateByParts);
+ }
+
///
/// Whether is a quotient of two polynomials linear in
/// , the one below with a slope.
diff --git a/Sources/Tests/UnitTests/Calculus/TrigonometricPowerProductTest.cs b/Sources/Tests/UnitTests/Calculus/TrigonometricPowerProductTest.cs
index efd752e2c..d83a72f74 100644
--- a/Sources/Tests/UnitTests/Calculus/TrigonometricPowerProductTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/TrigonometricPowerProductTest.cs
@@ -208,14 +208,31 @@ public void AFractionalPowerOfAProductKeepsItsSign(string integrand)
/// Two proportional linears under roots are one radical with a constant in it: the
/// quotient-of-two-radicals rule took them, its determinant was zero, and
/// sin(a + b (c + d x)^(1/3))/(c e + d e x)^(1/3) came back as
- /// 0 provided .... https://github.com/asc-community/AngouriMath/issues/1386
+ /// 0 provided .... With the second root written as e^(1/3) times the
+ /// first, it is the one-linear question, and is answered. Rubi's 4.1.12, with g for
+ /// its e, which here is Euler's number.
+ /// https://github.com/asc-community/AngouriMath/issues/1386
///
- [Fact]
- public void TwoProportionalRadicalsAreNotAQuotientOfTwo()
+ [Theory]
+ [InlineData("sin(a + b*(c + d*x)^(1/3))/(c*g + d*g*x)^(1/3)")]
+ [InlineData("(c + d*x)^(1/2)/(c*g + d*g*x)^(3/2)")]
+ [InlineData("x*(2*c + 2*d*x)^(1/3)*(c + d*x)^(2/3)")]
+ public void TwoProportionalRadicalsAreOne(string integrand)
{
- var integral = "sin(a + b*(c + d*x)^(1/3))/(c*e2 + d*e2*x)^(1/3)".ToEntity().Integrate("x");
- Assert.DoesNotContain("NaN", integral.Stringize());
- Assert.NotEqual("0", (integral.Substitute("C", 0) is Entity.Providedf(var inner, _) ? inner : integral.Substitute("C", 0)).Simplify().Stringize());
+ var integral = integrand.ToEntity().Integrate("x");
+ Assert.DoesNotContain("integral(", integral.Stringize());
+ Entity Pinned(Entity e) => e.Substitute("a", "1/3".ToEntity()).Substitute("b", "2/3".ToEntity())
+ .Substitute("c", "5/4".ToEntity()).Substitute("d", "1/2".ToEntity()).Substitute("g", "3/2".ToEntity());
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ foreach (var at in new[] { 0.35, 0.9, 1.45, 2.3 })
+ {
+ 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));
+ Assert.True(difference / scale < 1e-9, $"d/dx of {integral} is {got} at x = {at}, where {integrand} is {want}");
+ }
}
///