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
24 changes: 24 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2381,6 +2381,30 @@ family 3 at twenty a file and a sample of the others are answered alone as they
|---|---|---|
| `"li(b*x)/x".Integrate("x")` | `li * b * x + C`, with `li` a variable | `li(b * x) * ln(b * x) - b * x + C` |

### A special function of a logarithm of a monomial is integrated, through `Ei` of a complex argument

`(e x)^m Si(d (a + b ln(c x^n)))` is by parts against `(e x)^m`, and what is left is a power of `x`
times `sin(d (a + b ln(c x^n)))/(a + b ln(c x^n))`. Two rules were missing on the way
([#1501](https://github.com/asc-community/AngouriMath/issues/1501)):

- **A function of one logarithm of a monomial.** A power of `x` times `G(ln(c x^n))` is integrated
under `t = ln(c x^n)`, with `x^(m + 1)` written as `K e^((m + 1) t/n)` and
`K = x^(m + 1) (c x^n)^(-(m + 1)/n)`, whose derivative is 0 wherever it is defined. That is an
antiderivative wherever the integrand is real. For an even `n` that includes negative `x`, where
`ln(c x^n)` is not `ln(c) + n ln(x)`. By parts leaves `(d x)^m x/ln(b x)` of `(d x)^m li(b x)`,
and this rule answers it with `Ei((m + 2) ln(b x))`.
- **An exponential beside a sine or cosine, over linears.** Each sine and cosine is written as
exponentials, and each term is the exponential integral of a complex argument. The two conjugate
terms add up to a real value.

Both columns measured on a build, `v2.5.0` against this change.

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"e^(2*x)*sin(x)/x".Integrate("x")` | `integral(e ^ (2 * x) * sin(x) / x, x)` | `(-1/2 * i) * Ei((2 + i) * x) + 1/2 * i * Ei((2 - i) * x) + C` |
| `"x*sin(ln(x))/ln(x)".Integrate("x")` | `integral(x * sin(ln(x)) / ln(x), x)` | `(-1/2 * i) * Ei((2 + i) * ln(x)) + 1/2 * i * Ei((2 - i) * ln(x)) + C` |
| `"cos(a+b*ln(c*x^n))^2".Integrate("x")` | `integral(cos(a + b * ln(c * x ^ n)) ^ 2, x)` | an antiderivative in `sin` and `cos` of `2 (a + b ln(c x^n))`, beside `x (c x^n)^(-1/n)` |

### 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3978,6 +3978,99 @@ bool Read(Entity factor, int times)
return Functions.PartialFractions.Bare((constant * sum).InnerSimplified);
}

/// <summary>
/// An exponential of a linear times sines and cosines of linears, and a polynomial, over
/// linears: each sine and cosine is written as exponentials, <c>sin(c x) = (e^(i c x) - e^(-i c x))/(2i)</c>,
/// the product multiplied out, and every term is an exponential of a linear with a
/// complex rate over the linears, which <see cref="SolveAnExponentialOfALinearOverAPowerOfALinear"/>
/// and <see cref="SolveAnExponentialOverSeveralLinears"/> answer with the exponential
/// integral of a complex argument. <c>e^(2x) sin(x)/x</c> is
/// <c>(Ei((2 + i) x) - Ei((2 - i) x))/(2i)</c>, real where <c>x</c> is: the two terms are
/// conjugates. What by parts leaves of <c>Si(ln(x))</c>, under <c>t = ln(x)</c>, is
/// <c>e^t sin(t)/t</c>. Rubi's 8.4, <c>(e x)^m Si(d (a + b ln(c x^n)))</c>, answers the same way.
/// https://github.com/asc-community/AngouriMath/issues/1501
/// </summary>
/// <remarks>
/// Only with an exponential of a real rate beside the sine or cosine: alone over a linear,
/// the sine and cosine integrals are their closed form, and the rule for them answers.
/// </remarks>
internal static Entity? SolveAnExponentialTimesATrigonometricOverLinears(Entity expr, Entity.Variable x)
{
if (expr is not (Divf or Mulf))
return null;
int exponentialsByShape = 0, trigonometricsByShape = 0;
var aPowerOfASumByShape = false;
CountFactorsByShape(expr, x, ref exponentialsByShape, ref trigonometricsByShape, ref aPowerOfASumByShape);
if (exponentialsByShape == 0 || trigonometricsByShape == 0)
return null;
Entity constant = Number.Integer.One;
Entity? polynomial = null;
var linears = new List<(Entity Linear, int Power)>();
var exponents = new List<WrittenExponent>();
var terms = new List<(Entity Coefficient, int[] Multiples)> { (Number.Integer.One, new int[MostExponents]) };
int exponentials = 0, trigonometrics = 0;
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 && 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 (AsExponentialsOfQuadratics(factor, x, exponents) is { } read)
{
if (factor.Nodes.Any(node => node is Sinf or Cosf))
trigonometrics++;
else
exponentials++;
if (Multiplied(terms, read) is not { } product)
return null;
terms = product;
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 == 0 || trigonometrics == 0 || linears.Count == 0 || linears.Count > 4 || linears.Sum(pair => pair.Power) > 12)
return null;
var below = linears.Aggregate((Entity)Number.Integer.One, (product, pair) => product * MathS.Pow(pair.Linear, pair.Power));
var above = polynomial ?? Number.Integer.One;
Entity sum = Number.Integer.Zero;
foreach (var (coefficient, multiples) in terms)
{
var (a, b, c) = TheExponent(multiples, exponents);
// Linear exponents only: a quadratic left is the Gaussian's, and is another rule's.
if (!TreeAnalyzer.IsZero(a))
return null;
var factorOfTheTerm = TreeAnalyzer.IsZero(c) ? coefficient : coefficient * MathS.Pow(MathS.e, c);
var question = TreeAnalyzer.IsZero(b)
? above / below
: MathS.Pow(MathS.e, b * x) * above / below;
if (((TreeAnalyzer.IsZero(b) ? null
: linears.Count == 1 ? SolveAnExponentialOfALinearOverAPowerOfALinear(question, x) : SolveAnExponentialOverSeveralLinears(question, x))
?? Integration.ComputeIndefiniteIntegral(question, x, false)) is not { } integral)
return null;
sum += factorOfTheTerm * 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 @@ -6614,6 +6707,93 @@ node is Logf(var @base, var argument) && !@base.ContainsNode(x) && @base != Math
: null;
}

/// <summary>
/// A power of <c>x</c> times a function of one logarithm of a monomial, <c>x^m G(ln(c x^n))</c>,
/// under <c>t = ln(c x^n)</c>: <c>dx = x dt/n</c>, and <c>x^(m + 1)</c> is
/// <c>K e^((m + 1) t/n)</c> with <c>K = x^(m + 1) (c x^n)^(-(m + 1)/n)</c>, whose derivative
/// is 0 wherever it is defined. So the answer is <c>K/n</c> times the integral of
/// <c>e^((m + 1) t/n) G(t)</c> at <c>t = ln(c x^n)</c>, an antiderivative on the whole of
/// the real line where the integrand is real -- for an even <c>n</c> that includes negative
/// <c>x</c>, where <c>ln(c x^n)</c> is not <c>ln(c) + n ln(x)</c>. A power of a monomial,
/// <c>(e x)^p</c>, is <c>x^p</c> times a factor of the same kind. What by parts leaves of
/// <c>(e x)^m Si(d (a + b ln(c x^n)))</c> is this, with <c>G(t)</c> a sine over a linear in
/// <c>t</c>. Rubi's answers to 8.3 to 8.5 are written in exactly this <c>K</c>.
/// https://github.com/asc-community/AngouriMath/issues/1501
/// </summary>
/// <remarks>
/// After <see cref="SolveByLogarithmSubstitution"/>, which answers <c>ln(x)</c> alone.
/// </remarks>
internal static Entity? SolveByAPowerAndALogarithmOfAMonomial(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!expr.Nodes.Any(node => node is Logf))
return null;
Entity constant = Number.Integer.One;
Entity m = Number.Integer.Zero;
Entity locallyConstant = Number.Integer.One;
Entity rest = Number.Integer.One;
foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
{
if (!factor.ContainsNode(x))
{
constant = underneath ? constant / factor : constant * factor;
continue;
}
// x, a power of x, or a power of a monomial (k x)^p, p free of x.
var (@base, power) = factor is Powf(var raised, var exponent) && !exponent.ContainsNode(x) ? (raised, exponent) : (factor, (Entity)Number.Integer.One);
if (TheMonomial(@base, x) is var (coefficientOfIt, degreeOfIt) && degreeOfIt == Number.Integer.One)
{
var p = underneath ? -power : power;
m = m + p;
if (coefficientOfIt != Number.Integer.One)
locallyConstant = locallyConstant * MathS.Pow(@base, p) * MathS.Pow(x, -p);
continue;
}
rest = underneath ? rest / factor : rest * factor;
}
// One logarithm of a monomial in what is left.
var logarithms = rest.Nodes.OfType<Logf>().Where(node => node.Base == MathS.e && node.ContainsNode(x)).Distinct().ToList();
if (logarithms.Count != 1 || TheMonomial(logarithms[0].Antilogarithm, x) is not var (c, n) || TreeAnalyzer.IsZero(n))
return null;
var t = Variable.CreateUnique(expr, "t_log");
var inT = rest.Substitute(logarithms[0], t);
if (inT.ContainsNode(x))
return null;
var mPlusOne = (m + Number.Integer.One).InnerSimplified;
// One quotient, as the logarithm substitution writes its own: `e^((m + 2) t)/t` is the
// exponential integral, and as a product with `t^(-1)` it went to integration by parts.
// https://github.com/asc-community/AngouriMath/issues/1646
var integrand = Functions.SingleQuotient.Combine(inT * MathS.Pow(MathS.e, (mPlusOne / n).InnerSimplified * t)).InnerSimplified;
if (integrand is Providedf(var bare, _))
integrand = bare;
if (Integration.ComputeIndefiniteIntegral(integrand, t, integrateByParts) is not { } inTIntegral)
return null;
// Of ln(x) itself, K is 1, and written as x^(m + 1) x^(-(m + 1)) it is not seen to be.
var k = logarithms[0].Antilogarithm == x ? Number.Integer.One
: MathS.Pow(x, mPlusOne) * MathS.Pow(logarithms[0].Antilogarithm, (-mPlusOne / n).InnerSimplified);
var back = constant * locallyConstant * k / n * inTIntegral.Substitute(t, logarithms[0]);
return back.Nodes.Any(node => node == MathS.NaN) ? null : back;
}

/// <summary>
/// <paramref name="expr"/> as <c>c x^n</c>, <c>c</c> and <c>n</c> free of <paramref name="x"/>,
/// or <see langword="null"/>.
/// </summary>
private static (Entity Coefficient, Entity Degree)? TheMonomial(Entity expr, Entity.Variable x)
{
if (expr == x)
return (Number.Integer.One, Number.Integer.One);
if (expr is Powf(var @base, var power) && @base == x && !power.ContainsNode(x))
return (Number.Integer.One, power);
if (expr is Mulf(var left, var right))
{
if (!left.ContainsNode(x) && TheMonomial(right, x) is var (c, n))
return (left * c, n);
if (!right.ContainsNode(x) && TheMonomial(left, x) is var (c2, n2))
return (right * c2, n2);
}
return null;
}

/// <summary>
/// A quotient of two <b>homogeneous</b> polynomials in <c>sin(u)</c> and <c>cos(u)</c>,
/// integrated by <c>t = tan(u)</c> — which turns it into a rational function of <c>t</c>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// 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;
// And with sines and cosines beside the exponential, written as exponentials: each term
// is then the exponential's, with a complex rate.
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialTimesATrigonometricOverLinears(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 Expand Up @@ -838,6 +841,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// is why the general substitution does not find it and why it pays: the integrator
// answers an exponential times almost anything.
if ((answer = IndefiniteIntegralSolver.SolveByLogarithmSubstitution(expr, x, integrateByParts)) is { }) return answer;
// And a power of x times a function of one logarithm of a monomial, under t = that logarithm.
if ((answer = IndefiniteIntegralSolver.SolveByAPowerAndALogarithmOfAMonomial(expr, x, integrateByParts)) is { }) return answer;
// And the inverse trigonometric functions' own, beside the logarithm's and for the
// same reason: `x = sin(u)` removes the `x` that substituting for `arcsin(x)` leaves.
if ((answer = IndefiniteIntegralSolver.SolveByInverseTrigonometricSubstitution(expr, x, integrateByParts)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,34 @@ private static readonly (string, string)[] Pins =
public void AnExponentialOfALinearOverAPowerOfALinear(string integrand)
=> DifferentiatesBack(integrand, AroundZero, Pins);

/// <summary>
/// Beside a sine or a cosine of a linear, the sine and cosine are written as exponentials,
/// and each term is the exponential's above with a complex rate: <c>e^(2x) sin(x)/x</c> is
/// <c>(Ei((2 + i) x) - Ei((2 - i) x))/(2i)</c>, two conjugate terms whose sum is real.
/// </summary>
[Theory]
[InlineData("e^(2*x)*sin(x)/x")]
[InlineData("e^x*cos(x)/(1 + x)")]
[InlineData("x*e^x*sin(2*x)/(x - 1)")]
public void AnExponentialTimesASineOrCosineOverALinear(string integrand)
=> DifferentiatesBack(integrand, AroundZero, Pins);

/// <summary>
/// A power of <c>x</c> times a function of a logarithm of a monomial, under
/// <c>t = ln(c x^n)</c>, with <c>x^(m + 1)</c> written as <c>K e^((m + 1) t/n)</c> and
/// <c>K = x^(m + 1) (c x^n)^(-(m + 1)/n)</c> locally constant. With an even <c>n</c> the
/// integrand is real at negative <c>x</c> as well, and the answer is checked there too,
/// where <c>ln(c x^n)</c> is not <c>ln(c) + n ln(x)</c>.
/// </summary>
[Theory]
[InlineData("x*sin(ln(x))/ln(x)")]
[InlineData("Si(d*(a + b*ln(c*x^n)))")]
[InlineData("x*Ci(d*(a + b*ln(c*x^n)))")]
[InlineData("(k*x)^m*Ei(d*(a + b*ln(c*x^n)))")]
public void APowerTimesAFunctionOfALogarithmOfAMonomial(string integrand)
=> DifferentiatesBack(integrand, new[] { -2.1, -0.7, 0.6, 1.7 },
("a", "2/5"), ("b", "13/10"), ("c", "7/10"), ("d", "19/10"), ("k", "11/10"), ("m", "1/2"), ("n", "2"));

/// <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
Expand Down Expand Up @@ -103,6 +131,7 @@ public void AnOddNegativeMomentOfTheGaussian(string integrand)
[InlineData("(1 + x)/ln(x)")]
[InlineData("x^2/ln(c*(d + k*x^3)^n)")]
[InlineData("x^8/ln(c*(d + k*x^3)^n)^2")]
[InlineData("(k*x)^m*x/ln(b*x)")]
public void APowerOverAPowerOfALogarithm(string integrand)
=> DifferentiatesBack(integrand, PastOne, Pins);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ public void TheLogarithmicIntegralIsByParts(string integrand)
public void TheLogarithmicIntegralOverXIsOneRoundOfParts()
=> DifferentiatesBackAt(new[] { 0.35, 1.45, 2.3 }, "li(b*x)/x", ("b", "13/10"));

/// <summary>
/// Beside a power of a monomial, the remainder is <c>b (d x)^m x/((m + 1) ln(b x))</c>, a power
/// of <c>x</c> over a logarithm of a monomial, which is <c>Ei((m + 2) ln(b x))</c> times a
/// locally constant factor. Rubi's 8.3 row 269.
/// </summary>
[Fact]
public void TheLogarithmicIntegralBesideAPowerOfAMonomial()
=> DifferentiatesBackAt(new[] { 0.35, 1.45, 2.3 }, "(d*x)^m*li(b*x)", ("b", "13/10"), ("d", "19/10"), ("m", "1/2"));

/// <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.
Expand Down
Loading