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}"); + } } ///