From 3dcceab315a9c027e9ccd1d002b393921e8c6d06 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 23 Oct 2026 09:35:33 +0000 Subject: [PATCH] Integrate a polynomial beside a function of a shifted linear in that linear A polynomial beside a function of one shifted linear a + b x that parts cannot reach -- a power that is not whole of something holding a logarithm of it, or an exponential of a function of it that is not linear -- is written in u = a + b x, as the rule for error functions of a shifted linear writes its integrand. The polynomial is one in u there, and each of its terms beside the function is a gamma function: x (a + b ln(c (d + e x)))^p becomes u (a + b ln(c u))^p and the offset's own term, and x^2 f^(c (a + b x)^3) becomes powers of u beside f^(c u^3). In u the offset is zero, and the rule declines there. The function is found by its shape before any factor is read as a polynomial, which expands it: asked of every product the algebraic files are, the reading the other way round was five per cent of the corpus's time. Rubi's suite on the harness that reads GAMMA, against master 3dbae246: 48,875 -> 48,907 of 49,052, 0 wrong, no solved row lost, no row to a timeout, no answer's allocation grown; time per stream 0.97 to 1.01 of master's. The gains are 18 in 3.4, 9 in 2.3 and 5 in 3.3. Three definite integrals agree with quadrature to 25 digits. Part of #2075. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 4 +- .../Integration/IndefiniteIntegralSolver.cs | 90 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 2 + ...BesideAnExponentialOfAPowerIntegralTest.cs | 13 +++ 4 files changed, 108 insertions(+), 1 deletion(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index a92d42e79..cc161e3d1 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -150,7 +150,9 @@ as `a - b = 0`, as a statement's own solve reads it: `"x + 1 = 3".Solve("x")` wa `x^31 e^(-x)` is, whose sum of 32 terms is `-gamma(32, x)` now. A definite integral of such an answer that is undefined at a bound is left as written. And beside a power of a sine and a cosine, which Euler's formula writes as those exponentials, the gamma functions of an imaginary argument, and beside a hyperbolic function of a power of - `x`, as `x sinh(c + d x^3)`. + `x`, as `x sinh(c + d x^3)`. A polynomial beside a symbolic power of a logarithm of one shifted linear, or an + exponential of a power of one, is written in that linear, where each term is one of these: + `x (a + b ln(c (d + e x)))^p` and `x^2 f^(c (a + b x)^3)`. ([#2075](https://github.com/asc-community/AngouriMath/issues/2075)) | Input | Was (2.6.0) | Now | diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 4b4ca4295..37f4b799c 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -18148,6 +18148,96 @@ bool Read(Entity node, bool insideASum) return back.Nodes.Any(node => node == MathS.NaN) ? null : back; } + /// + /// A polynomial beside a function of one shifted linear a + b x that parts cannot reach, written in + /// u = a + b x: a power that is not whole of something holding a logarithm of it, x (a + b ln(c (d + e x)))^p, + /// or an exponential of a function of it that is not linear, x^2 f^(c (a + b x)^3). In u the polynomial is one + /// in u, and each of its terms beside the function is answered in the gamma function: u (a + b ln(c u))^p and + /// u^2 f^(c u^3). Only where the offset is not zero, since in u it is, and this declines there. Rubi's 3.3, 3.4 + /// and 2.3. https://github.com/asc-community/AngouriMath/issues/2075 + /// + internal static Entity? SolveAPolynomialBesideATranscendentalPowerOfAShiftedLinear(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAskedOrOneBelow || expr is not (Mulf or Divf)) + return null; + // The function first, by its shape, before any factor is read as a polynomial: that expands it, and asked of + // every product an algebraic integrand is, it was five per cent of the corpus's time. + Entity? transcendental = null; + foreach (var factor in Mulf.LinearChildren(expr)) + if (factor.ContainsNode(x) && IsATranscendentalPowerOfSomethingInX(factor, x)) + { + if (transcendental is not null) + return null; + transcendental = factor; + } + if (transcendental is null) + return null; + Entity? polynomial = null; + foreach (var factor in Mulf.LinearChildren(expr)) + { + if (!factor.ContainsNode(x) || ReferenceEquals(factor, transcendental)) + continue; + if (ThePolynomialIn(factor, x) is not null) + polynomial = polynomial is null ? factor : polynomial * factor; + else + return null; + } + if (transcendental is null || polynomial is null + || TheOneLinearIn(transcendental, x) is not { } linear + || !TreeAnalyzer.TryGetPolyLinear(linear, x, out var slope, out var offset) + || slope.ContainsNode(x) || offset.ContainsNode(x) || TreeAnalyzer.IsZero(slope) || VanishesIdentically(offset)) + return null; + // x = (u - a)/b, so that the function is of u, and dx = du/b: the whole integrand, its constants with it. + var u = Variable.CreateUnique(expr, "u_linear"); + if (transcendental.Replace(node => node == linear ? u : node).ContainsNode(x)) + return null; + var inU = expr.Replace(node => node == linear ? u : node).Substitute(x, (u - offset) / slope); + if (inU.ContainsNode(x) + || Integration.ComputeAsAQuestionOfItsOwn((inU / slope).InnerSimplified, u, integrateByParts) is not { } answer) + return null; + var back = answer.Substitute(u, linear); + return back.Nodes.Any(node => node == MathS.NaN) ? null : back; + } + + /// + /// Whether is a power that is not whole of something holding a logarithm of something in + /// , or an exponential of something in that is not linear in it. + /// + private static bool IsATranscendentalPowerOfSomethingInX(Entity factor, Entity.Variable x) => factor switch + { + // An exponential whose exponent holds a sum in x, c (a + b x)^3 or c/(a + b x), read as not linear only then. + Powf(var @base, var exponent) when !@base.ContainsNode(x) && exponent.ContainsNode(x) + => exponent.Nodes.Any(node => node is Sumf or Minusf && node.ContainsNode(x)) + && !TreeAnalyzer.TryGetPolyLinear(exponent, x, out _, out _), + Powf(var @base, var exponent) when !exponent.ContainsNode(x) && exponent is not Number.Integer + => @base.Nodes.Any(node => node is Logf(_, var antilogarithm) && antilogarithm.ContainsNode(x)), + _ => false + }; + + /// + /// The one sum linear in that every in stands in, + /// outermost, or . + /// + private static Entity? TheOneLinearIn(Entity expr, Entity.Variable x) + { + Entity? found = null; + bool Read(Entity node) + { + if (!node.ContainsNode(x)) + return true; + if (node is Sumf or Minusf && TreeAnalyzer.TryGetPolyLinear(node, x, out var slope, out _) && !slope.ContainsNode(x)) + { + if (found is null) + found = node; + return node == found; + } + if (node == x) + return false; + return node.DirectChildren.All(Read); + } + return Read(expr) ? found : null; + } + /// /// A sine or a cosecant of twice an argument beside functions of the argument, written as the /// product it is: sin(2A) = 2 sin(A) cos(A), so that every trigonometric function is of diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 5330f7a75..7a3f52a12 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -935,6 +935,8 @@ internal static bool ASumOverRootsWouldAnswer // A polynomial beside error functions of one shifted linear, written in it: by parts in // x leaves a Gaussian of the shifted linear beside a polynomial, which nothing reads. if ((answer = IndefiniteIntegralSolver.SolveAPolynomialBesideErrorFunctionsOfAShiftedLinear(expr, x, integrateByParts)) is { }) return answer; + // And beside a symbolic power of a logarithm of one, or an exponential of a power of one, the same way. + if ((answer = IndefiniteIntegralSolver.SolveAPolynomialBesideATranscendentalPowerOfAShiftedLinear(expr, x, integrateByParts)) is { }) return answer; // A half-odd power of a ± a sin(y) beside powers of other linears in the sine, by the half // angle, before the polynomial term: beneath it the substitution search ran out of time. if ((answer = IndefiniteIntegralSolver.SolveAHalfOddPowerOfOnePlusMinusASineBesidePowersOfLinearsInIt(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/APowerBesideAnExponentialOfAPowerIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerBesideAnExponentialOfAPowerIntegralTest.cs index 34b64892e..ef6a87725 100644 --- a/Sources/Tests/UnitTests/Calculus/APowerBesideAnExponentialOfAPowerIntegralTest.cs +++ b/Sources/Tests/UnitTests/Calculus/APowerBesideAnExponentialOfAPowerIntegralTest.cs @@ -86,6 +86,7 @@ public void AHighWholePowerIsOneGammaFunction() [InlineData("integral(e^(-x^3), x, 0, 1)", 0.807511182139671)] // 3^(-2/3) on both half-lines, which is not a number but one constant. [InlineData("integral(x^(1/3)*e^(-(1 + 3*x^2)), x, 0, 1)", 0.1169389395696306439)] + [InlineData("integral(x*(1/3 + 2*ln(3/2*(1 + 2*x)))^(5/2), x, 1, 2)", 47.01873105138465896167)] public void ADefiniteIntegralFromZero(string integral, double value) { var result = integral.ToEntity().Simplify().EvalNumerical(); @@ -123,6 +124,18 @@ public void APositiveRateIsTakenOutOfThePower() [InlineData("x^m*e^(-(a + b*x^2))")] public void AHyperbolicFunctionOfAPowerOfX(string integrand) => DifferentiatesBack(integrand); + /// + /// A polynomial beside a symbolic power of a logarithm of one shifted linear, or an exponential of a power of one, + /// written in u = a + b x, where each term is a gamma function. The constant in front comes along. Rubi's 3.3, + /// 3.4 and 2.3. + /// + [Theory] + [InlineData("2*x*(a + b*ln(c*(d + f*x)))^n")] + [InlineData("x*(a + b*ln(c*(d + f*sqrt(x))))^n")] + [InlineData("x^2*f^(c*(a + b*x)^3)")] + [InlineData("(a + b*x)^2*e^(-c*(d + f*x)^3)")] + public void APolynomialBesideAFunctionOfAShiftedLinear(string integrand) => DifferentiatesBack(integrand); + /// /// Not where the integral is elementary, an exponential integral or an error function: those keep the form /// they had.