Skip to content

Commit bf4be60

Browse files
Integrate a rational function times a logarithm of powers of linears by parts (#1910)
* 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 #718 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * 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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * 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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Record the logarithm of nested powers by parts in the breaking changes Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 87be872 commit bf4be60

4 files changed

Lines changed: 163 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,22 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take
396396
| `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters |
397397
| `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters |
398398

399+
### A rational function times a logarithm of nested powers of linears is integrated by parts
400+
401+
**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
402+
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
403+
`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
404+
`b/(a + b x)` -- which is exact by the chain rule on any branch, and rational
405+
([#718](https://github.com/asc-community/AngouriMath/issues/718)).
406+
407+
| Input | Was (2.5.0) | Now |
408+
|---|---|---|
409+
| `"ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)".ToEntity().Integrate("x")` | `integral(...)` | 167 characters |
410+
| `"x*ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)".ToEntity().Integrate("x")` | `integral(...)` | 241 characters |
411+
| `"ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)/(a+b*x)^5".ToEntity().Integrate("x")` | `integral(...)` | 817 characters |
412+
| `"ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)/(g+h*x)^3".ToEntity().Integrate("x")` | `integral(...)` | 960 characters |
413+
| `"(a+b*ln(c*(d*(k+f*x)^p)^q))/(g+h*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 261 characters |
414+
399415
### A function of an inverse tangent times its derivative up to a constant factor is integrated by substitution
400416

401417
**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

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29184,6 +29184,88 @@ bool HoldsItsValue(Entity factor)
2918429184
=> Functions.PartialFractions.HoldsAtSampledPoints(factor, factor.Substitute(x, Number.Rational.Create(3, 10)), x);
2918529185
}
2918629186

29187+
/// <summary>
29188+
/// A rational function <c>R</c> times the logarithm of <c>G</c>, a product of powers of linears
29189+
/// nested in powers and constants, by parts: <c>integral R ln(G) = S ln(G) - integral S G'/G</c>
29190+
/// with <c>S = integral R</c> rational, and <c>G'/G</c> read off <c>G</c> as it is written --
29191+
/// a product's is the sum of its factors', a power's the exponent times its base's, a linear's
29192+
/// <c>b/(a + b x)</c> -- which is exact by the chain rule on any branch, and rational.
29193+
/// </summary>
29194+
/// <remarks>
29195+
/// Rubi's 3.2.3 <c>ln(e (f (a + b x)^p (c + d x)^q)^r)</c>, over <c>(a + b x)^5</c> and over
29196+
/// <c>(g + h x)^3</c>, were declined or ran out of time.
29197+
/// https://github.com/asc-community/AngouriMath/issues/718
29198+
/// </remarks>
29199+
internal static Entity? SolveARationalFunctionTimesALogarithmOfPowersOfLinears(Entity expr, Entity.Variable x, bool integrateByParts, bool besideAPolynomialToo = false)
29200+
{
29201+
if (!Integration.AnsweringTheQuestionAsked)
29202+
return null;
29203+
var (numerator, denominator) = Functions.SingleQuotient.Of(expr);
29204+
Logf? logarithm = null;
29205+
Entity rest = Number.Integer.One;
29206+
foreach (var factor in Mulf.LinearChildren(numerator))
29207+
{
29208+
if (factor is Logf { Base: var lb } found && lb == MathS.e && found.Antilogarithm.ContainsNode(x) && logarithm is null)
29209+
logarithm = found;
29210+
else
29211+
rest *= factor;
29212+
}
29213+
if (logarithm is null || denominator.Nodes.Any(node => node is Logf))
29214+
return null;
29215+
var g = logarithm.Antilogarithm;
29216+
// Not a polynomial, which the rules for a logarithm of one answer: a nested power is what this is for.
29217+
if (TreeAnalyzer.TryGetPolynomial(g, x, out _))
29218+
return null;
29219+
Entity? Derivative(Entity node)
29220+
{
29221+
if (!node.ContainsNode(x))
29222+
return Number.Integer.Zero;
29223+
if (node is Mulf or Divf)
29224+
{
29225+
var (top, bottom) = Functions.SingleQuotient.Of(node);
29226+
Entity sum = Number.Integer.Zero;
29227+
foreach (var (side, sign) in new[] { (top, 1), (bottom, -1) })
29228+
foreach (var factor in Mulf.LinearChildren(side))
29229+
{
29230+
if (Derivative(factor) is not { } part)
29231+
return null;
29232+
sum += sign * part;
29233+
}
29234+
return sum;
29235+
}
29236+
if (node is Powf(var @base, var exponent) && !exponent.ContainsNode(x))
29237+
return Derivative(@base) is { } inner ? exponent * inner : null;
29238+
if (TreeAnalyzer.TryGetPolyLinear(node, x, out var slope, out _))
29239+
return slope / node;
29240+
return null;
29241+
}
29242+
if (Derivative(g) is not { } logarithmic)
29243+
return null;
29244+
var rational = rest / denominator;
29245+
if (!TreeAnalyzer.TryGetPolynomial(Functions.SingleQuotient.Of(rational).Numerator, x, out var readAbove)
29246+
|| !TreeAnalyzer.TryGetPolynomial(Functions.SingleQuotient.Of(rational).Denominator, x, out _))
29247+
return null;
29248+
// A polynomial of degree one or more beside the logarithm is left to by parts as it was, and
29249+
// asked here again only once everything else has declined: asked here first,
29250+
// `(f + g x)^4 (A + B ln(e ((a + b x)/(c + d x))^n))` ran out of time on its remainder, a fifth
29251+
// power over two linears, where it had been answered, and `x ln(...)` is answered nowhere else.
29252+
if (!besideAPolynomialToo && !Functions.SingleQuotient.Of(rational).Denominator.ContainsNode(x)
29253+
&& readAbove.Keys.Any(key => key.Sign > 0))
29254+
return null;
29255+
if (Integration.ComputeAsAQuestionOfItsOwn(rational, x, integrateByParts) is not { } s
29256+
|| s.Nodes.Any(node => node is Logf or Arctanf or Piecewise or Providedf))
29257+
return null;
29258+
// The rational remainder as the same question rather than one of its own: lifted, it may
29259+
// not lift again, and the sum it splits into was declined, `integral 1 * ln(G)` with it.
29260+
if (Integration.ComputeAsTheSameQuestion((s * logarithmic).InnerSimplified, x, integrateByParts) is not { } remainder)
29261+
return null;
29262+
// Exact by construction: S' = R and the remainder are the integrator's own answers, and
29263+
// G'/G is the chain rule. Not checked at sampled points: the check reported a difference
29264+
// for `x ln(G) - r (p (x - a/b ln(a + b x)) + q (x - c/d ln(c + d x)))`, which agrees with
29265+
// `ln(e (f (a + b x)^p (c + d x)^q)^r)` to fifty digits at the very points it pins.
29266+
return (s * logarithm - remainder).InnerSimplified;
29267+
}
29268+
2918729269
/// <summary>
2918829270
/// A power of a constant multiple of a radicand that a logarithm holds under a root,
2918929271
/// written over that radicand: <c>acosh(c x)</c> is <c>ln(c x + sqrt(c^2 x^2 - 1))</c>,

