diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index e4bc750a0..90e0f8f79 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -2178,6 +2178,20 @@ multiple of it, `(d - c^2 d x^2)^(3/2)`, is written over it the same way, with
Rubi's 5.1.4, 5.1.5, 5.2.4 and 5.2.5, all 504 problems that count: 405 to 463, no row lost, 77
timeouts to 19.
+### The special functions are integrated by parts, and so is what leads to one
+
+Integration by parts differentiates each of the nine special functions now, where it meets one
+beside a power of `x`, as it does a logarithm or an inverse trigonometric function: each has an
+elementary derivative. `Ei`, `li`, `Si`, `Ci`, `Shi` and `Chi` of a linear argument are integrated
+alone, against 1, as `erf` already was
+([#1501](https://github.com/asc-community/AngouriMath/issues/1501)). An integrand holding one of
+these functions had no reading in 2.5.0, which the entries for the functions themselves record. An
+elementary integrand whose antiderivative is reached through one is answered where it was not:
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"(a+b*ln(c*x^n))/(x^2*(d+e*ln(f*x^m)))".Integrate("x")`, Rubi's 3.1.5 row 213 | `integral(...)` | an antiderivative in `Ei`, provided `f > 0` and `e^d f^e > 0` |
+
### An inverse trigonometric function below the bar is integrated to the sine and cosine integrals
`1/arcsin(x)` was left unintegrated. Under the substitution that undoes the inverse function,
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index dfab1edd8..4b57434f7 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -1595,6 +1595,13 @@ private static Entity IntegrateAPowerOfTheVariable(Entity @base, Entity power, E
/// gives an antiderivative holding x*atan(x) again, which is where the search went
/// instead and why it was left unevaluated.
///
+ ///
+ /// The special functions belong here for the same reason, since each has an elementary
+ /// derivative: x Ei(b x) leaves x e^(b x)/2 after one step, and
+ /// x^2 Si(b x) leaves x^2 sin(b x)/3, where integrating the special function
+ /// first gives an antiderivative that holds it again.
+ /// https://github.com/asc-community/AngouriMath/issues/1501
+ ///
/// https://github.com/asc-community/AngouriMath/issues/718
///
private static bool IsDifferentiatedBeforeAPolynomial(Entity factor)
@@ -1603,6 +1610,8 @@ private static bool IsDifferentiatedBeforeAPolynomial(Entity factor)
Logf or Entity.Arcsinf or Entity.Arccosf
or Entity.Arctanf or Entity.Arccotanf
or Entity.Arcsecantf or Entity.Arccosecantf => true,
+ Entity.Erff or Entity.Erfcf or Entity.Erfif or Entity.Eif or Entity.Lif
+ or Entity.Sif or Entity.Cif or Entity.Shif or Entity.Chif => true,
// And a whole power of one, which is the same function for this purpose:
// differentiating ln(x)^2 gives 2ln(x)/x, whose x cancels against the integrated
// polynomial exactly as ln(x)'s does, leaving x*ln(x) -- one step simpler, and
@@ -1616,6 +1625,31 @@ or Entity.Arctanf or Entity.Arccotanf
_ => false
};
+ ///
+ /// Whether is a special function of an argument linear in
+ /// , other than the logarithmic integral: differentiated beside a
+ /// power of x, it leaves that power times sin(u), e^u or the like,
+ /// which parts against the power answer in as many steps as its degree.
+ ///
+ ///
+ /// Not li: its derivative 1/ln(u) leaves x^m/ln(a + b x), which the
+ /// exponential integral's rule answers, and parts there would integrate the reciprocal of
+ /// the logarithm back into li.
+ ///
+ private static bool IsASpecialFunctionOfALinear(Entity factor, Variable x)
+ => (factor switch
+ {
+ Entity.Erff(var u) => u,
+ Entity.Erfcf(var u) => u,
+ Entity.Erfif(var u) => u,
+ Entity.Eif(var u) => u,
+ Entity.Sif(var u) => u,
+ Entity.Cif(var u) => u,
+ Entity.Shif(var u) => u,
+ Entity.Chif(var u) => u,
+ _ => null
+ }) is { } argument && TreeAnalyzer.TryGetPolyLinear(argument, x, out _, out _);
+
internal static Entity? SolveIntegratingByParts(Entity expr, Entity.Variable x)
{
// The measure the nested call below decreases on. Read once, since every step
@@ -1735,8 +1769,19 @@ static Entity WithTheConstantMatchedTo(Entity antiderivative, Entity derivativeO
// is the runaway the node bound exists for: measured at thirty seconds for a
// decline, where the node bound alone declined it in one.
var remainingPower = HighestDifferentiatedPower(remaining);
+ // And after a special function of a linear argument, whose derivative leaves the
+ // power of x beside sin(u), e^u and the like: `x Si(b x)` leaves `x sin(b x)/2`,
+ // which parts against the power answer in as many steps as its degree. It has
+ // more nodes than `x Si(b x)` and no factor left to differentiate, so the measure
+ // alone declined it, and with it every power of x beside Si or Ci.
+ //
+ // Only against a polynomial, which is what makes the next steps a descent on its
+ // degree. Allowed against anything, it took erf(b x)^2, whose second step is
+ // against x e^(-b^2 x^2), from a decline in a third of a second to a timeout.
+ // https://github.com/asc-community/AngouriMath/issues/1501
var partsOnTheRemainder = remaining.Nodes.Count() < wholeSize
- || (remainingPower >= 1 && remainingPower < wholePower);
+ || (remainingPower >= 1 && remainingPower < wholePower)
+ || IsASpecialFunctionOfALinear(v, x) && MathS.TryPolynomial(u, x, out _);
// Spelled as the product its own next step reads, where there is one. `Simplify`
// writes `2 arctan(x)/(1 + x^2) * x^2/2` as `arctan(x) x^2/(x^2 + 1)`, a
// quotient, and this rule runs on a product. `x*arctan(x)^2` is answered by
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs
index 08dbf25d6..cf2e5a968 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IntegralPatterns.cs
@@ -61,6 +61,12 @@ internal static Entity AntiderivativeLog(Entity arg)
Entity.Erff(var arg) => arg,
Entity.Erfcf(var arg) => arg,
Entity.Erfif(var arg) => arg,
+ Entity.Eif(var arg) => arg,
+ Entity.Lif(var arg) => arg,
+ Entity.Sif(var arg) => arg,
+ Entity.Cif(var arg) => arg,
+ Entity.Shif(var arg) => arg,
+ Entity.Chif(var arg) => arg,
Entity.Arcsinf(var arg) => arg,
Entity.Arccosf(var arg) => arg,
Entity.Arctanf(var arg) => arg,
@@ -344,6 +350,36 @@ _ when GaussianMoment(expr, x) is { } moment => moment,
TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) =>
(arg * MathS.Erfi(arg) - MathS.Pow(MathS.e, MathS.Sqr(arg)) / MathS.Sqrt(MathS.pi)) / a,
+ // And the exponential, logarithmic, trigonometric and hyperbolic integrals, by parts
+ // against 1 the same way: each derivative is elementary and cancels the u the
+ // integrated 1 leaves beside it. int Ei(u) = u Ei(u) - e^u, int li(u) = u li(u) -
+ // Ei(2 ln u), int Si(u) = u Si(u) + cos(u), int Ci(u) = u Ci(u) - sin(u),
+ // int Shi(u) = u Shi(u) - cosh(u) and int Chi(u) = u Chi(u) - sinh(u).
+ // https://github.com/asc-community/AngouriMath/issues/1501
+ Entity.Eif(var arg) when
+ TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) =>
+ (arg * MathS.Ei(arg) - MathS.Pow(MathS.e, arg)) / a,
+
+ Entity.Lif(var arg) when
+ TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) =>
+ (arg * MathS.Li(arg) - MathS.Ei(2 * MathS.Ln(arg))) / a,
+
+ Entity.Sif(var arg) when
+ TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) =>
+ (arg * MathS.Si(arg) + MathS.Cos(arg)) / a,
+
+ Entity.Cif(var arg) when
+ TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) =>
+ (arg * MathS.Ci(arg) - MathS.Sin(arg)) / a,
+
+ Entity.Shif(var arg) when
+ TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) =>
+ (arg * MathS.Shi(arg) - MathS.Hyperbolic.Cosh(arg)) / a,
+
+ Entity.Chif(var arg) when
+ TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) =>
+ (arg * MathS.Chi(arg) - MathS.Hyperbolic.Sinh(arg)) / a,
+
Entity.Absf(var arg) when
TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) => // ∫ |ax + b| dx = sgn(ax + b) * (ax + b)^2 / (2a)
MathS.Signum(arg) * MathS.Pow(arg, 2) / (2 * a),
diff --git a/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs b/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs
new file mode 100644
index 000000000..b9ead215c
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs
@@ -0,0 +1,99 @@
+//
+// 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 System.Linq;
+using AngouriMath.Extensions;
+using Xunit;
+
+namespace AngouriMath.Tests.Calculus
+{
+ ///
+ /// The special functions integrated by parts: each has an elementary derivative, so against a
+ /// power of x it is the factor differentiated, and alone it is integrated against 1.
+ /// The rows are Rubi's, from its files 8.1, 8.3, 8.4 and 8.5.
+ /// https://github.com/asc-community/AngouriMath/issues/1501
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class SpecialFunctionsByPartsTest
+ {
+ /// Off 0, where the negative powers are undefined.
+ private static readonly double[] Points = { -1.7, -0.6, 0.35, 0.9, 1.45 };
+
+ ///
+ /// Integrates, pins the parameters, and compares the derivative of the answer with the
+ /// integrand at . The parameters are pinned after integrating, so the
+ /// rule is asked the symbolic question.
+ ///
+ private static void DifferentiatesBack(string integrand, params (string Name, string Value)[] pins)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ Assert.DoesNotContain("integral(", integral.Stringize());
+ Entity Pinned(Entity e) => pins.Aggregate(e, (current, pin) => current.Substitute(pin.Name, pin.Value.ToEntity()));
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ foreach (var at in Points)
+ {
+ 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-9,
+ $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ }
+ }
+
+ private static readonly (string, string)[] Parameters = { ("a", "2/5"), ("b", "13/10"), ("c", "7/10"), ("d", "19/10") };
+
+ ///
+ /// Alone, against 1: int Ei(u) = u Ei(u) - e^u and its kin, each over the rate.
+ ///
+ [Theory]
+ [InlineData("Ei(b*x)")]
+ [InlineData("Ei(a + b*x)")]
+ [InlineData("Si(b*x)")]
+ [InlineData("Si(a + b*x)")]
+ [InlineData("Ci(b*x)")]
+ [InlineData("Shi(a + b*x)")]
+ [InlineData("Chi(b*x)")]
+ public void ASpecialFunctionAloneIsByPartsAgainstOne(string integrand)
+ => DifferentiatesBack(integrand, Parameters);
+
+ ///
+ /// The logarithmic integral, where its argument stays above 0 at every point: a = 3
+ /// keeps a + b x between 0.79 and 4.9.
+ ///
+ [Theory]
+ [InlineData("li(a + b*x)")]
+ [InlineData("x*li(a + b*x)")]
+ public void TheLogarithmicIntegralIsByParts(string integrand)
+ => DifferentiatesBack(integrand, ("a", "3"), ("b", "13/10"));
+
+ ///
+ /// Against a power of x, the special function is the factor differentiated: what is
+ /// left is the power times e^(-u^2), e^u/u, sin(u)/u and the like.
+ ///
+ [Theory]
+ [InlineData("x^3*erf(b*x)")]
+ [InlineData("x^2*erf(b*x)")]
+ [InlineData("x*erf(b*x)")]
+ [InlineData("erf(b*x)/x^2")]
+ [InlineData("erf(b*x)/x^3")]
+ [InlineData("(c + d*x)^2*erfc(a + b*x)")]
+ [InlineData("x*erfi(b*x)")]
+ [InlineData("x^2*Ei(b*x)")]
+ [InlineData("x*Ei(a + b*x)")]
+ [InlineData("Ei(b*x)/x^2")]
+ [InlineData("x*Si(b*x)")]
+ [InlineData("x^2*Ci(b*x)")]
+ [InlineData("Si(b*x)/x^2")]
+ [InlineData("x*Shi(b*x)")]
+ [InlineData("x^3*Chi(b*x)")]
+ public void APowerTimesASpecialFunctionIsByParts(string integrand)
+ => DifferentiatesBack(integrand, Parameters);
+ }
+}