diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 78a771133..ff524e1a1 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -583,6 +583,18 @@ unevaluated, is answered by this route as well. | `"(a*x+2*b*n*ln(c*x^n))/(a*x^2+b*x*ln(c*x^n)^2)".ToEntity().Integrate("x")` | `integral(...)` | 37 characters | | `"tanh(a+2*ln(x))^2/x^3".ToEntity().Integrate("x")` | `integral(...)` | 7,796 characters | +### A symbolic cubic with a repeated root is written over its roots + +**Answers where there were none.** `1/(x^3 - 3b x + 2b^(3/2))` was left unevaluated: its coefficients are symbolic, so +no numeric root finder applies, and its discriminant vanishes identically. Such a cubic is `a (x - r)^2 (x - t)` with +`r` and `t` rational in its coefficients, and is written so before the partial fractions +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/(x^3-3*b*x+2*b^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 122 characters | +| `"1/(-9*b*x+9*x^3+2*b^(3/2)*sqrt(3))".ToEntity().Integrate("x")` | `integral(...)` | 176 characters | + ### A symbol shared by a numerator sum's coefficients is taken out before the partial fractions **Answers where there were none.** `(b d + 2c d x)^9/(a + b x + c x^2)^3` ran past a minute, where diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index d1fbe9ec7..d4937df7e 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -29334,6 +29334,79 @@ static bool IsZero(Entity value) => value.InnerSimplified.Evaled is Number.Compl return written == expr ? null : Integration.ComputeAsTheSameQuestion(written, x, integrateByParts); } + /// + /// A quotient with a cubic in below the bar whose coefficients are + /// symbolic and whose discriminant vanishes identically, with the cubic written over its + /// roots: a x^3 + b x^2 + c x + d is a (x - r)^2 (x - s) with the double root + /// r = (9ad - bc)/(2(b^2 - 3ac)) and the simple one + /// s = (4abc - 9a^2 d - b^3)/(a(b^2 - 3ac)), or a (x + b/(3a))^3 where + /// b^2 - 3ac vanishes too. + /// + /// + /// Rubi's 1.3.1 1/(9x^3 - 9bx + 2 sqrt(3) b^(3/2)) was left unevaluated, and the same + /// written 9 (x - sqrt(b/3))^2 (x + 2 sqrt(b/3)) is answered in 60 ms. The factored + /// form is checked against the cubic before it is handed on. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveACubicWithARepeatedRootOverItsRoots(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + var (above, below) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(expr)); + if (!below.ContainsNode(x)) + return null; + Entity rest = Number.Integer.One; + Entity? written = null; + foreach (var factor in Mulf.LinearChildren(below)) + { + if (written is null && factor is Sumf or Minusf && TreeAnalyzer.TryGetPolynomial(factor, x, out var monomials) + && monomials.Keys.All(k => k.Sign >= 0 && k.CompareTo(EInteger.FromInt32(3)) <= 0) + && monomials.TryGetValue(EInteger.FromInt32(3), out var a) && !VanishesIdentically(a) + && monomials.Values.All(coefficient => !coefficient.ContainsNode(x)) + && monomials.Values.Any(coefficient => coefficient.InnerSimplified.Evaled is not Number.Complex)) + { + Entity Coefficient(int k) => monomials.TryGetValue(EInteger.FromInt32(k), out var value) ? value : Number.Integer.Zero; + var (b, c, d) = (Coefficient(2), Coefficient(1), Coefficient(0)); + var discriminant = 18 * a * b * c * d - 4 * MathS.Pow(b, 3) * d + MathS.Sqr(b) * MathS.Sqr(c) - 4 * a * MathS.Pow(c, 3) - 27 * MathS.Sqr(a) * MathS.Sqr(d); + if (!VanishesIdentically(discriminant)) + return null; + var depressed = (MathS.Sqr(b) - 3 * a * c).InnerSimplified; + Entity? over = null; + // The roots simplified first, which folds the quotients of the coefficients to + // short ones -- `sqrt(b)` rather than a product of six numbers and it -- where + // the simplified form still expands back to the cubic, and as they are where not. + foreach (var simplify in new[] { true, false }) + { + // Without the condition the simplification adds for b^2 - 3ac, which this + // case already assumes: the generic case is the one answered. + Entity Tidy(Entity e) => simplify ? e.Simplify() is Providedf(var value, _) ? value : e.Simplify() : e.InnerSimplified; + Entity candidate; + if (VanishesIdentically(depressed)) + candidate = a * MathS.Pow(x + Tidy(b / (3 * a)), 3); + else + { + var r = Tidy((9 * a * d - b * c) / (2 * depressed)); + var t = Tidy((4 * a * b * c - 9 * MathS.Sqr(a) * d - MathS.Pow(b, 3)) / (a * depressed)); + candidate = a * MathS.Sqr(x - r) * (x - t); + } + if (VanishesIdentically((candidate - factor).Expand())) + { + over = candidate; + break; + } + } + if (over is null) + return null; + written = over; + continue; + } + rest *= factor; + } + if (written is null) + return null; + return Integration.ComputeAsTheSameQuestion(above / (rest * written), x, integrateByParts); + } + /// /// A quotient whose denominator is a sum every term of which carries the same power of one /// linear form in , with that power taken out below the bar: diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 69f3bbe02..cbfa55111 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -1223,6 +1223,11 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) if (expr is Entity.Sumf or Entity.Minusf && (answer = IndefiniteIntegralSolver.SolveByGatheringACommonPolynomialFactorOfASum(expr, x, integrateByParts)) is { }) return answer; + // A cubic below the bar with symbolic coefficients and a repeated root, written over + // its roots: `1/(x^3 - 3b x + 2b^(3/2))` is `1/((x - sqrt(b))^2 (x + 2 sqrt(b)))`. + if (expr is Entity.Divf or Entity.Powf + && (answer = IndefiniteIntegralSolver.SolveACubicWithARepeatedRootOverItsRoots(expr, x, integrateByParts)) is { }) + return answer; // The same factor taken out of a sum below the bar: `1/(a x + b x/ln(c x^n))` is // `1/(x (a + b/ln(c x^n)))`, a function of the logarithm beside its differential. if (expr is Entity.Divf or Entity.Powf diff --git a/Sources/Tests/UnitTests/Calculus/ACubicWithARepeatedRootIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ACubicWithARepeatedRootIntegralTest.cs new file mode 100644 index 000000000..458efcf48 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ACubicWithARepeatedRootIntegralTest.cs @@ -0,0 +1,57 @@ +// +// 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 +{ + /// + /// A cubic with symbolic coefficients and a repeated root below the bar, written over its + /// roots: Rubi's 1.3.1 1/(9x^3 - 9bx + 2 sqrt(3) b^(3/2)), which was left unevaluated. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ACubicWithARepeatedRootIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + var pinned = integral.Substitute("C", 0); + Entity original = integrand.ToEntity(); + foreach (var (name, value) in pins) + { + pinned = pinned.Substitute(name, value); + original = original.Substitute(name, value); + } + var derivative = pinned.Differentiate("x"); + // Away from the roots, at b > 0, where the coefficients are real. + foreach (var at in new[] { -3.1, -0.4, 0.3, 2.7 }) + { + 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) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(difference / scale < 1e-8, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + [Theory] + [InlineData("1/(-9*b*x+9*x^3+2*b^(3/2)*sqrt(3))")] + [InlineData("x/(x^3-3*b*x+2*b^(3/2))")] + [InlineData("1/(x^3+3*k*x^2+3*k^2*x+k^3)")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("b", 1.3), ("k", 0.7)); + DifferentiatesBack(integrand, ("b", 2.2), ("k", -1.6)); + } + } +}