diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 33458eb6e..dc450e579 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1745,6 +1745,21 @@ quadratic alone, or beside a polynomial only, is left to the rules that answered | `"x^2*sinh(a + b*x + c*x^2)^2".Integrate("x")` | `integral(x ^ 2 * ((e ^ (a + b * x + c * x ^ 2) - e ^ (-(a + b * x + c * x ^ 2))) / 2) ^ 2, x)` | the antiderivative | | `"2^(x^2)*3^(x + 1)".Integrate("x")` | `integral(2 ^ x ^ 2 * 3 ^ (x + 1), x)` | the antiderivative, with `erfi` | +### An exponential of a quadratic in a logarithm beside a power of `x` is integrated + +`e^(ln(x)^2)` was left unintegrated. `x^p G^(Q(L))`, where `L` is `ln(c x^r)` and `Q` a quadratic +with a square term, is now integrated under `t = L`, where it is the Gaussian with a linear term. A +polynomial beside it is summed a power at a time, and the logarithm of a power of a linear is the +same question under `u = d + e x` +([#1501](https://github.com/asc-community/AngouriMath/issues/1501)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"e^(ln(x)^2)".Integrate("x")` | `integral(e ^ ln(x) ^ 2, x)` | the antiderivative, with `erfi` | +| `"x^2*F^(f*(a + b*ln(c*x^n))^2)".Integrate("x")` | `integral(x ^ 2 * F ^ (f * (a + b * ln(c * x ^ n)) ^ 2), x)` | the antiderivative | +| `"(d*g + k*g*x)^m*F^(f*(a + b*ln(c*(d + k*x)^n))^2)".Integrate("x")` | `integral((d * g + k * g * x) ^ m * F ^ (f * (a + b * ln(c * (d + k * x) ^ n)) ^ 2), x)` | the antiderivative | +| `"(g + h*x)^3*F^(f*(a + b*ln(c*(d + k*x)^n)^2))".Integrate("x")` | `integral((g + h * x) ^ 3 * F ^ (f * (a + b * ln(c * (d + k * x) ^ n) ^ 2)), x)` | the antiderivative | + ### `binomial(n, k)` is a function **Addition, not silent.** The binomial coefficient is a node, `Entity.Binomialf`, spelled diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 47bf3f25e..af38df176 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -642,3 +642,6 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Calculus/ExponentialsOfQuadraticsTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + +[Tests/UnitTests/Calculus/ExponentialOfAQuadraticInALogarithmTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 3ef20c341..e53e44037 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -9059,6 +9059,166 @@ private static Entity SquarefreePartAsWritten(Entity denominator, Entity.Variabl return 2 / slopeOfF * theConstant * answerInT.Substitute(t, MathS.Sqrt(logarithm)); } + /// + /// A power of x times an exponential of a quadratic in a logarithm, x^p G^(Q(L)) with + /// L = ln(c x^r) and Q a quadratic with a square term, onto the + /// Gaussian. With t = L and L' = s/x, dx = x dt/s, and + /// x^(p + 1) e^(-(p + 1) L/s) is a constant, so the integral is that constant over + /// s times the integral of e^(Q(t) ln G + (p + 1) t/s): the Gaussian with a + /// linear term, which the table answers. A polynomial beside it is summed a power at a time, + /// and the logarithm of a power of a linear is the same question under u = d + e x. + /// Rubi's 2.3, F^(f (a + b ln(c (d + e x)^n))^2) (g + h x)^m and + /// F^(f (a + b ln(c (d + e x)^n)^2)) (g + h x)^m. + /// https://github.com/asc-community/AngouriMath/issues/1501 + /// + /// + /// The constant is written x^(p + 1) e^(-(p + 1) L/s), as + /// writes it and as Rubi does, + /// rather than with ln(c x^r) split into ln c + r ln x, which holds only + /// where c is positive. A quadratic without its square term is a power of + /// c x^r, which the rules for powers answer. + /// + internal static Entity? SolveAPowerTimesAnExponentialOfAQuadraticInALogarithm(Entity expr, Entity.Variable x) + { + // The exponential and its logarithm first, which allocates nothing where there is + // none; and the logarithm of a linear before any factor is read, since beside it a + // constant multiple of that linear, (d g + e g x)^m, is a factor only in u. + if (expr is not (Mulf or Divf or Powf) || AnExponentialOfALogarithm(expr, x) is not { } theExponential) + return null; + if (TheLogarithmOfAPowerOfX(theExponential.Exponent, x) is not { } logarithm) + return LinearUnderALogarithm(theExponential.Exponent, x) is { } linear ? UnderTheLinearUnderTheLogarithm(expr, x, linear) : null; + Entity? p = null; + Entity constant = Number.Integer.One; + Entity? @base = null, exponent = null; + Dictionary? polynomial = null; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (!factor.ContainsNode(x)) + { + constant = underneath ? constant / factor : constant * factor; + continue; + } + if (factor == x && p is null) + p = underneath ? Number.Integer.MinusOne : Number.Integer.One; + else if (factor is Powf(var b, var e) && b == x && !e.ContainsNode(x) && p is null) + p = underneath ? -e : e; + else if (factor is Powf(Mulf(var left, var right), var scaledExponent) && (left == x) != (right == x) + && !(left == x ? right : left).ContainsNode(x) && !scaledExponent.ContainsNode(x) && p is null) + { + p = underneath ? -scaledExponent : scaledExponent; + var scalePower = MathS.Pow(left == x ? right : left, scaledExponent); + constant = underneath ? constant / scalePower : constant * scalePower; + } + else if (exponent is null && factor is Powf(var g, var power) && !g.ContainsNode(x) && power.ContainsNode(x)) + (@base, exponent) = (g, underneath ? -power : power); + else if (polynomial is null && !underneath && TreeAnalyzer.TryGetPolynomial(factor, x, out var monomials) + && monomials.Count > 1 && monomials.Keys.All(degree => degree.Sign >= 0 && degree.CompareTo(EInteger.FromInt32(12)) <= 0)) + polynomial = monomials; + else + return null; + } + if (@base is null || exponent is null || !exponent.ContainsNode(logarithm)) + return null; + var t = Variable.CreateUnique(expr, "t"); + var inT = exponent.Replace(node => node == logarithm ? t : node); + if (inT.ContainsNode(x) || !TreeAnalyzer.TryGetPolyQuadratic(inT, t, out var square, out _, out _) || TreeAnalyzer.IsZero(square)) + return null; + var rate = Functions.PartialFractions.Bare((logarithm.Differentiate(x) * x).Simplify()); + if (rate.ContainsNode(x) || rate.Evaled is Number.Complex { IsZero: true } || rate.Nodes.Any(node => node == MathS.NaN)) + return null; + var exponentInT = @base == MathS.e ? inT : inT * MathS.Ln(@base); + p = (p ?? Number.Integer.Zero).InnerSimplified; + if (polynomial is null) + return ExponentialOfAQuadraticInALogarithmTerm(p, exponentInT, t, logarithm, rate, x) is { } single ? (constant * single).InnerSimplified : null; + Entity sum = Number.Integer.Zero; + foreach (var monomial in polynomial) + { + if (ExponentialOfAQuadraticInALogarithmTerm((p + Number.Integer.Create(monomial.Key)).InnerSimplified, exponentInT, t, logarithm, rate, x) is not { } term) + return null; + sum += monomial.Value * term; + } + return (constant * sum).InnerSimplified; + } + + /// + /// The integral of x^q e^(E(L)), where is E in + /// standing for and L' = s/x: see + /// . + /// + private static Entity? ExponentialOfAQuadraticInALogarithmTerm(Entity q, Entity exponentInT, Entity.Variable t, Entity logarithm, Entity rate, Entity.Variable x) + { + var qPlusOne = (q + 1).InnerSimplified; + var atMinusOne = qPlusOne.Evaled is Number.Complex { IsZero: true }; + var inT = MathS.Pow(MathS.e, atMinusOne ? exponentInT : exponentInT + qPlusOne / rate * t); + if (IntegralPatterns.TryStandardIntegrals(inT, t) is not { } answerInT) + return null; + // x^(q + 1) e^(-(q + 1) ln(x)) is 1 wherever ln(x) is real, so for ln(x) itself it is left out. + Entity theConstant = atMinusOne || logarithm == MathS.Ln(x) + ? Number.Integer.One + : MathS.Pow(x, qPlusOne) * MathS.Pow(MathS.e, -qPlusOne * logarithm / rate); + return theConstant / rate * answerInT.Substitute(t, logarithm); + } + + /// + /// A factor of that is an exponential of with a + /// logarithm in its exponent, read through products and quotients, or . + /// + private static Powf? AnExponentialOfALogarithm(Entity expr, Entity.Variable x) => expr switch + { + Mulf(var left, var right) => AnExponentialOfALogarithm(left, x) ?? AnExponentialOfALogarithm(right, x), + Divf(var numerator, var denominator) => AnExponentialOfALogarithm(numerator, x) ?? AnExponentialOfALogarithm(denominator, x), + Powf(var @base, var exponent) power when !@base.ContainsNode(x) && exponent.ContainsNode(x) + && exponent.Nodes.Any(node => node is Logf) => power, + _ => null + }; + + /// + /// The one natural logarithm of in , where its + /// argument is a constant times a power of , or . + /// + private static Entity? TheLogarithmOfAPowerOfX(Entity expr, Entity.Variable x) + { + Entity? logarithm = null; + foreach (var node in expr.Nodes) + if (node is Logf(var @base, var argument) && @base == MathS.e && argument.ContainsNode(x)) + { + if (logarithm is not null && logarithm != node) + return null; + logarithm = node; + } + return logarithm is Logf(_, var antilogarithm) && IsAProductOfPowersOfTheVariable(antilogarithm, x) ? logarithm : null; + } + + /// + /// under u = d + e x, with the + /// d + e x under the logarithm, answered by + /// in u and + /// written back in x. A constant multiple of the linear, as (d g + e g x)^m + /// writes one, is read as g u, where substituting for x would leave a sum + /// that is a multiple of u only once simplified. + /// + private static Entity? UnderTheLinearUnderTheLogarithm(Entity expr, Entity.Variable x, Entity linear) + { + if (!TreeAnalyzer.TryGetPolyLinear(linear, x, out var slope, out var intercept) || slope is null || intercept is null) + return null; + var u = Variable.CreateUnique(expr, "u"); + var inU = (expr.Replace(node => + { + if (node == linear) + return u; + if (node is not (Sumf or Minusf) || !TreeAnalyzer.TryGetPolyLinear(node, x, out var nodeSlope, out var nodeIntercept) + || TreeAnalyzer.IsZero(nodeSlope) || TreeAnalyzer.IsZero(nodeIntercept)) + return node; + // Bare: simplified, k g/k is g provided k is not 0, which k is, the linear being one. + return Functions.PartialFractions.Bare((nodeIntercept * slope - nodeSlope * intercept).Simplify()).Evaled is Number.Complex { IsZero: true } + ? Functions.PartialFractions.Bare((nodeSlope / slope).Simplify()) * u + : node; + }).Substitute(x, (u - intercept) / slope) / slope).InnerSimplified; + if (inU.ContainsNode(x) || LinearUnderALogarithm(inU, u) is not null) + return null; + return SolveAPowerTimesAnExponentialOfAQuadraticInALogarithm(inU, u)?.Substitute(u, linear).InnerSimplified; + } + /// /// Whether is A + B ln(c x^r) with A, B, /// c and r free of and the logarithm present: one diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index a41f92922..abcd8827b 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -569,6 +569,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => if ((answer = IndefiniteIntegralSolver.SolveAPowerTimesAPowerOfTheLogarithm(expr, x)) is { }) return answer; // And a half-odd power of it, onto the Gaussian's moments by t = sqrt(F). if ((answer = IndefiniteIntegralSolver.SolveAPowerTimesAHalfOddPowerOfTheLogarithm(expr, x)) is { }) return answer; + // And an exponential of a quadratic in it, onto the Gaussian by t = ln(c x^r). + if ((answer = IndefiniteIntegralSolver.SolveAPowerTimesAnExponentialOfAQuadraticInALogarithm(expr, x)) is { }) return answer; // A power of an exponential with a positive base is the exponential of the product, // exactly, and only that spelling is one the exponential rules read. if ((answer = IndefiniteIntegralSolver.SolveByFlatteningAPowerOfAnExponential(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/ExponentialOfAQuadraticInALogarithmTest.cs b/Sources/Tests/UnitTests/Calculus/ExponentialOfAQuadraticInALogarithmTest.cs new file mode 100644 index 000000000..1a1a302cc --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ExponentialOfAQuadraticInALogarithmTest.cs @@ -0,0 +1,80 @@ +// +// 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 +{ + /// + /// A power of x times an exponential of a quadratic in a logarithm, x^p G^(Q(ln(c x^r))), + /// onto the Gaussian by t = ln(c x^r): Rubi's 2.3, + /// F^(f (a + b ln(c (d + e x)^n))^2) (g + h x)^m, every one of which was left unevaluated. + /// https://github.com/asc-community/AngouriMath/issues/1501 + /// + [Trait("Area", "Calculus")] + public sealed class ExponentialOfAQuadraticInALogarithmTest + { + /// Past 1, where every logarithm here is real for the pins below. + private static readonly double[] Points = { 1.3, 1.9, 2.6, 3.4 }; + private static readonly (string Name, string Value)[] Pins = + { ("F", "2"), ("a", "1/2"), ("b", "2/3"), ("c", "5/4"), ("n", "3"), ("m", "1/2"), ("d", "1/2"), ("k", "2"), ("f", "3/4"), ("g", "2"), ("h", "1/3") }; + + private static Entity DifferentiatesBack(string integrand) + { + 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)); + Assert.True(difference / scale < 1e-9, $"d/dx of {integral} is {got} at x = {at}, where {integrand} is {want}"); + } + return integral; + } + + [Theory] + [InlineData("e^(ln(x)^2)")] + [InlineData("x*e^(ln(x)^2)")] + [InlineData("e^(-ln(x)^2)/x^2")] + [InlineData("2^(ln(x)^2 + ln(x))")] + [InlineData("x^m*e^(ln(x)^2)")] + public void OfTheLogarithmItself(string integrand) => DifferentiatesBack(integrand); + + [Theory] + [InlineData("F^(f*(a + b*ln(c*x^n))^2)")] + [InlineData("x^2*F^(f*(a + b*ln(c*x^n))^2)")] + [InlineData("F^(-f*(a + b*ln(c*x^n)^2))/x^3")] + [InlineData("x^m*F^(f*(a + b*ln(c*x^n))^2)")] + [InlineData("(1 + x)^2*F^(f*(a + b*ln(c*x^n))^2)")] + public void OfALogarithmOfAPower(string integrand) => DifferentiatesBack(integrand); + + /// + /// The logarithm of a power of a linear, under u = d + e x, with a constant multiple + /// of the linear or a polynomial beside it, as Rubi's 2.3 writes them. + /// + [Theory] + [InlineData("F^(f*(a + b*ln(c*(d + k*x)^n))^2)")] + [InlineData("(d*g + k*g*x)^m*F^(f*(a + b*ln(c*(d + k*x)^n))^2)")] + [InlineData("F^(f*(a + b*ln(c*(d + k*x)^n)^2))/(d*g + k*g*x)^2")] + [InlineData("(g + h*x)^3*F^(f*(a + b*ln(c*(d + k*x)^n))^2)")] + [InlineData("e^(ln((d + k*x)^n)^2)*(d + k*x)^m")] + public void OfALogarithmOfAPowerOfALinear(string integrand) => DifferentiatesBack(integrand); + + /// Beside 1/x it is the Gaussian in ln(x) alone. + [Fact] + public void BesideTheReciprocalItIsTheGaussianOfTheLogarithm() + => Assert.Contains(DifferentiatesBack("e^(ln(x)^2)/x").Nodes, node => node is Entity.Erfif); + } +}