diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 08fb6e2bb..5772f5ada 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -2295,6 +2295,26 @@ no integrand without one changes: Rubi's independent suites and a sample of its
| `"e^(b*x)*Ei(b*x)/x".Integrate("x")` | `Ei * e ^ (b * x) + C`, with `Ei` a variable | `Ei(b * x) ^ 2 / 2 + C` |
| `"Si(b*x)*sin(b*x)/x".Integrate("x")` | `Si * -cos(b * x) + C`, with `Si` a variable | `Si(b * x) ^ 2 / 2 + C` |
+### A square of a special function, and one beside a power of `x` and its elementary derivative, are integrated by parts
+
+`x Si(b x) sin(b x)` was left unevaluated where `Si(b x) sin(b x)` was not: by parts it is
+`Si(b x)` against `x sin(b x)`, whose integral needs parts of its own, and the integral of that
+factor was taken with parts off. It is integrated by the polynomial's parts now, beside a special
+function of a linear argument, and what is left, `sin(b x)/x` times sines and cosines, by parts in
+turn. A square of one of `b x` is two rounds of parts, and what the first round leaves comes back
+as one product over a sum -- `(b x Ei(b x) - e^(b x)) e^(b x)/(b x)` for `Ei(b x)^2` -- that no rule
+reads whole: its terms are asked one at a time now
+([#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, and
+no integrand without one changes: Rubi's independent suites and a sample of its families 1 to 7,
+2726 problems, are answered alone as they were.
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"x*Si(b*x)*sin(b*x)".Integrate("x")` | a polynomial in a variable `Si` | an antiderivative in `Si(b x)`, `Ci(2 b x)` and `ln(x)` |
+| `"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 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 b230c5e49..ff88f4070 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -1722,6 +1722,19 @@ static Entity WithTheConstantMatchedTo(Entity antiderivative, Entity derivativeO
// a coefficient's sign whose conditions are decided, `0 = 0`, and a remainder
// holding that piecewise is read by nothing.
var integralOfU = Integration.ComputeIndefiniteIntegral(u, x, false)?.InnerSimplified;
+ // Beside a special function of a linear argument, a polynomial times something
+ // else is integrated by the polynomial's parts, which end in as many steps as its
+ // degree: `x sin(b x)` beside `Si(b x)`, which nothing answers with parts off.
+ // https://github.com/asc-community/AngouriMath/issues/1501
+ var besideASpecialFunction = IsASpecialFunctionOfALinearOrAPowerOfOne(v, x);
+ var byThePolynomialsParts = false;
+ if (integralOfU is null && besideASpecialFunction
+ && ThePolynomialFactor(u, x) is var (polynomialBeside, restBeside)
+ && polynomialBeside is not null && restBeside is not null)
+ {
+ integralOfU = IntegrateByPartsPolynomial(polynomialBeside, restBeside, x)?.InnerSimplified;
+ byThePolynomialsParts = integralOfU is not null;
+ }
if (integralOfU is null) return null;
// Differentiate v
@@ -1780,7 +1793,12 @@ static Entity WithTheConstantMatchedTo(Entity antiderivative, Entity derivativeO
// https://github.com/asc-community/AngouriMath/issues/1501
var partsOnTheRemainder = remaining.Nodes.Count() < wholeSize
|| (remainingPower >= 1 && remainingPower < wholePower)
- || IsASpecialFunctionOfALinear(v, x) && MathS.TryPolynomial(u, x, out _);
+ || IsASpecialFunctionOfALinear(v, x) && MathS.TryPolynomial(u, x, out _)
+ // And where what was integrated beside it was a polynomial times an
+ // elementary function, whose integral the polynomial's parts wrote: the
+ // remainder is then the special function's derivative times polynomials and
+ // elementary functions, and parts on it descend on their degrees.
+ || byThePolynomialsParts;
// 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
@@ -1804,6 +1822,46 @@ static Entity WithTheConstantMatchedTo(Entity antiderivative, Entity derivativeO
// search above it carry on, thirty seconds where the gate leaves it at one.
var remainingIntegral = (Integration.AnsweringTheQuestionAsked ? SolveByEulerSubstitution(remaining, x) : null)
?? Integration.ComputeIndefiniteIntegral(remaining, x, partsOnTheRemainder);
+ // After a special function, or a power of one, the remainder is its derivative
+ // times what was integrated, written as one product over a sum: for `Ei(b x)^2`,
+ // `(b x Ei(b x) - e^(b x)) e^(b x)/(b x)`, which no rule reads, where its two
+ // terms are `e^(b x) Ei(b x)` and `e^(2 b x)/(b x)`, each answered when asked.
+ // So its terms are asked, each as a question of its own. From the top only,
+ // which is where that asking reaches, and once: a term asked so is at the top
+ // itself, and its own remainder asked the same way would lift the search again,
+ // each level with the depth reset. And of a multiple of x only: of `a + b x`, the
+ // remainder is over the linear, and its terms are the exponentials a hyperbolic
+ // function is written as, each a harder question than the whole -- the decline of
+ // `x Shi(a + b x)^2` went from a second to past twenty asking them.
+ // https://github.com/asc-community/AngouriMath/issues/1501
+ if (remainingIntegral is null && besideASpecialFunction && OfAMultipleOfTheVariable(v, x)
+ && Integration.AnsweringTheQuestionAsked && !askingTheTermsOfARemainder)
+ {
+ var terms = remaining is Divf(var remainingAbove, var remainingBelow)
+ ? DistributedOverTheSum(remainingAbove).Select(term => term / remainingBelow).ToList()
+ : DistributedOverTheSum(remaining);
+ askingTheTermsOfARemainder = true;
+ try
+ {
+ foreach (var term in terms)
+ {
+ if ((Integration.ComputeIndefiniteIntegral(term, x, integrateByParts: true)
+ ?? Integration.ComputeAsAQuestionOfItsOwn(term, x, integrateByParts: true)) is not { } termIntegral)
+ {
+ remainingIntegral = null;
+ break;
+ }
+ remainingIntegral = remainingIntegral is null ? termIntegral : remainingIntegral + termIntegral;
+ }
+ }
+ finally
+ {
+ askingTheTermsOfARemainder = false;
+ }
+ }
+ else if (remainingIntegral is null && besideASpecialFunction && OfAMultipleOfTheVariable(v, x)
+ && Integration.AnsweringTheQuestionAsked)
+ Integration.DeclinedForItsScope();
if (remainingIntegral is null) return null;
return v * integralOfU - remainingIntegral;
@@ -20155,10 +20213,49 @@ Entity Term(Entity? sum, Entity coefficient, int power)
/// #1501: the error
/// functions and the exponential, logarithmic, sine, cosine and hyperbolic integrals.
///
+ ///
+ /// Set while the terms of a remainder after a special function are asked as questions of
+ /// their own, so that the asking is not nested; see TryIntegrateByPartsOnce.
+ ///
+ [System.ThreadStatic] private static bool askingTheTermsOfARemainder;
+
private static bool IsASpecialFunction(Entity node)
=> node is 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;
+ ///
+ /// A special function of a linear argument, as
+ /// reads one, or a positive whole power of one: Si(b x)^2.
+ ///
+ private static bool IsASpecialFunctionOfALinearOrAPowerOfOne(Entity factor, Variable x)
+ => IsASpecialFunctionOfALinear(factor, x)
+ || factor is Powf(var @base, Number.Integer power) && power.EInteger.Sign > 0 && IsASpecialFunctionOfALinear(@base, x);
+
+ ///
+ /// Whether the special function in , or in the base of its power,
+ /// is of a multiple of , b x, with no offset.
+ ///
+ private static bool OfAMultipleOfTheVariable(Entity factor, Variable x)
+ => (factor is Powf(var @base, _) ? @base : factor).DirectChildren.FirstOrDefault() is { } argument
+ && TreeAnalyzer.TryGetPolyLinear(argument, x, out _, out var offset) && TreeAnalyzer.IsZero(offset);
+
+ ///
+ /// The factors of that are polynomials in of
+ /// positive degree, against the rest: x sin(b x) is x and sin(b x).
+ /// Both halves where either would be empty.
+ ///
+ private static (Entity? Polynomial, Entity? Others) ThePolynomialFactor(Entity expr, Variable x)
+ {
+ Entity? polynomial = null;
+ Entity? rest = null;
+ foreach (var factor in Mulf.LinearChildren(expr))
+ if (factor.ContainsNode(x) && MathS.TryPolynomial(factor, x, out _))
+ polynomial = polynomial is null ? factor : polynomial * factor;
+ else
+ rest = rest is null ? factor : rest * factor;
+ return polynomial is null || rest is null || !rest.ContainsNode(x) ? (null, null) : (polynomial, rest);
+ }
+
///
/// Whether has what the derivative of the special function
/// is made of: an exponential of a quadratic in
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index a2770fba7..885414428 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -412,6 +412,13 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
///
[System.ThreadStatic] private static bool descentTruncated;
+ ///
+ /// Marks what is being worked out as declined for the scope it was asked in rather than
+ /// for its mathematics, as running out of descent is, so that the decline is not
+ /// remembered: the same question asked where that scope does not bind may be answered.
+ ///
+ internal static void DeclinedForItsScope() => descentTruncated = true;
+
///
/// The integrals this thread is part-way through, so that asking for one again while it is
/// still being worked out is recognised as a cycle rather than followed round again.
diff --git a/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs b/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs
index b9ead215c..0b774a01e 100644
--- a/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs
@@ -95,5 +95,43 @@ public void TheLogarithmicIntegralIsByParts(string integrand)
[InlineData("x^3*Chi(b*x)")]
public void APowerTimesASpecialFunctionIsByParts(string integrand)
=> DifferentiatesBack(integrand, Parameters);
+
+ ///
+ /// Beside a power of x times the elementary function its derivative is made of, the
+ /// special function is still the factor differentiated: x sin(b x) is integrated by
+ /// its polynomial's parts, and what is left, sin(b x)/x times that, is products of
+ /// sines and cosines over powers of x.
+ ///
+ [Theory]
+ [InlineData("x*Si(b*x)*sin(b*x)")]
+ [InlineData("x^3*Si(b*x)*sin(b*x)")]
+ [InlineData("x^2*Ci(b*x)*cos(b*x)")]
+ [InlineData("x*Ci(b*x)*sin(b*x)")]
+ [InlineData("x*Si(a + b*x)*sin(a + b*x)")]
+ [InlineData("x*Si(c + d*x)*sin(a + b*x)")]
+ public void BesideAPowerAndTheElementaryFactorOfItsDerivative(string integrand)
+ => DifferentiatesBack(integrand, Parameters);
+
+ ///
+ /// A square of a special function of b x is two rounds of parts: the first against
+ /// the power of x leaves the special function once, beside its derivative, and that
+ /// is the case above or a substitution. The remainder of a round is asked term by term,
+ /// since it comes back as one product over a sum -- (b x Ei(b x) - e^(b x)) e^(b x)/(b x)
+ /// for Ei(b x)^2 -- that no rule reads whole.
+ ///
+ [Theory]
+ [InlineData("Ei(b*x)^2")]
+ [InlineData("x*Ei(b*x)^2")]
+ [InlineData("x^2*Ei(b*x)^2")]
+ [InlineData("x*Si(b*x)^2")]
+ [InlineData("Ci(b*x)^2")]
+ [InlineData("x*Ci(b*x)^2")]
+ [InlineData("x^2*erf(b*x)^2")]
+ [InlineData("x*erfc(b*x)^2")]
+ [InlineData("erfi(b*x)^2/x^3")]
+ [InlineData("x*Shi(b*x)^2")]
+ [InlineData("Chi(b*x)^2")]
+ public void ASquareIsTwoRoundsOfParts(string integrand)
+ => DifferentiatesBack(integrand, Parameters);
}
}