diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 5772f5ada..0ab69148c 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -2315,6 +2315,26 @@ no integrand without one changes: Rubi's independent suites and a sample of its
| `"Ei(b*x)^2".Integrate("x")` | `Ei * (b * x) ^ 3 / 3 / b + C`, with `Ei` a variable | an antiderivative in `Ei(b x)` and `Ei(2 b x)` |
| `"x*erf(b*x)^2".Integrate("x")` | `UnrecognizedFunctionParseException`: there is no function `erf` | an antiderivative in `erf(b x)` |
+### An exponential or a hyperbolic function over several linears is split into partial fractions over them
+
+`e^x/(x (x + 1))` was left unevaluated, where `e^x/x` and `e^x/(x + 1)` were each answered with
+the exponential integral: the rule for an exponential of a linear over a power of a linear read
+one linear below the bar, and the rule for `sinh` and `cosh` did the same. The quotient is split
+into partial fractions over its linears now, as the rule for a sine or a cosine already split it,
+and each term is the one-linear question: `e^x/(x (x + 1))` is `Ei(x) - Ei(x + 1)/e`. It is also
+what by parts leaves of `Ei(a + b x)/x^2`, `e^(a + b x)/((a + b x) x)`
+([#1501](https://github.com/asc-community/AngouriMath/issues/1501)). The rule for a sine or a cosine
+declined a polynomial over linears with a symbol among their coefficients, `x sin(x)/((c + d x)(x - 2))`,
+for dividing a fraction that was proper already; neither rule divides one now. Both columns
+measured on a build, `v2.5.0` against this change.
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"e^x/(x*(x+1))".Integrate("x")` | `integral(e ^ x / (x * (x + 1)), x)` | `Ei(x) + -1 / e * Ei(x + 1) + C` |
+| `"sinh(x)/(x*(x+1))".Integrate("x")` | `integral((e ^ x - e ^ (-x)) / 2 / (x * (x + 1)), x)` | `1/2 * (2 * Shi(x) + ((-1) / e + e) * Chi(x + 1) + ((-1) / e - e) * Shi(x + 1)) + C` |
+| `"x^3*e^(2*x)/((x+1)*(x-2))".Integrate("x")` | `integral(x ^ 3 * e ^ (2 * x) / ((x + 1) * (x - 2)), x)` | `e ^ (2 * x) * (-1/4 + 1/2 * x) + e ^ (2 * x) / 2 + 1/3 * e ^ (-2) * Ei(2 * (x + 1)) + 8/3 * e ^ 4 * Ei(2 * (x - 2)) + C` |
+| `"x*sin(x)/((c+d*x)*(x-2))".Integrate("x")` | `integral(x * sin(x) / ((c + d * x) * (x - 2)), x)` | in `Si` and `Ci` of `(c + d x)/d` and `x - 2` |
+
### 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 ff88f4070..90ed4521c 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -3859,6 +3859,102 @@ bool Read(Entity factor, int times)
}
}
+ ///
+ /// An exponential of a linear, or a sum of them -- which is how sinh and cosh
+ /// arrive -- times a polynomial, over two or more linears each to a whole power: split into
+ /// partial fractions over the linears, and each term the one-linear question of
+ /// and
+ /// . e^x/(x (x + 1)) is
+ /// e^x/x - e^x/(x + 1), which is Ei(x) - Ei(x + 1)/e; the trigonometric rule
+ /// splits the same way (). It is also what by parts leaves
+ /// of Ei(a + b x)/x^2, e^(a + b x)/((a + b x) x).
+ /// https://github.com/asc-community/AngouriMath/issues/1501
+ ///
+ internal static Entity? SolveAnExponentialOverSeveralLinears(Entity expr, Entity.Variable x)
+ {
+ if (expr is not (Divf or Mulf) || AnExponentialOfALinear(expr, x) is null && !HasASumOfExponentials(expr, x))
+ return null;
+ Entity constant = Number.Integer.One;
+ var linears = new List<(Entity Linear, int Power)>();
+ Entity? polynomial = null;
+ Entity? exponentials = null;
+ foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
+ {
+ if (!factor.ContainsNode(x))
+ {
+ constant = underneath ? constant / factor : constant * factor;
+ continue;
+ }
+ var (@base, exponent) = factor is Powf(var raised, Number.Integer whole) && whole.EInteger.CanFitInInt32() && !whole.EInteger.IsZero
+ ? (raised, whole.EInteger.ToInt32Checked()) : (factor, 1);
+ if (underneath)
+ exponent = -exponent;
+ if (exponent < 0 && TreeAnalyzer.TryGetPolyLinear(@base, x, out var slopeOfIt, out _) && !TreeAnalyzer.IsZero(slopeOfIt))
+ {
+ var at = linears.FindIndex(pair => pair.Linear == @base);
+ if (at < 0)
+ linears.Add((@base, -exponent));
+ else
+ linears[at] = (@base, linears[at].Power - exponent);
+ continue;
+ }
+ if (underneath)
+ return null;
+ if (factor.Nodes.Any(node => node is Powf(var b, var e) && !b.ContainsNode(x) && e.ContainsNode(x)))
+ {
+ exponentials = exponentials is null ? factor : exponentials * factor;
+ continue;
+ }
+ if (TreeAnalyzer.TryGetPolynomial(factor, x, out var monomials) && monomials.Keys.All(degree => degree.Sign >= 0 && degree.CompareTo(EInteger.FromInt32(12)) <= 0))
+ {
+ polynomial = polynomial is null ? factor : polynomial * factor;
+ continue;
+ }
+ return null;
+ }
+ if (exponentials is null || linears.Count < 2 || linears.Count > 4 || linears.Sum(pair => pair.Power) > 12)
+ return null;
+ // Over each linear by partial fractions, the polynomial part first, since the split is
+ // of a proper fraction -- as the trigonometric rule does it. Only where the fraction is
+ // not proper already: divided, `x` over `(c + d x)(x - 2)` came back as a quotient and a
+ // remainder `2 provided not c + d x = 0`, which nothing splits.
+ var denominator = linears.Aggregate((Entity)Number.Integer.One, (product, pair) => product * MathS.Pow(pair.Linear, pair.Power));
+ Entity numerator = polynomial ?? Number.Integer.One;
+ var terms = new List();
+ if (numerator.ContainsNode(x)
+ && TreeAnalyzer.TryGetPolynomial(numerator, x, out var monomialsAbove)
+ && monomialsAbove.Keys.Any(degree => degree.CompareTo(EInteger.FromInt32(linears.Sum(pair => pair.Power))) >= 0)
+ && TreeAnalyzer.PolynomialLongDivision(numerator, denominator, genericCase: true, inTermsOf: x) is var (quotient, remainder))
+ {
+ if (!TreeAnalyzer.IsZero(quotient))
+ terms.Add(quotient);
+ numerator = remainder is Divf(var left, _) ? left : (numerator - quotient * denominator).Expand().InnerSimplified;
+ }
+ if (!TreeAnalyzer.IsZero(numerator))
+ {
+ if (!Functions.PartialFractions.TrySplitOverWrittenFactors(numerator, denominator, x, out var decomposition))
+ return null;
+ var fractions = decomposition.InnerSimplified;
+ if (fractions is Divf(var over, var by) && !by.ContainsNode(x))
+ fractions = Sumf.LinearChildren(over).Aggregate((Entity)Number.Integer.Zero, (all, one) => all + one / by);
+ terms.AddRange(Sumf.LinearChildren(fractions));
+ }
+ Entity sum = Number.Integer.Zero;
+ foreach (var term in terms)
+ {
+ // Each term as the quotient the one-linear rules read, or, with no linear left, the
+ // elementary product of a polynomial and the exponentials.
+ var (above, linear, power) = OverOneLinear(term, x);
+ var question = linear is null ? above * exponentials : above * exponentials / MathS.Pow(linear, power);
+ if ((SolveAnExponentialOfALinearOverAPowerOfALinear(question, x)
+ ?? SolveAHyperbolicOfALinearOverAPowerOfALinear(question, x)
+ ?? Integration.ComputeIndefiniteIntegral(question, x, false)) is not { } integral)
+ return null;
+ sum += integral;
+ }
+ return Functions.PartialFractions.Bare((constant * sum).InnerSimplified);
+ }
+
/// Whether a factor of is a sum with an exponential of the variable in it.
private static bool HasASumOfExponentials(Entity expr, Entity.Variable x) => expr switch
{
@@ -4542,11 +4638,15 @@ Entity LessTheRoot(Entity root) =>
? Functions.PartialFractions.Bare((constant * one).InnerSimplified)
: null;
// Over each linear by partial fractions, and each term the one-linear question; the
- // polynomial part first, since the split is of a proper fraction.
+ // polynomial part first, since the split is of a proper fraction -- and only where it is
+ // not proper already, as in SolveAnExponentialOverSeveralLinears.
var denominator = linears.Aggregate((Entity)Number.Integer.One, (product, pair) => product * MathS.Pow(pair.Linear, pair.Power));
Entity numerator = polynomial ?? Number.Integer.One;
var terms = new List();
- if (numerator.ContainsNode(x) && TreeAnalyzer.PolynomialLongDivision(numerator, denominator, genericCase: true, inTermsOf: x) is var (quotient, remainder))
+ if (numerator.ContainsNode(x)
+ && TreeAnalyzer.TryGetPolynomial(numerator, x, out var monomialsAbove)
+ && monomialsAbove.Keys.Any(degree => degree.CompareTo(EInteger.FromInt32(linears.Sum(pair => pair.Power))) >= 0)
+ && TreeAnalyzer.PolynomialLongDivision(numerator, denominator, genericCase: true, inTermsOf: x) is var (quotient, remainder))
{
if (!TreeAnalyzer.IsZero(quotient))
terms.Add(quotient);
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index 885414428..5be10b342 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -643,6 +643,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfALinearOverAPowerOfALinear(expr, x)) is { }) return answer;
// And a sum of exponentials, which is how sinh and cosh arrive, onto Shi and Chi.
if ((answer = IndefiniteIntegralSolver.SolveAHyperbolicOfALinearOverAPowerOfALinear(expr, x)) is { }) return answer;
+ // And either over several linears, split into partial fractions over them first, as the
+ // trigonometric rule below splits.
+ if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOverSeveralLinears(expr, x)) is { }) return answer;
// Sines and cosines of a linear over a power of a linear, onto Si and Ci under u = the
// linear, where the search would take the quotient by parts without end.
if ((answer = IndefiniteIntegralSolver.SolveATrigonometricOfALinearOverAPowerOfALinear(expr, x)) is { }) return answer;
diff --git a/Sources/Tests/UnitTests/Calculus/ExponentialIntegralIntegrationTest.cs b/Sources/Tests/UnitTests/Calculus/ExponentialIntegralIntegrationTest.cs
index 8a13e3cbb..18fff93ee 100644
--- a/Sources/Tests/UnitTests/Calculus/ExponentialIntegralIntegrationTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/ExponentialIntegralIntegrationTest.cs
@@ -62,6 +62,20 @@ private static readonly (string, string)[] Pins =
public void AnExponentialOfALinearOverAPowerOfALinear(string integrand)
=> DifferentiatesBack(integrand, AroundZero, Pins);
+ ///
+ /// Over several linears, the quotient is split into partial fractions over them first, and
+ /// each term is the question above: e^x/(x (x + 1)) is Ei(x) - Ei(x + 1)/e. The
+ /// last row is what by parts leaves of Ei(a + b x)/x^2.
+ ///
+ [Theory]
+ [InlineData("e^x/(x*(x + 1))")]
+ [InlineData("e^x/(x*(x - a))")]
+ [InlineData("e^x/(x^2*(x + 1))")]
+ [InlineData("x^3*e^(2*x)/((x + 1)*(x - 2))")]
+ [InlineData("e^(a + b*x)/((a + b*x)*x)")]
+ public void AnExponentialOfALinearOverSeveralLinears(string integrand)
+ => DifferentiatesBack(integrand, AroundZero, Pins);
+
///
/// The Gaussian's odd negative moments end at int e^(A x^2)/x = Ei(A x^2)/2, and
/// x e^(-1/x^2) is one of them under u = 1/x.
diff --git a/Sources/Tests/UnitTests/Calculus/HyperbolicIntegralIntegrationTest.cs b/Sources/Tests/UnitTests/Calculus/HyperbolicIntegralIntegrationTest.cs
index 984e30132..7b7282918 100644
--- a/Sources/Tests/UnitTests/Calculus/HyperbolicIntegralIntegrationTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/HyperbolicIntegralIntegrationTest.cs
@@ -67,6 +67,17 @@ private static readonly (string, string)[] Pins =
public void ASumOfExponentialsOverAPowerOfALinear(string integrand)
=> DifferentiatesBack(integrand, AroundZero, Pins);
+ ///
+ /// And over several linears, split into partial fractions over them first, each term the
+ /// question above.
+ ///
+ [Theory]
+ [InlineData("sinh(x)/(x*(x + 1))")]
+ [InlineData("cosh(2*x)/(x*(x + 3))")]
+ [InlineData("x*sinh(a + b*x)/((c + d*x)*(x - 2))")]
+ public void ASumOfExponentialsOverSeveralLinears(string integrand)
+ => DifferentiatesBack(integrand, AroundZero, Pins);
+
///
/// An inverse hyperbolic tangent below the bar, beside a whole power of 1 - a^2 x^2:
/// under x = tanh(u)/a it is a polynomial in sinh(u) and cosh(u) over
diff --git a/Sources/Tests/UnitTests/Calculus/TrigonometricIntegralIntegrationTest.cs b/Sources/Tests/UnitTests/Calculus/TrigonometricIntegralIntegrationTest.cs
index ef129c110..478d248c0 100644
--- a/Sources/Tests/UnitTests/Calculus/TrigonometricIntegralIntegrationTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/TrigonometricIntegralIntegrationTest.cs
@@ -88,6 +88,8 @@ public void APowerOfASineOrACosineOverAPowerOfALinear(string integrand)
[InlineData("cos(2*x + 1)/((x - 1)*(x + 2)^2)")]
[InlineData("sin(c + d*x)/(x^2*(a + b*x))")]
[InlineData("x^3*sin(x)^2/((x + 1)*(x + 3))")]
+ [InlineData("x*sin(x)/((c + d*x)*(x - 2))")]
+ [InlineData("x*cos(a + b*x)/((c + d*x)*(x + 1))")]
public void OverSeveralLinears(string integrand)
=> DifferentiatesBack(integrand, AroundZero, Pins);