diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index ccb8530f8..d286091ff 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -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 diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 46a7ea811..e04cdb285 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -3978,6 +3978,99 @@ bool Read(Entity factor, int times) return Functions.PartialFractions.Bare((constant * sum).InnerSimplified); } + /// + /// An exponential of a linear times sines and cosines of linears, and a polynomial, over + /// linears: each sine and cosine is written as exponentials, sin(c x) = (e^(i c x) - e^(-i c x))/(2i), + /// the product multiplied out, and every term is an exponential of a linear with a + /// complex rate over the linears, which + /// and answer with the exponential + /// integral of a complex argument. e^(2x) sin(x)/x is + /// (Ei((2 + i) x) - Ei((2 - i) x))/(2i), real where x is: the two terms are + /// conjugates. What by parts leaves of Si(ln(x)), under t = ln(x), is + /// e^t sin(t)/t. Rubi's 8.4, (e x)^m Si(d (a + b ln(c x^n))), answers the same way. + /// https://github.com/asc-community/AngouriMath/issues/1501 + /// + /// + /// 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. + /// + 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(); + 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); + } + /// Whether a factor of is a sum with an exponential of the variable in it. private static bool HasASumOfExponentials(Entity expr, Entity.Variable x) => expr switch { @@ -6614,6 +6707,93 @@ node is Logf(var @base, var argument) && !@base.ContainsNode(x) && @base != Math : null; } + /// + /// A power of x times a function of one logarithm of a monomial, x^m G(ln(c x^n)), + /// under t = ln(c x^n): dx = x dt/n, and x^(m + 1) is + /// K e^((m + 1) t/n) with K = x^(m + 1) (c x^n)^(-(m + 1)/n), whose derivative + /// is 0 wherever it is defined. So the answer is K/n times the integral of + /// e^((m + 1) t/n) G(t) at t = ln(c x^n), an antiderivative on the whole of + /// the real line where the integrand is real -- for an even n that includes negative + /// x, where ln(c x^n) is not ln(c) + n ln(x). A power of a monomial, + /// (e x)^p, is x^p times a factor of the same kind. What by parts leaves of + /// (e x)^m Si(d (a + b ln(c x^n))) is this, with G(t) a sine over a linear in + /// t. Rubi's answers to 8.3 to 8.5 are written in exactly this K. + /// https://github.com/asc-community/AngouriMath/issues/1501 + /// + /// + /// After , which answers ln(x) alone. + /// + 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().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; + } + + /// + /// as c x^n, c and n free of , + /// or . + /// + 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; + } + /// /// A quotient of two homogeneous polynomials in sin(u) and cos(u), /// integrated by t = tan(u) — which turns it into a rational function of t diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 5be10b342..5889a488e 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -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; @@ -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; diff --git a/Sources/Tests/UnitTests/Calculus/ExponentialIntegralIntegrationTest.cs b/Sources/Tests/UnitTests/Calculus/ExponentialIntegralIntegrationTest.cs index 18fff93ee..7190702a9 100644 --- a/Sources/Tests/UnitTests/Calculus/ExponentialIntegralIntegrationTest.cs +++ b/Sources/Tests/UnitTests/Calculus/ExponentialIntegralIntegrationTest.cs @@ -62,6 +62,34 @@ private static readonly (string, string)[] Pins = public void AnExponentialOfALinearOverAPowerOfALinear(string integrand) => DifferentiatesBack(integrand, AroundZero, Pins); + /// + /// 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: e^(2x) sin(x)/x is + /// (Ei((2 + i) x) - Ei((2 - i) x))/(2i), two conjugate terms whose sum is real. + /// + [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); + + /// + /// A power of x times a function of a logarithm of a monomial, 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) locally constant. With an even n the + /// integrand is real at negative x as well, and the answer is checked there too, + /// where ln(c x^n) is not ln(c) + n ln(x). + /// + [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")); + /// /// Over several linears, the quotient is split into partial fractions over them first, and /// each term is the question above: e^x/(x (x + 1)) is Ei(x) - Ei(x + 1)/e. The @@ -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); diff --git a/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs b/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs index 4139e5635..0ba2167aa 100644 --- a/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs +++ b/Sources/Tests/UnitTests/Calculus/SpecialFunctionsByPartsTest.cs @@ -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")); + /// + /// Beside a power of a monomial, the remainder is b (d x)^m x/((m + 1) ln(b x)), a power + /// of x over a logarithm of a monomial, which is Ei((m + 2) ln(b x)) times a + /// locally constant factor. Rubi's 8.3 row 269. + /// + [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")); + /// /// Against a power of x, the special function is the factor differentiated: what is /// left is the power times e^(-u^2), e^u/u, sin(u)/u and the like.