From c23be45ee3ae000df2974ac98eb37010db3d67d1 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 19 Oct 2026 23:58:20 +0000 Subject: [PATCH] Integrate a power of x times the logarithm of a polynomial plus a root, by parts With D = A^2 - B^2 Q, the derivative of ln(A + B sqrt(Q)) is D'/(2 D) + (Q (A B' - A' B) + A B Q'/2)/(D sqrt(Q)), an identity of the algebra with no branch in it, so by parts what is left is a rational function and one over the root, which the rules for those answer. The search differentiated the logarithm as written, a quotient with the root in both parts, and ran past the budget. Only for a D that is not constant: the inverse hyperbolic functions, whose D is, are answered already. Rubi's 3.5: rows 136, 143, 146 and 147 go from unevaluated or timed out to solved, 0 wrong. Families 0 and 1 and samples of 2-8 otherwise identical; gate and suite pass. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 12 +++ .../Integration/IndefiniteIntegralSolver.cs | 91 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 2 + ...imesALogarithmOfARadicalSumIntegralTest.cs | 50 ++++++++++ 4 files changed, 155 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/APowerTimesALogarithmOfARadicalSumIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 5ec2f6700..fdd6a2b3b 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -231,6 +231,18 @@ It is written out again in the precision asked for now | `Rational.Create(128, 12167)`'s `EDecimal`, read at the default precision and then under 300 digits | 100 digits | 300 digits | | `sqrt(46) * 128/12167` evaluated under 300 digits, with that rational | relative error `4.7e-101` | correct to 300 digits | +### A power of x times the logarithm of a polynomial plus a root is integrated by parts over the conjugate + +**Answers where there were none.** `x^3 ln(-1 + 4 x + 4 sqrt((x - 1) x))` was declined, and so were three more of Rubi's 3.5, +with `x^(3/2)`, `x^(-3/2)` and `x^(-5/2)` beside the same logarithm. With `D = A^2 - B^2 Q`, the +derivative of `ln(A + B sqrt(Q))` is `D'/(2 D) + (Q (A B' - A' B) + A B Q'/2)/(D sqrt(Q))` -- here `D = 8 x + 1` -- so by +parts what is left is a rational function and one over the root +([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x^3*ln(-1+4*x+4*sqrt((x-1)*x))".ToEntity().Integrate("x")` | `integral(...)` | `x^4 ln(...)/4` minus a polynomial, `ln(8 x + 1)` and terms over `sqrt(x^2 - x)` | + ### The last digits of an evaluated logarithm, exponential, real power or complex power **Silent, and in the hundredth digit.** `ln`, `log`, `e^x`, a real base to a real power and the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 6986f050e..ed165e985 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -442,6 +442,97 @@ private static bool IsAnInverseFunctionTimesARationalFunction(Entity expr, Entit return inverse == 1; } + /// + /// A power of x times the logarithm of A + B sqrt(Q), for polynomials A, + /// B and Q, by parts, with the logarithm's derivative written over the conjugate: + /// with D = A^2 - B^2 Q, it is + /// D'/(2 D) + (Q (A B' - A' B) + A B Q'/2)/(D sqrt(Q)), a rational function and one over + /// the root, which the rules for those answer. Rubi's 3.5, + /// x^3 ln(-1 + 4 x + 4 sqrt((x - 1) x)), where D = 8 x + 1. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// + /// The search differentiates the logarithm as written, a quotient with the root in both + /// parts, and ran past the budget on what was left. Only for a D that is not a + /// constant: for one that is, ln(x + sqrt(x^2 + 1)) and the other inverse hyperbolic + /// functions, the derivative is the root's alone, and the rules for those answer them. + /// The derivative is an identity of the algebra, sqrt(Q)^2 = Q, with no branch in it. + /// + internal static Entity? SolveByPartsAgainstALogarithmOfARadicalSum(Entity expr, Entity.Variable x) + { + Entity constant = Number.Integer.One; + Logf? logarithm = null; + Entity power = Number.Integer.Zero; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (!factor.ContainsNode(x)) + { + constant = underneath ? constant / factor : constant * factor; + continue; + } + if (!underneath && logarithm is null && factor is Logf ln && ln.Base == MathS.e) + { + logarithm = ln; + continue; + } + Entity? k = factor == x ? Number.Integer.One : factor is Powf(var raisedX, Number.Rational r) && raisedX == x ? r : null; + if (k is null) + return null; + power = underneath ? power - k : power + k; + } + if (logarithm is null || logarithm.Antilogarithm is not (Sumf or Minusf) || power.InnerSimplified.Evaled is not Number.Rational m + || m == Number.Integer.MinusOne) + return null; + // The argument as A + B sqrt(Q), term by term. + Entity a = Number.Integer.Zero, b = Number.Integer.Zero; + Entity? radicand = null; + foreach (var term in Sumf.LinearChildren(logarithm.Antilogarithm)) + { + Entity? root = null; + Entity rest = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(term)) + { + if (root is null && factor is Powf(var q, Number.Rational half) && half == Number.Rational.Create(1, 2) && q.ContainsNode(x)) + root = q; + else + rest *= factor; + } + if (root is null) + { + a += term; + continue; + } + if (radicand is not null && radicand != root) + return null; + radicand = root; + b += rest; + } + if (radicand is null + || !TreeAnalyzer.TryGetPolynomial(a, x, out var aRead) || aRead.Keys.Any(k => k.Sign < 0) + || !TreeAnalyzer.TryGetPolynomial(b, x, out var bRead) || bRead.Keys.Any(k => k.Sign < 0) + || !TreeAnalyzer.TryGetPolynomial(radicand, x, out var qRead) || qRead.Keys.Any(k => k.Sign < 0) || qRead.Keys.All(k => k.IsZero)) + return null; + // D = A^2 - B^2 Q, and the numerator over the root, as polynomials. + Entity Written(Entity polynomial) => TreeAnalyzer.TryGetPolynomial(polynomial, x, out var read) + ? read.Where(monomial => monomial.Value.InnerSimplified.Evaled is not Number.Complex { IsZero: true }) + .Aggregate((Entity)Number.Integer.Zero, (sum, monomial) => sum + monomial.Value.InnerSimplified * (monomial.Key.IsZero ? Number.Integer.One : monomial.Key.Equals(EInteger.One) ? x : MathS.Pow(x, Number.Integer.Create(monomial.Key)))) + : polynomial; + var d = Written(a * a - b * b * radicand); + if (!TreeAnalyzer.TryGetPolynomial(d, x, out var dRead) || dRead.Keys.All(k => k.IsZero) || d == Number.Integer.Zero) + return null; + var (da, db, dq) = (a.Differentiate(x), b.Differentiate(x), radicand.Differentiate(x)); + var overTheRoot = Written(radicand * (a * db - da * b) + a * b * dq / 2); + var raised = (m + 1).InnerSimplified; + Entity xRaised = raised == Number.Integer.One ? x : MathS.Pow(x, raised); + var rational = xRaised * Written(d.Differentiate(x)) / (2 * d); + var radical = xRaised * overTheRoot / (d * MathS.Sqrt(radicand)); + if (Integration.ComputeAsAQuestionOfItsOwn(rational, x, integrateByParts: true) is not { } first + || Integration.ComputeAsAQuestionOfItsOwn(radical, x, integrateByParts: true) is not { } second + || first.Nodes.Concat(second.Nodes).Any(node => node is Integralf || node == MathS.NaN)) + return null; + return (constant * (xRaised * logarithm - first - second) / raised).InnerSimplified; + } + /// /// An integrand that is a constant multiple of D'/D for a sum D below the /// bar holding a root, decided at sampled points and answered as that multiple of diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 2f5e7b9b9..22dc0dac3 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -787,6 +787,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A power of a quotient of linears beside whole powers of them, which is how e^(n acoth(a x)) // over a power of c - a^2 c x^2 arrives folded, under t = the quotient. if ((answer = IndefiniteIntegralSolver.SolveByTheMoebiusSubstitutionOfAQuotientOfLinears(expr, x)) is { }) return answer; + // And a power of x times the logarithm of A + B sqrt(Q), by parts, its derivative over the conjugate. + if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstALogarithmOfARadicalSum(expr, x)) is { }) return answer; // A power of an exponential with a positive base is the exponential of the product, // exactly, and only that spelling is one the exponential rules read. if ((answer = IndefiniteIntegralSolver.SolveByFlatteningAPowerOfAnExponential(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/APowerTimesALogarithmOfARadicalSumIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerTimesALogarithmOfARadicalSumIntegralTest.cs new file mode 100644 index 000000000..155ef7a9b --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerTimesALogarithmOfARadicalSumIntegralTest.cs @@ -0,0 +1,50 @@ +// +// 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 power of x times ln(A + B sqrt(Q)), by parts, with the logarithm's derivative written + /// over the conjugate, D = A^2 - B^2 Q: a rational function plus one over the root. The + /// search differentiated it as written and ran past its budget. Rubi's 3.5. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class APowerTimesALogarithmOfARadicalSumIntegralTest + { + [Theory] + [InlineData("ln(-1 + 4*x + 4*sqrt((x - 1)*x))")] + [InlineData("x^3*ln(-1 + 4*x + 4*sqrt((x - 1)*x))")] + [InlineData("x*ln(2*x + sqrt(x^2 + 3))")] + public void ByPartsOverTheConjugate(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + var derivative = integral.Substitute("C", 0).Differentiate("x"); + var original = integrand.ToEntity(); + var compared = 0; + // Where the root is real and the logarithm's argument positive. + foreach (var at in new[] { 1.2, 1.7, 2.3, 3.1, 4.4, 6.9 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)), + $"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}"); + } + } +}