Skip to content

Commit a4e7656

Browse files
Integrate a polynomial beside a logarithm of a quotient of linears by its logarithmic derivative (#2005)
By parts, the integral of P F is S F minus the integral of S F', and F' is now read with the power of the quotient written apart, B n (b/(a + b x) - d/(c + d x)), since (Q^n)'/Q^n is n Q'/Q. Taken whole, the derivative was two terms over a cubic, and each remainder was divided by it with simplified coefficients. Over a power of a linear, the question in u = f + g x is now asked without the `0 provided c != 0` the pole's own fraction carries, which the substitution search spent seconds declining. Pocket of 1,202 rows in Rubi's 3.x: 1,171 -> 1,179 solved, 23 -> 15 timeouts, 0 wrong on either arm. Samples of every family unchanged, except 3.x 220 -> 221. The ten rows that moved, re-run alone: 4/10 -> 10/10 solved; 3.2.1:258 20 s -> 58 ms (46.9 GB -> 0.03 GB), 3.2.1:48 17 s -> 2.8 s. No row's allocation grew past 1.3x. Part of #233. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent a567c8b commit a4e7656

3 files changed

Lines changed: 187 additions & 6 deletions

File tree

‎BREAKING-CHANGES.md‎

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

1101+
### A polynomial beside a logarithm of a power of a quotient of linears, and a logarithm over a power of a linear, are integrated shorter
1102+
1103+
**Answers where there were none, shorter and sooner than on the unreleased master.** `(a g + b g x)^4 (A + B ln(e ((a + b x)/(c + d x))^n))`
1104+
was declined, and so were the other three below, from Rubi's 3.2.1 and 3.2.2, a polynomial beside the same logarithm.
1105+
By parts, `integral P F` is `S F - integral S F'` with `S = integral P`, and `F'` is now read with the power of the
1106+
quotient written apart, `B n (b/(a + b x) - d/(c + d x))`, which is exact since `(Q^n)'/Q^n = n Q'/Q`. Taken whole, the
1107+
derivative was two terms over `(c + d x)^2 (a + b x)`, and the unreleased master divided each remainder by that cubic
1108+
with its coefficients simplified: 4,063 characters in seventeen seconds for the first, where it is 1,560 in two
1109+
([#233](https://github.com/asc-community/AngouriMath/issues/233)).
1110+
1111+
Over a power of a linear, `integral F/(f + g x)^k` by parts leaves partial fractions over `f + g x`, and the one at
1112+
the pole the antiderivative was matched to is a zero over a constant, which the inner simplification writes
1113+
`0 provided c != 0`. Written in `u = f + g x`, that condition stood at the top of the question, the substitution
1114+
search spent seconds declining it, and the fractions were then integrated one by one in x. The question in u is now
1115+
asked without it, the generic case: `(A + B ln(e (a + b x)/(c + d x)))/(f + g x)^5`, declined by 2.5.0, is 10,605
1116+
characters in under a second, where the unreleased master wrote 11,215 in eight. The same holds over powers of a
1117+
linear in Rubi's 3.1.4 and 3.3, which come out shorter too.
1118+
1119+
| Input | Was (2.5.0) | Now |
1120+
|---|---|---|
1121+
| `"(a*g+b*g*x)^4*(A+B*ln(k*((a+b*x)/(c+d*x))^n))".ToEntity().Integrate("x")` | `integral(...)` | 1,560 characters |
1122+
| `"(f+g*x)^3*(A+B*ln(k*((a+b*x)/(c+d*x))^n))".ToEntity().Integrate("x")` | `integral(...)` | 1,766 characters |
1123+
| `"(a*g+b*g*x)*(A+B*ln(k*((a+b*x)/(c+d*x))^n))".ToEntity().Integrate("x")` | `integral(...)` | 465 characters |
1124+
| `"(a*g+b*g*x)^3*(c*j+d*j*x)*(A+B*ln(k*((a+b*x)/(c+d*x))^n))".ToEntity().Integrate("x")` | `integral(...)` | 2,292 characters |
1125+
| `"(A+B*ln(k*(a+b*x)/(c+d*x)))/(f+g*x)^5".ToEntity().Integrate("x")` | `integral(...)` | 10,605 characters |
1126+
| `"(A+B*ln(k*(a+b*x)/(c+d*x)))/(f+g*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 6,284 characters |
1127+
| `"(a+b*ln(c*(d+h*x)^n))/(f+g*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 565 characters |
1128+
| `"(a+b*ln(c*x^n))/(d+h*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 266 characters |
1129+
11011130
### A secant or cosecant power of a logarithm beside the power two below it is integrated as the derivative of a product
11021131

11031132
**Answers where there were none.** `-(1 + b^2 n^2) sec(a + b ln(c x^n)) + 2 b^2 n^2 sec(a + b ln(c x^n))^3` was

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

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16148,12 +16148,40 @@ node is Powf(var @base, var power) && @base.ContainsNode(u)
1614816148
if (inU.ContainsNode(x))
1614916149
return null;
1615016150
// dx = du/d.
16151-
if (Integration.ComputeAsAQuestionOfItsOwn((inU / slope).InnerSimplified, u, integrateByParts) is not { } answer)
16151+
var question = (inU / slope).InnerSimplified;
16152+
// A zero over a constant among partial fractions over the linear -- what a pole whose
16153+
// residue vanishes leaves -- is written `0 provided c != 0` by the inner simplification,
16154+
// and the condition rises to the top of the question in u, where only the substitution
16155+
// search reads a question: it spent seven seconds declining the remainder by parts of
16156+
// Rubi's `(A + B ln(e (a + b x)/(c + d x)))/(f + g x)^5` for it, and the sum was then
16157+
// integrated term by term in x, in a longer answer. The condition says only that a
16158+
// constant is not zero, which the generic case takes, so the question is asked without
16159+
// it. Not beside a polynomial term, which in u integrates to a multiple of the linear
16160+
// where in x it is one of x, the longer spelling.
16161+
if (question is Providedf(var unconditioned, var condition) && IsANonzeroCondition(condition, u)
16162+
&& expr is Sumf or Minusf && Sumf.LinearChildren(expr).Any(IsAZeroOverAConstant)
16163+
&& Sumf.LinearChildren(expr).All(term => IsAZeroOverAConstant(term) || term.ContainsNode(x) && !TreeAnalyzer.TryGetPolynomial(term, x, out _)))
16164+
question = unconditioned;
16165+
if (Integration.ComputeAsAQuestionOfItsOwn(question, u, integrateByParts) is not { } answer)
1615216166
return null;
1615316167
var back = answer.Substitute(u, linear);
1615416168
return back.Nodes.Any(node => node == MathS.NaN) ? null : back;
16169+
16170+
bool IsAZeroOverAConstant(Entity term) => term is Divf(var zero, var constant) && zero == Number.Integer.Zero && !constant.ContainsNode(x);
1615516171
}
1615616172

16173+
/// <summary>
16174+
/// Whether <paramref name="condition"/> says only that constants are not zero:
16175+
/// <c>not c = 0</c> with <c>c</c> free of <paramref name="u"/>, or a conjunction of such.
16176+
/// </summary>
16177+
private static bool IsANonzeroCondition(Entity condition, Entity.Variable u) => condition switch
16178+
{
16179+
Andf(var left, var right) => IsANonzeroCondition(left, u) && IsANonzeroCondition(right, u),
16180+
Notf(Equalsf(var left, var right)) => !left.ContainsNode(u) && !right.ContainsNode(u)
16181+
&& (right.Evaled is Number.Complex { IsZero: true } || left.Evaled is Number.Complex { IsZero: true }),
16182+
_ => false,
16183+
};
16184+
1615716185
/// <summary>
1615816186
/// A polynomial times powers of error functions of one shifted linear, written in
1615916187
/// <c>u = a + b x</c>: <c>x erf(a + b x)^2</c> is <c>(u - a) erf(u)^2/b^2</c>, which by parts
@@ -28508,7 +28536,25 @@ private static Entity WithLogarithmArgumentsOverOneBar(Entity expr, Entity.Varia
2850828536
{
2850928537
if (!Integration.AnsweringTheQuestionAskedOrOneBelow)
2851028538
return null;
28511-
expr = WithNegativePowersOfProductsApart(expr);
28539+
// The reciprocal of a product read as the quotient it is, where written apart it hides
28540+
// a linear. The constant-over-a-product branch of SolveAsPolynomialTerm hands
28541+
// `1/((a/b + x)^3 (A + B ln(...)))` on as its power -1, and that power written apart
28542+
// over one bar is `((a + b x)^3)^(-1) b^3`, a power of a power, from which no linear is
28543+
// read: declined, the substitution search spent five to forty seconds on it and
28544+
// declined it too, and the quotient one level up was then answered here at once. Read
28545+
// as that quotient it is answered as that quotient is, in the same words. A reciprocal
28546+
// that written apart is read, `((a + b x)(c + d x) L^2)^(-1)`, is read so, as before.
28547+
var readFromTheReciprocal = false;
28548+
var apart = WithNegativePowersOfProductsApart(expr);
28549+
if (expr is Powf(Mulf reciprocal, Number.Integer { EInteger: var minusOne }) && minusOne.Equals(EInteger.FromInt32(-1))
28550+
&& FactorsOfTheIntegrand(apart).Any(pair => pair.Factor is Powf(Powf(var nested, _), _) && nested.ContainsNode(x)
28551+
&& !nested.Nodes.Any(node => node is Logf)))
28552+
{
28553+
expr = Number.Integer.One / reciprocal;
28554+
readFromTheReciprocal = true;
28555+
apart = WithNegativePowersOfProductsApart(expr);
28556+
}
28557+
expr = apart;
2851228558
// The logarithm's argument: a product of constants and powers of two linears with
2851328559
// opposite exponents.
2851428560
Entity? logarithm = null;
@@ -28622,6 +28668,14 @@ private static Entity WithLogarithmArgumentsOverOneBar(Entity expr, Entity.Varia
2862228668
}
2862328669
if (logarithmsFactor is null || aPolynomialBesideTheLogarithm)
2862428670
return null;
28671+
// Read from the reciprocal, a power of t beside the logarithm's alone is left where it
28672+
// went before: the substitution search after this rule answers it through the
28673+
// logarithm, writing `e^((A + B ln(k t))/B)/t` as the `k e^(A/B)` it is -- Rubi's
28674+
// `1/((a g + b g x)^2 (A + B ln(e (a + b x)/(c + d x))))` in 129 characters, where
28675+
// this writes 231.
28676+
if (readFromTheReciprocal && powerOfDenominator.Evaled is Number.Complex { IsZero: true }
28677+
&& powerOfT.Evaled is not Number.Complex { IsZero: true })
28678+
return null;
2862528679
Entity above = constantInFront * logarithmsFactor, below = Number.Integer.One;
2862628680
foreach (var (@base, power) in new[] { (t, Functions.PartialFractions.Bare(powerOfT.Simplify())), (denominatorInT, Functions.PartialFractions.Bare(powerOfDenominator.Simplify())) })
2862728681
{
@@ -28761,11 +28815,14 @@ static bool AreEqualAsPolynomials(Entity left, Entity right)
2876128815
Entity? differentiated = null;
2876228816
Entity writtenPower = Number.Integer.One;
2876328817
Entity rational = Number.Integer.One;
28818+
var aPolynomialBeside = true;
2876428819
foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
2876528820
{
2876628821
if (!factor.ContainsNode(x) || IsARationalFunction(factor, x))
2876728822
{
2876828823
rational = underneath ? rational / factor : rational * factor;
28824+
if (factor.ContainsNode(x) && (underneath || factor is Powf(_, var exponentOfFactor) && exponentOfFactor.Evaled is not Number.Integer { EInteger.Sign: >= 0 }))
28825+
aPolynomialBeside = false;
2876928826
continue;
2877028827
}
2877128828
if (differentiated is not null)
@@ -28793,8 +28850,16 @@ static bool AreEqualAsPolynomials(Entity left, Entity right)
2879328850
// And term by term, each term of the sum differentiated on its own: simplified
2879428851
// whole, `b/(a + b x) - d/(c + d x)` is brought over one bar, and the pieces below
2879528852
// then hold a quadratic with symbols in it.
28853+
// And beside a polynomial, a power of a quotient with x on both sides written apart as
28854+
// well: `ln(e ((a + b x)/(c + d x))^n)` as written differentiates to two terms each
28855+
// over `(c + d x)^2 (a + b x)`, and the long division of every remainder by that
28856+
// cubic, its symbolic coefficients simplified, took Rubi's
28857+
// `(a g + b g x)^4 (A + B ln(e ((a + b x)/(c + d x))^n))` seventeen seconds and an
28858+
// answer of 4,063 characters, where the spelling `e (a + b x)^n/(c + d x)^n` takes a
28859+
// third of a second. Beside a polynomial only: over `(f + g x)^5` the remainders
28860+
// written so took one second to six.
2879628861
var derivativeTerms = new List<Entity>();
28797-
foreach (var term in TermsDistributed(WithLogarithmsOfProductsApart(differentiated, x)))
28862+
foreach (var term in TermsDistributed(WithLogarithmsOfProductsApart(differentiated, x, powersOfQuotientsApart: aPolynomialBeside)))
2879828863
{
2879928864
var termDerivative = Functions.PartialFractions.Bare(term.Differentiate(x).InnerSimplified);
2880028865
if (termDerivative == Number.Integer.Zero || termDerivative.Evaled is Number.Complex { IsZero: true })
@@ -29067,22 +29132,38 @@ private static List<Entity> TermsDistributed(Entity expr)
2906729132
/// as the sum of the logarithms of the factors holding <paramref name="x"/>, each
2906829133
/// times its power, and the constant factors dropped. Equal to the original up to a
2906929134
/// constant on each interval where both are defined, which is all a derivative sees.
29135+
/// With <paramref name="powersOfQuotientsApart"/>, a power of a quotient of two factors
29136+
/// holding <paramref name="x"/> is written apart too, each side's logarithm times the
29137+
/// power: <c>ln(e ((a + b x)/(c + d x))^n)</c> is <c>n ln(a + b x) - n ln(c + d x)</c>,
29138+
/// whose derivative is the same, since <c>(Q^n)'/Q^n = n Q'/Q</c>.
2907029139
/// </summary>
29071-
private static Entity WithLogarithmsOfProductsApart(Entity expr, Entity.Variable x)
29140+
private static Entity WithLogarithmsOfProductsApart(Entity expr, Entity.Variable x, bool powersOfQuotientsApart)
2907229141
=> expr.Replace(node =>
2907329142
{
2907429143
if (node is not Logf(var @base, var argument) || @base != MathS.e || !argument.ContainsNode(x) || argument is not (Mulf or Divf or Powf))
2907529144
return node;
2907629145
Entity sum = Number.Integer.Zero;
2907729146
foreach (var (factor, underneath) in FactorsOfTheIntegrand(argument))
29147+
Add(factor, underneath, Number.Integer.One);
29148+
return sum;
29149+
29150+
void Add(Entity factor, bool underneath, Entity outer)
2907829151
{
2907929152
if (!factor.ContainsNode(x))
29080-
continue;
29153+
return;
2908129154
var (inner, power) = factor is Powf(var b, var p) && !p.ContainsNode(x) ? (b, p) : (factor, (Entity)Number.Integer.One);
29155+
if (outer != Number.Integer.One)
29156+
power = power == Number.Integer.One ? outer : outer * power;
29157+
if (powersOfQuotientsApart && power != Number.Integer.One
29158+
&& inner is Divf(var numerator, var denominator) && numerator.ContainsNode(x) && denominator.ContainsNode(x))
29159+
{
29160+
foreach (var (innerFactor, innerUnderneath) in FactorsOfTheIntegrand(inner))
29161+
Add(innerFactor, underneath != innerUnderneath, power);
29162+
return;
29163+
}
2908229164
var term = power == Number.Integer.One ? MathS.Ln(inner) : power * MathS.Ln(inner);
2908329165
sum = underneath ? sum - term : sum + term;
2908429166
}
29085-
return sum;
2908629167
});
2908729168

2908829169
/// <summary>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
/// Powers of linears beside a power of <c>A + B ln(k ((a + b x)/(c + d x))^n)</c>, Rubi's 3.2.1,
16+
/// on the three readings that sent them past the corpus's budget: the reciprocal of a product
17+
/// handed on as its power -1, which written apart hid its linear under a power of a power; the
18+
/// logarithm of a power of the quotient, whose derivative was taken whole and brought every
19+
/// remainder over a cubic; and a remainder over a power of a third linear, whose zero term put
20+
/// a condition on the constants at the top of the question in that linear. Answered and
21+
/// differentiated back.
22+
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
23+
/// </summary>
24+
[Trait("Area", "Calculus")]
25+
public sealed class PowersOfLinearsBesideALogarithmOfTheirQuotientIntegralTest
26+
{
27+
[Theory]
28+
[InlineData("1/((a*g + b*g*x)^3*(A + B*ln(k*(a + b*x)/(c + d*x))))")]
29+
[InlineData("1/((a*g + b*g*x)^3*(A + B*ln(k*((a + b*x)/(c + d*x))^n))^2)")]
30+
[InlineData("1/((c*g + d*g*x)^2*(A + B*ln(k*((a + b*x)/(c + d*x))^n))^2)")]
31+
[InlineData("1/((a*g + b*g*x)^2*(A + B*ln(k*(c + d*x)/(a + b*x)))^2)")]
32+
[InlineData("1/((a*g + b*g*x)^3*(A + B*ln(k*(c + d*x)^2/(a + b*x)^2))^2)")]
33+
public void TheReciprocalReadAsTheQuotient(string integrand) => AnsweredAndDifferentiatedBack(integrand);
34+
35+
[Theory]
36+
[InlineData("(a*g + b*g*x)^4*(A + B*ln(k*((a + b*x)/(c + d*x))^n))")]
37+
[InlineData("(c*g + d*g*x)^4*(A + B*ln(k*((a + b*x)/(c + d*x))^n))")]
38+
[InlineData("(f + g*x)^3*(A + B*ln(k*((a + b*x)/(c + d*x))^n))")]
39+
public void TheLogarithmOfAPowerOfTheQuotientDifferentiatedApart(string integrand) => AnsweredAndDifferentiatedBack(integrand);
40+
41+
[Theory]
42+
[InlineData("(A + B*ln(k*(a + b*x)/(c + d*x)))/(f + g*x)^5")]
43+
[InlineData("(A + B*ln(k*(a + b*x)^2/(c + d*x)^2))/(f + g*x)^4")]
44+
[InlineData("(a + b*ln(c*x^n))/(d + h*x)^4")]
45+
public void ARemainderOverAPowerOfALinearAskedWithoutTheConditionOfAZeroTerm(string integrand) => AnsweredAndDifferentiatedBack(integrand);
46+
47+
private static void AnsweredAndDifferentiatedBack(string integrand)
48+
{
49+
var integral = integrand.ToEntity().Integrate("x");
50+
var text = integral.Stringize();
51+
Assert.DoesNotContain("integral(", text);
52+
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.3)
53+
.Substitute("f", 0.9).Substitute("g", 1.1).Substitute("h", 0.5).Substitute("A", 0.6).Substitute("B", 0.8).Substitute("k", 1.7).Substitute("n", 1.4);
54+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
55+
var original = Pinned(integrand.ToEntity());
56+
var compared = 0;
57+
foreach (var at in new[] { 0.3, 0.6, 0.8, 1.3, 1.9, 2.9 })
58+
{
59+
var want = original.Substitute("x", at).EvalNumerical();
60+
var got = derivative.Substitute("x", at).EvalNumerical();
61+
if (want.IsNaN)
62+
continue;
63+
compared++;
64+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
65+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
66+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
67+
}
68+
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)