diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index b82a9959c..e317ff4bb 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -22156,17 +22156,35 @@ private static (Entity Constant, Entity Base, Entity ExponentInU, Variable U, En /// exponential of the product: (e^x)^(1/3) is e^(x/3) and (3^(3x))^(1/4) /// is 3^(3x/4), exactly, since the base is positive; and it is only in that /// spelling that the exponential rules read them. Timofeev's - /// (cos(x/2) + sin(x/2))/(e^x)^(1/3) and cos(3x/2)/(3^(3x))^(1/4). + /// (cos(x/2) + sin(x/2))/(e^x)^(1/3) and cos(3x/2)/(3^(3x))^(1/4). And a whole + /// power of a constant times one, (-e^(-b x))^2, as the constant's power times the + /// exponential of the multiple, whatever the constant is. /// internal static Entity? SolveByFlatteningAPowerOfAnExponential(Entity expr, Entity.Variable x, bool integrateByParts) { - var flattened = expr.Replace(node => - node is Powf(Powf(var @base, var inner), var outer) - && !@base.ContainsNode(x) && inner.ContainsNode(x) && !outer.ContainsNode(x) - && (@base == MathS.e || @base.Evaled is Number.Real { IsPositive: true }) - && outer.Evaled is Number.Real - ? MathS.Pow(@base, (outer * inner).InnerSimplified) - : node); + bool IsAnExponential(Entity @base, Entity inner) => + !@base.ContainsNode(x) && inner.ContainsNode(x) + && (@base == MathS.e || @base.Evaled is Number.Real { IsPositive: true }); + // A whole power of a constant times an exponential: (k E)^n is k^n E^n for every k and + // E when n is whole, so (-e^(-b x))^2 is e^(-2 b x). The product of two of them, which + // sinh(b x)^3 sinh(d x)^3 expands into, is read by nothing else. + Entity? Distributed(Entity left, Entity right, Number.Integer whole) => + (left, right) switch + { + (Powf(var b, var p), var k) when !k.ContainsNode(x) && IsAnExponential(b, p) => + MathS.Pow(k, whole) * MathS.Pow(b, (whole * p).InnerSimplified), + (var k, Powf(var b, var p)) when !k.ContainsNode(x) && IsAnExponential(b, p) => + MathS.Pow(k, whole) * MathS.Pow(b, (whole * p).InnerSimplified), + _ => null + }; + var flattened = expr.Replace(node => node switch + { + Powf(Powf(var @base, var inner), var outer) + when IsAnExponential(@base, inner) && !outer.ContainsNode(x) && outer.Evaled is Number.Real => + MathS.Pow(@base, (outer * inner).InnerSimplified), + Powf(Mulf(var left, var right), Number.Integer whole) => Distributed(left, right, whole) ?? node, + _ => node + }); // The same question in another spelling, asked as one of its own, so that the // closed rules that answer only at the top -- the exponential times a // trigonometric -- are consulted for it. diff --git a/Sources/Tests/UnitTests/Calculus/AWholePowerOfAMultipleOfAnExponentialIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AWholePowerOfAMultipleOfAnExponentialIntegralTest.cs new file mode 100644 index 000000000..f8b4d8a70 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AWholePowerOfAMultipleOfAnExponentialIntegralTest.cs @@ -0,0 +1,54 @@ +// +// 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 +{ + /// + /// A whole power of a constant times an exponential, (k e^u)^n = k^n e^(n u). The terms of + /// sinh(a + b x)^3 sinh(c + d x)^3, written in exponentials, are products of two of them, + /// (-e^(-b x))^2 (-e^(-d x))^2, which nothing else read: 2.5.0 answered the product, and + /// 2.6.0's development had stopped. Rubi's 6.7.1. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AWholePowerOfAMultipleOfAnExponentialIntegralTest + { + [Theory] + [InlineData("(-e^(-b*x))^2*(-e^(-d*x))^2")] + [InlineData("(-e^(-b*x))^2")] + [InlineData("(2*e^x)^3")] + [InlineData("sinh(b*x)^3*sinh(d*x)^3")] + [InlineData("sinh(a + b*x)^3*sinh(c + d*x)^3")] + public void AsAnExponentialOfTheMultiple(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.3); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -1.2, -0.7, 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}"); + } + } +}