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
20 changes: 20 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3859,6 +3859,102 @@ bool Read(Entity factor, int times)
}
}

/// <summary>
/// An exponential of a linear, or a sum of them -- which is how <c>sinh</c> and <c>cosh</c>
/// 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
/// <see cref="SolveAnExponentialOfALinearOverAPowerOfALinear"/> and
/// <see cref="SolveAHyperbolicOfALinearOverAPowerOfALinear"/>. <c>e^x/(x (x + 1))</c> is
/// <c>e^x/x - e^x/(x + 1)</c>, which is <c>Ei(x) - Ei(x + 1)/e</c>; the trigonometric rule
/// splits the same way (<see cref="OverAPowerOfALinear"/>). It is also what by parts leaves
/// of <c>Ei(a + b x)/x^2</c>, <c>e^(a + b x)/((a + b x) x)</c>.
/// https://github.com/asc-community/AngouriMath/issues/1501
/// </summary>
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<Entity>();
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);
}

/// <summary>Whether a factor of <paramref name="expr"/> is a sum with an exponential of the variable in it.</summary>
private static bool HasASumOfExponentials(Entity expr, Entity.Variable x) => expr switch
{
Expand Down Expand Up @@ -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<Entity>();
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ private static readonly (string, string)[] Pins =
public void AnExponentialOfALinearOverAPowerOfALinear(string integrand)
=> DifferentiatesBack(integrand, AroundZero, Pins);

/// <summary>
/// Over several linears, the quotient is split into partial fractions over them first, and
/// each term is the question above: <c>e^x/(x (x + 1))</c> is <c>Ei(x) - Ei(x + 1)/e</c>. The
/// last row is what by parts leaves of <c>Ei(a + b x)/x^2</c>.
/// </summary>
[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);

/// <summary>
/// The Gaussian's odd negative moments end at <c>int e^(A x^2)/x = Ei(A x^2)/2</c>, and
/// <c>x e^(-1/x^2)</c> is one of them under <c>u = 1/x</c>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ private static readonly (string, string)[] Pins =
public void ASumOfExponentialsOverAPowerOfALinear(string integrand)
=> DifferentiatesBack(integrand, AroundZero, Pins);

/// <summary>
/// And over several linears, split into partial fractions over them first, each term the
/// question above.
/// </summary>
[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);

/// <summary>
/// An inverse hyperbolic tangent below the bar, beside a whole power of <c>1 - a^2 x^2</c>:
/// under <c>x = tanh(u)/a</c> it is a polynomial in <c>sinh(u)</c> and <c>cosh(u)</c> over
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading