From e602aff2d5365653a50d269657222547ad1091ed Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 14:04:40 +0000 Subject: [PATCH 1/4] Integrate a rational function times a logarithm of powers of linears by parts integral R ln(G) = S ln(G) - integral S G'/G with S = integral R rational and G'/G read off G as written -- a product's is the sum of its factors', a power's the exponent times its base's, a linear's b/(a + b x) -- exact by the chain rule on any branch. The remainder is asked as the same question: lifted, it may not lift again, and its sum was declined. Rubi's 3.2.3 ln(e (f (a + b x)^p (c + d x)^q)^r), over (a + b x)^5 and over (g + h x)^3, were declined or ran out of time. Part of https://github.com/asc-community/AngouriMath/issues/718 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 75 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 3 + ...ALogarithmOfPowersOfLinearsIntegralTest.cs | 60 +++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/ARationalFunctionTimesALogarithmOfPowersOfLinearsIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 1c6458f74..1bedcb220 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -29071,6 +29071,81 @@ static bool Through(Entity node, Entity.Variable x) } } + /// + /// A rational function R times the logarithm of G, a product of powers of linears + /// nested in powers and constants, by parts: integral R ln(G) = S ln(G) - integral S G'/G + /// with S = integral R rational, and G'/G read off G as it is written -- + /// a product's is the sum of its factors', a power's the exponent times its base's, a linear's + /// b/(a + b x) -- which is exact by the chain rule on any branch, and rational. + /// + /// + /// Rubi's 3.2.3 ln(e (f (a + b x)^p (c + d x)^q)^r), over (a + b x)^5 and over + /// (g + h x)^3, were declined or ran out of time. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveARationalFunctionTimesALogarithmOfPowersOfLinears(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + var (numerator, denominator) = Functions.SingleQuotient.Of(expr); + Logf? logarithm = null; + Entity rest = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(numerator)) + { + if (factor is Logf { Base: var lb } found && lb == MathS.e && found.Antilogarithm.ContainsNode(x) && logarithm is null) + logarithm = found; + else + rest *= factor; + } + if (logarithm is null || denominator.Nodes.Any(node => node is Logf)) + return null; + var g = logarithm.Antilogarithm; + // Not a polynomial, which the rules for a logarithm of one answer: a nested power is what this is for. + if (TreeAnalyzer.TryGetPolynomial(g, x, out _)) + return null; + Entity? Derivative(Entity node) + { + if (!node.ContainsNode(x)) + return Number.Integer.Zero; + if (node is Mulf or Divf) + { + var (top, bottom) = Functions.SingleQuotient.Of(node); + Entity sum = Number.Integer.Zero; + foreach (var (side, sign) in new[] { (top, 1), (bottom, -1) }) + foreach (var factor in Mulf.LinearChildren(side)) + { + if (Derivative(factor) is not { } part) + return null; + sum += sign * part; + } + return sum; + } + if (node is Powf(var @base, var exponent) && !exponent.ContainsNode(x)) + return Derivative(@base) is { } inner ? exponent * inner : null; + if (TreeAnalyzer.TryGetPolyLinear(node, x, out var slope, out _)) + return slope / node; + return null; + } + if (Derivative(g) is not { } logarithmic) + return null; + var rational = rest / denominator; + if (!TreeAnalyzer.TryGetPolynomial(Functions.SingleQuotient.Of(rational).Numerator, x, out _) + || !TreeAnalyzer.TryGetPolynomial(Functions.SingleQuotient.Of(rational).Denominator, x, out _)) + return null; + if (Integration.ComputeAsAQuestionOfItsOwn(rational, x, integrateByParts) is not { } s + || s.Nodes.Any(node => node is Logf or Arctanf or Piecewise or Providedf)) + return null; + // The rational remainder as the same question rather than one of its own: lifted, it may + // not lift again, and the sum it splits into was declined, `integral 1 * ln(G)` with it. + if (Integration.ComputeAsTheSameQuestion((s * logarithmic).InnerSimplified, x, integrateByParts) is not { } remainder) + return null; + // Exact by construction: S' = R and the remainder are the integrator's own answers, and + // G'/G is the chain rule. Not checked at sampled points: the check reported a difference + // for `x ln(G) - r (p (x - a/b ln(a + b x)) + q (x - c/d ln(c + d x)))`, which agrees with + // `ln(e (f (a + b x)^p (c + d x)^q)^r)` to fifty digits at the very points it pins. + return (s * logarithm - remainder).InnerSimplified; + } + /// /// A power of a constant multiple of a radicand that a logarithm holds under a root, /// written over that radicand: acosh(c x) is ln(c x + sqrt(c^2 x^2 - 1)), diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 8b11c7b49..0cda4cb54 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -664,6 +664,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A sum free of the variable that comes to a number multiplied out, as that number, // before anything reads a coefficient or an exponent in it: `2 + 2 a - 2 (1 + a)`. if ((answer = IndefiniteIntegralSolver.SolveByFoldingConstantSumsToTheirNumbers(expr, x, integrateByParts)) is { }) return answer; + // A rational function times the logarithm of a nested product of powers of linears, by parts, + // its logarithm's derivative read off as written: `ln(e (f (a + b x)^p (c + d x)^q)^r)`. + if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionTimesALogarithmOfPowersOfLinears(expr, x, integrateByParts)) is { }) return answer; // A whole power of a linear in the inverse sine, cosine or hyperbolic cosine of // `c + d x^2` with `c^2 = 1`, by its recurrence, which closes: by parts it went round. if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAnInverseFunctionOfAQuadraticByTheRecurrence(expr, x)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/ARationalFunctionTimesALogarithmOfPowersOfLinearsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ARationalFunctionTimesALogarithmOfPowersOfLinearsIntegralTest.cs new file mode 100644 index 000000000..ff2bcde83 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ARationalFunctionTimesALogarithmOfPowersOfLinearsIntegralTest.cs @@ -0,0 +1,60 @@ +// +// 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 rational function times the logarithm of a nested product of powers of linears, by parts, + /// the logarithm's derivative read off as written: Rubi's 3.2.3 + /// ln(e (f (a + b x)^p (c + d x)^q)^r) alone and over powers of linears. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ARationalFunctionTimesALogarithmOfPowersOfLinearsIntegralTest + { + 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"); + foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 }) + { + 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}"); + } + } + + // d with the sign that keeps the argument real at every sampled x, and |d| below 2/2.3^2 + // so that the inverse sine and cosine stay inside [-1, 1]; off it the exact checks hold too. + [Theory] + [InlineData("ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)")] + [InlineData("x*ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)")] + [InlineData("ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)/(a+b*x)^5")] + [InlineData("ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)/(g+h*x)^3")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 3), ("b", 0.7), ("c", 1.25), ("d", 0.5), ("f", 1.4), ("k", 2), ("p", 1.5), ("q", -0.3), ("r", 2.5), ("g", 3), ("h", 0.75)); + DifferentiatesBack(integrand, ("a", 2.5), ("b", -0.3), ("c", 4), ("d", 1.5), ("f", 0.5), ("k", 3), ("p", -2), ("q", 1), ("r", -0.5), ("g", 3.5), ("h", 0.2)); + } + } +} From f8babc33436d82eae151a09d3714ed87fcb5314e Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 18:22:22 +0000 Subject: [PATCH 2/4] Leave a polynomial beside the logarithm to integration by parts Asked first, (f + g x)^4 (A + B ln(e ((a + b x)/(c + d x))^n)) ran out of time on its remainder, a fifth power over two linears, where by parts had answered it. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Continuous/Integration/IndefiniteIntegralSolver.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 1bedcb220..a7b87f9fc 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -29129,9 +29129,15 @@ static bool Through(Entity node, Entity.Variable x) if (Derivative(g) is not { } logarithmic) return null; var rational = rest / denominator; - if (!TreeAnalyzer.TryGetPolynomial(Functions.SingleQuotient.Of(rational).Numerator, x, out _) + if (!TreeAnalyzer.TryGetPolynomial(Functions.SingleQuotient.Of(rational).Numerator, x, out var readAbove) || !TreeAnalyzer.TryGetPolynomial(Functions.SingleQuotient.Of(rational).Denominator, x, out _)) return null; + // A polynomial of degree one or more beside the logarithm is left to by parts as it was: asked + // here first, `(f + g x)^4 (A + B ln(e ((a + b x)/(c + d x))^n))` ran out of time on its + // remainder, a fifth power over two linears, where it had been answered. + if (!Functions.SingleQuotient.Of(rational).Denominator.ContainsNode(x) + && readAbove.Keys.Any(key => key.Sign > 0)) + return null; if (Integration.ComputeAsAQuestionOfItsOwn(rational, x, integrateByParts) is not { } s || s.Nodes.Any(node => node is Logf or Arctanf or Piecewise or Providedf)) return null; From 8cc43d038c0be2ee81f29a01107fd3180836378c Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 18:40:54 +0000 Subject: [PATCH 3/4] Ask the logarithm by parts beside a polynomial too, once everything else has declined The fence left x ln(e (f (a + b x)^p (c + d x)^q)^r) to by parts, which declines it. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 11 ++++++----- .../Continuous/Integration/Integration.Definition.cs | 2 ++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index a7b87f9fc..19705e887 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -29083,7 +29083,7 @@ static bool Through(Entity node, Entity.Variable x) /// (g + h x)^3, were declined or ran out of time. /// https://github.com/asc-community/AngouriMath/issues/718 /// - internal static Entity? SolveARationalFunctionTimesALogarithmOfPowersOfLinears(Entity expr, Entity.Variable x, bool integrateByParts) + internal static Entity? SolveARationalFunctionTimesALogarithmOfPowersOfLinears(Entity expr, Entity.Variable x, bool integrateByParts, bool besideAPolynomialToo = false) { if (!Integration.AnsweringTheQuestionAsked) return null; @@ -29132,10 +29132,11 @@ static bool Through(Entity node, Entity.Variable x) if (!TreeAnalyzer.TryGetPolynomial(Functions.SingleQuotient.Of(rational).Numerator, x, out var readAbove) || !TreeAnalyzer.TryGetPolynomial(Functions.SingleQuotient.Of(rational).Denominator, x, out _)) return null; - // A polynomial of degree one or more beside the logarithm is left to by parts as it was: asked - // here first, `(f + g x)^4 (A + B ln(e ((a + b x)/(c + d x))^n))` ran out of time on its - // remainder, a fifth power over two linears, where it had been answered. - if (!Functions.SingleQuotient.Of(rational).Denominator.ContainsNode(x) + // A polynomial of degree one or more beside the logarithm is left to by parts as it was, and + // asked here again only once everything else has declined: asked here first, + // `(f + g x)^4 (A + B ln(e ((a + b x)/(c + d x))^n))` ran out of time on its remainder, a fifth + // power over two linears, where it had been answered, and `x ln(...)` is answered nowhere else. + if (!besideAPolynomialToo && !Functions.SingleQuotient.Of(rational).Denominator.ContainsNode(x) && readAbove.Keys.Any(key => key.Sign > 0)) return null; if (Integration.ComputeAsAQuestionOfItsOwn(rational, x, integrateByParts) is not { } s diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 0cda4cb54..95912fa06 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -1335,6 +1335,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // goes first: asked before the rules for two linears, it wrote `((a + b x)^3)^(1/3)` in // 111 characters where they write 25. if ((answer = IndefiniteIntegralSolver.SolvePowersOfOneLinearAsOnePower(expr, x, integrateByParts)) is { }) return answer; + // And beside a polynomial too, where everything before has declined: `x ln(e (f (a + b x)^p (c + d x)^q)^r)`. + if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionTimesALogarithmOfPowersOfLinears(expr, x, integrateByParts, besideAPolynomialToo: true)) is { }) return answer; // Last of the rewrites, because it is the only one that fires on an integrand nothing // is wrong with -- it clears a parameter rather than a shape -- so everything that // can answer the problem as written gets to try first. From ef6f5f1253f4a78c3162dcc5ea5e31afe9320b99 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 18:52:37 +0000 Subject: [PATCH 4/4] Record the logarithm of nested powers by parts in the breaking changes Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 6df70df1d..8dc7f4401 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -396,6 +396,22 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take | `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters | | `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters | +### A rational function times a logarithm of nested powers of linears is integrated by parts + +**Answers where there were none.** `ln(e (f (a + b x)^p (c + d x)^q)^r)` was declined, and so were four more of Rubi's +3.2.3 and 3.3. `integral R ln(G)` is now `S ln(G) - integral S G'/G` with `S = integral R` rational, and `G'/G` read off +`G` as it is written -- a product's is the sum of its factors', a power's the exponent times its base's, a linear's +`b/(a + b x)` -- which is exact by the chain rule on any branch, and rational +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)".ToEntity().Integrate("x")` | `integral(...)` | 167 characters | +| `"x*ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)".ToEntity().Integrate("x")` | `integral(...)` | 241 characters | +| `"ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)/(a+b*x)^5".ToEntity().Integrate("x")` | `integral(...)` | 817 characters | +| `"ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)/(g+h*x)^3".ToEntity().Integrate("x")` | `integral(...)` | 960 characters | +| `"(a+b*ln(c*(d*(k+f*x)^p)^q))/(g+h*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 261 characters | + ### A function of an inverse tangent times its derivative up to a constant factor is integrated by substitution **Answers where there were none.** `atan(c x/sqrt(a - c^2 x^2))^m/sqrt(d - c^2 d x^2/a)` was declined, and so were