Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1595,6 +1595,13 @@ private static Entity IntegrateAPowerOfTheVariable(Entity @base, Entity power, E
/// gives an antiderivative holding <c>x*atan(x)</c> again, which is where the search went
/// instead and why it was left unevaluated.
/// </para>
/// <para>
/// The special functions belong here for the same reason, since each has an elementary
/// derivative: <c>x Ei(b x)</c> leaves <c>x e^(b x)/2</c> after one step, and
/// <c>x^2 Si(b x)</c> leaves <c>x^2 sin(b x)/3</c>, where integrating the special function
/// first gives an antiderivative that holds it again.
/// https://github.com/asc-community/AngouriMath/issues/1501
/// </para>
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
private static bool IsDifferentiatedBeforeAPolynomial(Entity factor)
Expand All @@ -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
Expand All @@ -1616,6 +1625,31 @@ or Entity.Arctanf or Entity.Arccotanf
_ => false
};

/// <summary>
/// Whether <paramref name="factor"/> is a special function of an argument linear in
/// <paramref name="x"/>, other than the logarithmic integral: differentiated beside a
/// power of <c>x</c>, it leaves that power times <c>sin(u)</c>, <c>e^u</c> or the like,
/// which parts against the power answer in as many steps as its degree.
/// </summary>
/// <remarks>
/// Not <c>li</c>: its derivative <c>1/ln(u)</c> leaves <c>x^m/ln(a + b x)</c>, which the
/// exponential integral's rule answers, and parts there would integrate the reciprocal of
/// the logarithm back into <c>li</c>.
/// </remarks>
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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down
99 changes: 99 additions & 0 deletions Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// The special functions integrated by parts: each has an elementary derivative, so against a
/// power of <c>x</c> 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
/// </summary>
[Trait("Area", "Calculus")]
public sealed class SpecialFunctionsByPartsTest
{
/// <summary>Off 0, where the negative powers are undefined.</summary>
private static readonly double[] Points = { -1.7, -0.6, 0.35, 0.9, 1.45 };

/// <summary>
/// Integrates, pins the parameters, and compares the derivative of the answer with the
/// integrand at <see cref="Points"/>. The parameters are pinned after integrating, so the
/// rule is asked the symbolic question.
/// </summary>
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") };

/// <summary>
/// Alone, against 1: <c>int Ei(u) = u Ei(u) - e^u</c> and its kin, each over the rate.
/// </summary>
[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);

/// <summary>
/// The logarithmic integral, where its argument stays above 0 at every point: <c>a = 3</c>
/// keeps <c>a + b x</c> between 0.79 and 4.9.
/// </summary>
[Theory]
[InlineData("li(a + b*x)")]
[InlineData("x*li(a + b*x)")]
public void TheLogarithmicIntegralIsByParts(string integrand)
=> DifferentiatesBack(integrand, ("a", "3"), ("b", "13/10"));

/// <summary>
/// Against a power of <c>x</c>, the special function is the factor differentiated: what is
/// left is the power times <c>e^(-u^2)</c>, <c>e^u/u</c>, <c>sin(u)/u</c> and the like.
/// </summary>
[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);
}
}
Loading