‎Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
664664
// A sum free of the variable that comes to a number multiplied out, as that number,
665665
// before anything reads a coefficient or an exponent in it: `2 + 2 a - 2 (1 + a)`.
666666
if ((answer = IndefiniteIntegralSolver.SolveByFoldingConstantSumsToTheirNumbers(expr, x, integrateByParts)) is { }) return answer;
667+
// A rational function times the logarithm of a nested product of powers of linears, by parts,
668+
// its logarithm's derivative read off as written: `ln(e (f (a + b x)^p (c + d x)^q)^r)`.
669+
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionTimesALogarithmOfPowersOfLinears(expr, x, integrateByParts)) is { }) return answer;
667670
// A function of an inverse of something not linear times its derivative up to a factor
668671
// constant though written with x: `atan(c x/sqrt(a - c^2 x^2))^2/sqrt(d - c^2 d x^2/a)`. Early,
669672
// 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)
13371340
// goes first: asked before the rules for two linears, it wrote `((a + b x)^3)^(1/3)` in
13381341
// 111 characters where they write 25.
13391342
if ((answer = IndefiniteIntegralSolver.SolvePowersOfOneLinearAsOnePower(expr, x, integrateByParts)) is { }) return answer;
1343+
// And beside a polynomial too, where everything before has declined: `x ln(e (f (a + b x)^p (c + d x)^q)^r)`.
1344+
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionTimesALogarithmOfPowersOfLinears(expr, x, integrateByParts, besideAPolynomialToo: true)) is { }) return answer;
13401345
// Last of the rewrites, because it is the only one that fires on an integrand nothing
13411346
// is wrong with -- it clears a parameter rather than a shape -- so everything that
13421347
// can answer the problem as written gets to try first.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// A rational function times the logarithm of a nested product of powers of linears, by parts,
16+
/// the logarithm's derivative read off as written: Rubi's 3.2.3
17+
/// <c>ln(e (f (a + b x)^p (c + d x)^q)^r)</c> alone and over powers of linears.
18+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
19+
/// </summary>
20+
[Trait("Area", "Calculus")]
21+
public sealed class ARationalFunctionTimesALogarithmOfPowersOfLinearsIntegralTest
22+
{
23+
private static void DifferentiatesBack(string integrand, params (string, double)[] pins)
24+
{
25+
var integral = integrand.ToEntity().Integrate("x");
26+
Assert.DoesNotContain("integral(", integral.Stringize());
27+
28+
var pinned = integral.Substitute("C", 0);
29+
Entity original = integrand.ToEntity();
30+
foreach (var (name, value) in pins)
31+
{
32+
pinned = pinned.Substitute(name, value);
33+
original = original.Substitute(name, value);
34+
}
35+
var derivative = pinned.Differentiate("x");
36+
foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 })
37+
{
38+
var got = derivative.Substitute("x", at).EvalNumerical();
39+
var want = original.Substitute("x", at).EvalNumerical();
40+
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
41+
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
42+
Assert.True(difference / scale < 1e-8,
43+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
44+
}
45+
}
46+
47+
// d with the sign that keeps the argument real at every sampled x, and |d| below 2/2.3^2
48+
// so that the inverse sine and cosine stay inside [-1, 1]; off it the exact checks hold too.
49+
[Theory]
50+
[InlineData("ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)")]
51+
[InlineData("x*ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)")]
52+
[InlineData("ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)/(a+b*x)^5")]
53+
[InlineData("ln(k*(f*(a+b*x)^p*(c+d*x)^q)^r)/(g+h*x)^3")]
54+
public void IsAnsweredAndDifferentiatesBack(string integrand)
55+
{
56+
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));
57+
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));
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)