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
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 44973d73f..1ca87d71a 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -29184,6 +29184,88 @@ bool HoldsItsValue(Entity factor)
=> Functions.PartialFractions.HoldsAtSampledPoints(factor, factor.Substitute(x, Number.Rational.Create(3, 10)), 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, bool besideAPolynomialToo = false)
+ {
+ 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 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, 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
+ || 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 176741cf2..8d03a8aa3 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 function of an inverse of something not linear times its derivative up to a factor
// constant though written with x: `atan(c x/sqrt(a - c^2 x^2))^2/sqrt(d - c^2 d x^2/a)`. Early,
// since it fires only where that factor holds x, and the rules before it spent 28 seconds on
@@ -1337,6 +1340,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.
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));
+ }
+ }
+}