diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 04230258d..35527cf88 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -4889,6 +4889,23 @@ now, and a closed interval at one point meets the other in that point or in noth |---|---|---| | `"(3; 1) /\ [0; 5]".ToEntity().InnerSimplified` | `[0; 5]` — wrong | `{ }` | +### Exponentials of linears in two bases over a power of x are integrated to the exponential integral + +**Answers where there were none.** `a^x b^x/x`, `a^x b^x/x^2` and `a^x b^x/x^3`, Rubi's 2.3, were declined, and so was +`sinh(f x)^2/x`. The rules for an exponential over a power of x read one exponential, and no spelling of the integrand +merges two: `(a b)^x` is `e^(x ln(a b))`, and `ln(a b)` is not `ln(a) + ln(b)` for every `a` and `b`. A product of +exponentials of linears beside a negative power of x is now taken as one, `e^(r x)` with `r = ln(a) + ln(b)`, by parts +down to `Ei(r x)`. Where the rates cancel, as `e^(f x) e^(-f x)` does in `sinh(f x)^2`, the term is the power of x +alone, decided as a value rather than read off the written `f - f`. An exponent with a quadratic in it, or a sine +beside the exponentials, is declined as before ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"a^x*b^x/x".ToEntity().Integrate("x")` | `integral(...)` | `Ei((ln(a) + ln(b)) x)`, 27 characters | +| `"a^x*b^x/x^3".ToEntity().Integrate("x")` | `integral(...)` | `e^(r x)` over `x^2` and over `x`, and `Ei(r x)`, 214 characters | +| `"a^(c+d*x)*b^(f+g*x)/x".ToEntity().Integrate("x")` | `integral(...)` | `e^(c ln(a) + f ln(b)) Ei((d ln(a) + g ln(b)) x)`, 65 characters | +| `"sinh(f*x)^2/x".ToEntity().Integrate("x")` | `integral(...)` | `Ei(2 f x)/4 - ln(x)/2 + Ei(-2 f x)/4`, 63 characters | + ### A power of an exponential over a polynomial is integrated to the exponential integral **Answers where there were none.** `(F^(g (e + f x)))^n` with a symbol for `F` or `n` was read by no rule diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index f47f57a9b..3ab6ac777 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -8355,8 +8355,10 @@ void AddALinear(Entity linear, int power) quadratics.Add((coefficient, a, b, c)); } // Some exponent left a quadratic: with none, the product is the elementary - // exponential's, and the rules for that answer it without an imaginary unit. - if (!quadratics.Any(term => !TreeAnalyzer.IsZero(term.A))) + // exponential's, and the rules for that answer it without an imaginary unit -- except + // beside a negative power of x, below. + var aQuadratic = quadratics.Any(term => !TreeAnalyzer.IsZero(term.A)); + if (!aQuadratic && trigonometrics > 0) return null; // The polynomial and the power of x together: as the product written, for the moments // at a square's centre, which substitute into it as the integrand writes it, and as @@ -8378,6 +8380,8 @@ void AddALinear(Entity linear, int power) if (powers is not null && powers.Keys.Any(power => power.Abs().CompareTo(EInteger.FromInt32(MostPowerOfAPolynomialBesideExponentials)) > 0)) return null; var aNegativePower = powers is not null && powers.Keys.Any(power => power.Sign < 0); + if (!aQuadratic && !aNegativePower) + return null; Entity total = Number.Integer.Zero; foreach (var (coefficient, a, b, c) in quadratics) { @@ -8392,9 +8396,39 @@ void AddALinear(Entity linear, int power) else integral = IntegralPatterns.APolynomialTimesTheGaussian(written, MathS.e, a, b, c, x); } - // A negative power beside an elementary exponential is the exponential integral. + // A negative power beside an elementary exponential is the exponential integral: + // for exponentials alone, each power of x against e^(b x) by parts down to Ei(b x). + // The other rules read one exponential, and a^x b^x/x is two of different bases, + // which no spelling of the integrand merges: (a b)^x is e^(x ln(a b)), and + // ln(a b) is not ln(a) + ln(b) for every a and b. else if (aNegativePower) - return null; + { + if (aQuadratic || powers is null) + return null; + // A rate that cancels, `b - b` from e^(b x) e^(-b x), is a power of x alone, and is + // decided as a value: written, it is not zero, and the exponential integral of it + // divided by it. + if (VanishesIdentically(b)) + { + Entity powersIntegrated = Number.Integer.Zero; + foreach (var monomial in powers) + powersIntegrated += monomial.Value * (monomial.Key.Equals(EInteger.FromInt32(-1)) + ? MathS.Ln(x) + : MathS.Pow(x, Number.Integer.Create(monomial.Key + 1)) / Number.Integer.Create(monomial.Key + 1)); + integral = MathS.Pow(MathS.e, c) * powersIntegrated; + total += coefficient * integral; + continue; + } + var exponential = MathS.Pow(MathS.e, b * x); + Entity elementary = Number.Integer.Zero, ei = Number.Integer.Zero; + foreach (var monomial in powers) + { + var (part, eiCoefficient) = APowerTimesAnExponential(monomial.Key.ToInt32Checked(), b, exponential, x); + elementary += monomial.Value * part; + ei += monomial.Value * eiCoefficient; + } + integral = MathS.Pow(MathS.e, c) * (elementary + ei * MathS.Ei(b * x)); + } else if (!TreeAnalyzer.IsZero(b)) integral = MathS.Pow(MathS.e, b * x + c) * APolynomialAgainstAnExponential(powers, b, x); else diff --git a/Sources/Tests/UnitTests/Calculus/ExponentialsOfTwoBasesOverAPowerOfXIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ExponentialsOfTwoBasesOverAPowerOfXIntegralTest.cs new file mode 100644 index 000000000..ac6537266 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ExponentialsOfTwoBasesOverAPowerOfXIntegralTest.cs @@ -0,0 +1,63 @@ +// +// 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 AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// Exponentials of linears in different bases over a power of x: a^x b^x/x is + /// Ei((ln(a) + ln(b)) x). The rule for a product of exponentials left every product + /// without a quadratic to the rules for one exponential, and beside a negative power of x none + /// of them reads two. Rubi's 2.3. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class ExponentialsOfTwoBasesOverAPowerOfXIntegralTest + { + [Fact] + public void TheExponentialIntegralOfTheSumOfTheLogarithms() + => Assert.Equal("Ei((ln(a) + ln(b)) * x)".ToEntity(), "a^x*b^x/x".ToEntity().Integrate("x").Substitute("C", 0).InnerSimplified); + + [Theory] + [InlineData("a^x*b^x/x^2")] + [InlineData("a^x*b^x/x^3")] + [InlineData("a^x*b^x*(1 + x)/x^2")] + [InlineData("a^(c + d*x)*b^(f + g*x)/x")] + [InlineData("2^x*3^x/x")] + // e^(b x) e^(-b x) beside 1/x is 1/x, its rate b - b decided as a value: written, the rate is + // not zero, and these were answered with the exponential integral of it divided by it. + [InlineData("x*Shi(f*x)*sinh(f*x)")] + [InlineData("x*Chi(f*x)*cosh(f*x)")] + [InlineData("sinh(f*x)^2/x")] + public void ByPartsDownToTheExponentialIntegral(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.3) + .Substitute("f", 2.3).Substitute("g", -0.6); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -0.9, -0.4, 0.3, 0.8, 1.3, 2.9 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } +}