From ada352048332ff724883c3a115af5d0e71393088 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 04:32:12 +0000 Subject: [PATCH] An exponential of a hyperbolic sine or cosine beside a function of it over its derivative is integrated e^(n cosh(a + b x)) tanh(a + b x) is Ei(n cosh(a + b x))/b, as e^(n cos(x)) tan(x) is -Ei(n cos(x)), and was declined: a hyperbolic function arrives as exponentials, and the substitution that reads the sine or cosine as a node had none to read. In w = e^L the rest over the derivative of the outer exponent's sine or cosine is asked as a rational function of u = w + s/w, twice that sine or cosine, and where it is one, integrated in u. Rubi's 6.7.1. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 17 ++++ .../Integration/IndefiniteIntegralSolver.cs | 99 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 3 + .../ExponentialOfAHyperbolicIntegralTest.cs | 53 ++++++++++ 4 files changed, 172 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/ExponentialOfAHyperbolicIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e9ae2c6a1..050953823 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -2385,6 +2385,23 @@ with `int F` the next power over `p^2`. Rubi's 6.1.1, 6.2.1, 6.5.1 and 6.6.1 | `"x/sech(x)^(7/2)-5/21*x*sqrt(sech(x))".Integrate("x")` | left unevaluated | the antiderivative, over two reduction steps | | `"x/csch(x)^(3/2)+1/3*x*sqrt(csch(x))".Integrate("x")` | left unevaluated | the antiderivative | +### An exponential of a hyperbolic sine or cosine beside a function of it over its derivative is integrated + +**Answers where there were none.** `e^(n cosh(a + b x)) tanh(a + b x)` is `Ei(n cosh(a + b x))/b`, as +`e^(n cos(x)) tan(x)` is `-Ei(n cos(x))`, which was answered; the hyperbolic one was declined, after +seconds, and so were `e^(n sinh(a + b x)) sinh(2 (a + b x))` and the same with half the argument in the +exponent, Rubi's 6.7.1. A hyperbolic function arrives as exponentials, and the substitution that reads +the sine or cosine as a node had none to read. Written in `w = e^L`, the integrand beside the outer +exponential over the derivative of its sine or cosine is now asked as a rational function of +`u = w + s/w`, twice that sine or cosine; where it is one, it is integrated in `u` and written back +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"exp(n*cosh(a + b*x))*tanh(a + b*x)".ToEntity().Integrate("x")` | `integral(...)` | `Ei(n cosh(a + b x))/b`, written as exponentials | +| `"exp(n*sinh(a + b*x))*sinh(2*(a + b*x))".ToEntity().Integrate("x")` | `integral(...)` | elementary in `sinh(a + b x)` and its exponential | +| `"exp(n*cosh(1/2*(a + b*x)))*sinh(a + b*x)".ToEntity().Integrate("x")` | `integral(...)` | the same in `cosh((a + b x)/2)` | + ### A hyperbolic function of a logarithm is integrated, the exponent folded structurally `tanh(ln(x))` was left as written. The library spells `tanh(y)` with `e^(2y)`, so a hyperbolic diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 9965d1608..a35081f6d 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -23245,6 +23245,105 @@ private static bool IsARationalFunctionOfExponentials(Entity expr, Entity.Variab return Integration.ComputeAsAQuestionOfItsOwn(inU, u, integrateByParts)?.Substitute(u, polynomial); } + /// + /// An exponential of a multiple of a hyperbolic sine or cosine of a linear, beside a function + /// of the exponentials of the linear whose quotient by the derivative of that sine or cosine + /// is a rational function of it: under u = e^L + s e^(-L), twice the cosine or the + /// sine, e^(n sinh(L)) cosh(L) is e^(n u/2)/2, e^(n cosh(L)) tanh(L) is + /// e^(n u/2)/u, onto the exponential integral, and e^(n sinh(L)) sinh(2L) is + /// u e^(n u/2)/2. The substitution answers the trigonometric ones written as nodes -- + /// e^(n cos(x)) tan(x) is -Ei(n cos(x)) -- while a hyperbolic function arrives as + /// exponentials, and no rule read the sine or cosine in them. Rubi's 6.7.1. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + /// + /// Every exponential of the variable but the outer one is a whole power of w = e^L, with + /// L the linear their exponents are whole multiples of, compared as polynomials and not + /// as written; the quotient is written in u = w + s/w by undetermined coefficients and a + /// check at points, or the rule declines. + /// + internal static Entity? SolveAnExponentialOfAHyperbolicFunctionBesideItsDerivative(Entity expr, Entity.Variable x, bool integrateByParts) + { + bool IsAnExponential(Entity node) => node is Powf(var b, var p) && !b.ContainsNode(x) && p.ContainsNode(x); + // The outer exponential, above the bar, with exponentials of the variable in its exponent. + Powf? outer = null; + Entity rest = Number.Integer.One; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (!underneath && outer is null && factor is Powf(var @base, var exponent) && @base == MathS.e && exponent.Nodes.Any(IsAnExponential)) + { + outer = (Powf)factor; + continue; + } + rest = underneath ? rest / factor : rest * factor; + } + if (outer is null) + return null; + // The exponentials inside it and beside it, each `e^(k L)` with one linear L. + var multiples = new Dictionary(); + Entity? unitSlope = null, unitOffset = null, unit = null; + foreach (var node in expr.Nodes) + { + if (node == outer || !IsAnExponential(node) || multiples.ContainsKey(node)) + continue; + if (node is not Powf(var @base, var exponent) || @base != MathS.e + || !TreeAnalyzer.TryGetPolyLinear(exponent, x, out var slope, out var offset) || TreeAnalyzer.IsZero(slope)) + return null; + if (unit is null) + { + (unit, unitSlope, unitOffset) = (exponent, slope, offset); + multiples[node] = ERational.One; + continue; + } + if (Functions.PartialFractions.Bare((slope / unitSlope!).Simplify()).Evaled is not Number.Rational ratio || ratio.ERational.IsZero + || !IsTheZeroPolynomial((offset - ratio * unitOffset!).InnerSimplified)) + return null; + multiples[node] = ratio.ERational; + } + if (unit is null || unitSlope is null) + return null; + var numerators = EInteger.Zero; + var denominators = EInteger.One; + foreach (var multiple in multiples.Values) + { + numerators = numerators.Gcd(multiple.Numerator.Abs()); + denominators = denominators.Multiply(multiple.Denominator).Divide(denominators.Gcd(multiple.Denominator)); + } + var step = ERational.Create(numerators, denominators); + if (multiples.Values.Any(multiple => multiple.Divide(step).ToLowestTerms().Numerator.Abs().CompareTo(EInteger.FromInt32(8)) > 0)) + return null; + var w = Variable.CreateUnique(expr, "w_hyp"); + Entity InW(Entity e) => e.Replace(node => + node != outer && multiples.TryGetValue(node, out var multiple) ? MathS.Pow(w, Number.Integer.Create(multiple.Divide(step).ToLowestTerms().Numerator)) : node); + // The exponent `c (w + s/w) + d`: a multiple of the sine or the cosine in w and nothing else. + if (!TreeAnalyzer.TryGetPolynomial(InW(outer.Exponent), w, out var exponentRead) + || exponentRead.Keys.Any(degree => degree.Abs().CompareTo(EInteger.One) > 0) + || exponentRead.Values.Any(coefficient => coefficient.ContainsNode(x)) + || !exponentRead.TryGetValue(EInteger.One, out var c) || !exponentRead.TryGetValue(EInteger.FromInt32(-1), out var cBelow)) + return null; + int sign; + if (IsTheZeroPolynomial((cBelow - c).InnerSimplified)) + sign = 1; + else if (IsTheZeroPolynomial((cBelow + c).InnerSimplified)) + sign = -1; + else + return null; + var d = exponentRead.TryGetValue(EInteger.Zero, out var constantTerm) ? constantTerm : Number.Integer.Zero; + // du = slope (w - s/w) dx, with u = w + s/w and the slope that of the unit, step L. + var restInW = InW(rest); + if (restInW.ContainsNode(x)) + return null; + var slopeOfW = (Number.Rational.Create(step) * unitSlope).InnerSimplified; + if (!TryWriteInTheReciprocalVariable(restInW / (slopeOfW * (w - Number.Integer.Create(sign) / w)), w, sign, out var u, out var inU)) + return null; + var question = (MathS.Pow(MathS.e, c * u + d) * inU).InnerSimplified; + if (Integration.ComputeAsAQuestionOfItsOwn(question, u, integrateByParts) is not { } answer || answer.Nodes.Any(node => node is Integralf)) + return null; + var exponentOfW = (Number.Rational.Create(step) * unit).InnerSimplified; + return Functions.PartialFractions.Bare(answer.InnerSimplified) + .Substitute(u, MathS.Pow(MathS.e, exponentOfW) + Number.Integer.Create(sign) * MathS.Pow(MathS.e, -exponentOfW)); + } + /// /// A factor of , read through products and quotients, that is a /// constant to a polynomial in of degree two or more, or . diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 8cae2ee5f..adc6fb52e 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -641,6 +641,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // An exponential of a polynomial beside the polynomial's derivative, under u = P, // which the substitution search does not reach: it writes the exponential apart. if ((answer = IndefiniteIntegralSolver.SolveByTheExponentAsTheVariable(expr, x, integrateByParts)) is { }) return answer; + // And an exponential of a hyperbolic sine or cosine of a linear beside a function of it + // over its derivative, under u = twice that sine or cosine, which arrive as exponentials. + if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfAHyperbolicFunctionBesideItsDerivative(expr, x, integrateByParts)) is { }) return answer; // `A + i A tan(z)` is `A e^(i z)/cos(z)`, which beside a polynomial is a shape the // closed rules answer, where the imaginary unit in the coefficient is read by none. if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginaryTangentAsAnExponential(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/ExponentialOfAHyperbolicIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ExponentialOfAHyperbolicIntegralTest.cs new file mode 100644 index 000000000..183817d9b --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ExponentialOfAHyperbolicIntegralTest.cs @@ -0,0 +1,53 @@ +// +// 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 +{ + /// + /// An exponential of a hyperbolic sine or cosine of a linear beside a function of it over its + /// derivative, under u twice that sine or cosine: e^(n cosh(a + b x)) tanh(a + b x) + /// is Ei(n cosh(a + b x))/b, as e^(n cos(x)) tan(x) is -Ei(n cos(x)), and was + /// declined, the hyperbolic functions arriving as exponentials. Rubi's 6.7.1. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ExponentialOfAHyperbolicIntegralTest + { + [Theory] + [InlineData("exp(n*cosh(a + b*x))*tanh(a + b*x)")] + [InlineData("exp(n*sinh(a + b*x))*coth(a + b*x)")] + [InlineData("exp(n*sinh(a + b*x))*sinh(2*(a + b*x))")] + [InlineData("exp(n*cosh(1/2*(a + b*x)))*sinh(a + b*x)")] + [InlineData("exp(n*sinh(a*c + b*c*x))*cosh(c*(a + b*x))")] + public void UnderTwiceTheSineOrCosine(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Assert.DoesNotContain("NaN", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 0.3).Substitute("b", 0.7).Substitute("c", 1.1).Substitute("n", 0.6); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -1.7, -0.9, 0.3, 0.8, 1.6, 2.9 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + if (want.IsNaN || Math.Abs((double)want.ImaginaryPart) > 1e-12) + continue; + compared++; + var got = derivative.Substitute("x", at).EvalNumerical(); + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)), + $"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}"); + } + } +}