From 17423ccba6152c4c5461525e05772155c183c197 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 1 Oct 2026 06:39:36 +0000 Subject: [PATCH] A logarithm of x is matched to a logarithm of a multiple of x below the bar By parts on li(b x)/x takes the antiderivative of 1/x, and with ln(x) the remainder was b ln(x)/ln(b x), which nothing read. ln(b x) is as much an antiderivative of 1/x, and taken so, the logarithm the derivative of li(b x) divides by cancels: li(b x)/x is li(b x) ln(b x) - b x, which is Rubi's answer. The constant of integration is matched to a logarithm of a multiple of x below the bar now, as it is matched to a polynomial divisor. Measured on the Rubi corpus, master at 16203aa6 and this change on it, run side by side: family 8 380 -> 381 of 420 (li(b x)/x); family 3 at twenty a file 172 and 173 of 180, family 0 1756 of 1814, and families 1, 2 and 4 to 7 at five a file 771 and 770 of 867. 0 wrong everywhere. Run alone, the three that moved are li(b x)/x, answered here and not on master, and 3.2.3 #30 and 6.7.1 #180, answered on both in 19 to 24 s: their side-by-side difference was load. The unit tests pass, 14,262, and the performance gate passes on c6f7a34c, which is this change on 16203aa6: allocation is what the baseline says on all 19 gated benchmarks. SpecialFunctionsByPartsTest checks li(b x)/x at positive points, where li(b x) is real. Part of #1501. Co-Authored-By: Claude Opus 5.5 --- BREAKING-CHANGES.md | 13 ++++++++ .../Integration/IndefiniteIntegralSolver.cs | 31 +++++++++++++++++++ .../Calculus/SpecialFunctionsByPartsTest.cs | 16 +++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 90c8c587b..bc6dc1b29 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -2353,6 +2353,19 @@ a sample of its families 1 to 7 are answered alone as they were. | `"x^2*Ei(a+b*x)^2".Integrate("x")` | `a ^ 2 * Ei * x ^ 3 / 3 + a * b * 2 * Ei * x ^ 4 / 4 + b ^ 2 * Ei * x ^ 5 / 5 + C`, with `Ei` a variable | an antiderivative in `Ei(a + b x)` and `Ei(2 (a + b x))` | | `"erf(a+b*x)^2".Integrate("x")` | `UnrecognizedFunctionParseException`: there is no function `erf` | an antiderivative in `erf` | +### A logarithm of `x` is matched to a logarithm of a multiple of `x` below the bar + +By parts on `li(b x)/x` takes the antiderivative of `1/x`, and with `ln(x)` the remainder was +`b ln(x)/ln(b x)`, which nothing read. `ln(b x)` is as much an antiderivative of `1/x`, and taken so, +the logarithm the derivative of `li(b x)` divides by cancels: `li(b x)/x` is `li(b x) ln(b x) - b x` +([#1501](https://github.com/asc-community/AngouriMath/issues/1501)). `li` had no reading in 2.5.0, +which its own entry records, and no integrand without it changes: Rubi's independent suites, its +family 3 at twenty a file and a sample of the others are answered alone as they were. + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"li(b*x)/x".Integrate("x")` | `li * b * x + C`, with `li` a variable | `li(b * x) * ln(b * x) - b * x + C` | + ### An exponential or a hyperbolic function over several linears is split into partial fractions over them `e^x/(x (x + 1))` was left unevaluated, where `e^x/x` and `e^x/(x + 1)` were each answered with diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index c4354b9a5..0a8479415 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -1679,6 +1679,18 @@ private static bool IsASpecialFunctionOfALinear(Entity factor, Variable x) // divisor, where the division leaves a constant. static Entity WithTheConstantMatchedTo(Entity antiderivative, Entity derivativeOfV, Variable x) { + // A logarithm of x, against a logarithm of a multiple of x below the bar: ln(c x) is + // as much an antiderivative of 1/x as ln(x) is, and taken so, the logarithm the + // derivative divides by cancels. li(b x)/x is li(b x) ln(b x) - b x, where with ln(x) + // the remainder was ln(x)/ln(b x), which nothing read. + // https://github.com/asc-community/AngouriMath/issues/1501 + if (TheLogarithmOfTheVariable(antiderivative, x) is (var logarithmCoefficient, true) + && Sumf.LinearChildren(Functions.PartialFractions.Bare(derivativeOfV)) + .SelectMany(term => Mulf.LinearChildren(Functions.SingleQuotient.Of(term).Denominator)) + .FirstOrDefault(factor => factor is Logf(var logBase, var argument) && logBase == MathS.e + && TreeAnalyzer.TryGetPolyLinear(argument, x, out var multiple, out var offset) + && TreeAnalyzer.IsZero(offset) && !TreeAnalyzer.IsZero(multiple)) is { } logarithmBelow) + return logarithmCoefficient * logarithmBelow; if (!TreeAnalyzer.TryGetPolynomial(antiderivative, x, out _)) return antiderivative; var divisors = Sumf.LinearChildren(Functions.PartialFractions.Bare(derivativeOfV)) @@ -20367,6 +20379,25 @@ private static bool TheRemainderIsAskedTermByTerm(Entity v, Entity u, Variable x || u == special; } + /// + /// as a constant times ln(x), the coefficient and + /// , or where it is not one. + /// + private static (Entity Coefficient, bool IsOne) TheLogarithmOfTheVariable(Entity expr, Variable x) + { + static bool IsTheLogarithm(Entity factor, Variable x) => factor is Logf(var logBase, var argument) && logBase == MathS.e && argument == x; + if (IsTheLogarithm(expr, x)) + return (Number.Integer.One, true); + if (expr is Mulf(var left, var right)) + { + if (IsTheLogarithm(right, x) && !left.ContainsNode(x)) + return (left, true); + if (IsTheLogarithm(left, x) && !right.ContainsNode(x)) + return (right, true); + } + return (Number.Integer.Zero, false); + } + /// /// The factors of that are polynomials in of /// positive degree, against the rest: x sin(b x) is x and sin(b x). diff --git a/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs b/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs index c83e8806d..4139e5635 100644 --- a/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs +++ b/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs @@ -30,13 +30,17 @@ public sealed class SpecialFunctionsByPartsTest /// rule is asked the symbolic question. /// private static void DifferentiatesBack(string integrand, params (string Name, string Value)[] pins) + => DifferentiatesBackAt(Points, integrand, pins); + + /// at . + private static void DifferentiatesBackAt(double[] points, string integrand, params (string Name, string Value)[] pins) { var integral = integrand.ToEntity().Integrate("x"); Assert.DoesNotContain("integral(", integral.Stringize()); Entity Pinned(Entity e) => pins.Aggregate(e, (current, pin) => current.Substitute(pin.Name, pin.Value.ToEntity())); var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); var original = Pinned(integrand.ToEntity()); - foreach (var at in Points) + foreach (var at in points) { var got = derivative.Substitute("x", at).EvalNumerical(); var want = original.Substitute("x", at).EvalNumerical(); @@ -73,6 +77,16 @@ public void ASpecialFunctionAloneIsByPartsAgainstOne(string integrand) public void TheLogarithmicIntegralIsByParts(string integrand) => DifferentiatesBack(integrand, ("a", "3"), ("b", "13/10")); + /// + /// Over x, the antiderivative of 1/x is taken as ln(b x), the logarithm the + /// derivative of li(b x) divides by, and the remainder is b: + /// li(b x) ln(b x) - b x. At positive x, where li(b x) is real, and off + /// x = 1/b, where ln(b x) is 0. + /// + [Fact] + public void TheLogarithmicIntegralOverXIsOneRoundOfParts() + => DifferentiatesBackAt(new[] { 0.35, 1.45, 2.3 }, "li(b*x)/x", ("b", "13/10")); + /// /// Against a power of x, the special function is the factor differentiated: what is /// left is the power times e^(-u^2), e^u/u, sin(u)/u and the like.