Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2666,6 +2666,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24028,6 +24028,105 @@ private static bool IsARationalFunctionOfExponentials(Entity expr, Entity.Variab
return Integration.ComputeAsAQuestionOfItsOwn(inU, u, integrateByParts)?.Substitute(u, polynomial);
}

/// <summary>
/// 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 <c>u = e^L + s e^(-L)</c>, twice the cosine or the
/// sine, <c>e^(n sinh(L)) cosh(L)</c> is <c>e^(n u/2)/2</c>, <c>e^(n cosh(L)) tanh(L)</c> is
/// <c>e^(n u/2)/u</c>, onto the exponential integral, and <c>e^(n sinh(L)) sinh(2L)</c> is
/// <c>u e^(n u/2)/2</c>. The substitution answers the trigonometric ones written as nodes --
/// <c>e^(n cos(x)) tan(x)</c> is <c>-Ei(n cos(x))</c> -- 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
/// </summary>
/// <remarks>
/// Every exponential of the variable but the outer one is a whole power of <c>w = e^L</c>, with
/// <c>L</c> the linear their exponents are whole multiples of, compared as polynomials and not
/// as written; the quotient is written in <c>u = w + s/w</c> by undetermined coefficients and a
/// check at points, or the rule declines.
/// </remarks>
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, ERational>();
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));
}

/// <summary>
/// A factor of <paramref name="expr"/>, read through products and quotients, that is a
/// constant to a polynomial in <paramref name="x"/> of degree two or more, or <see langword="null"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// An exponential of a hyperbolic sine or cosine of a linear beside a function of it over its
/// derivative, under <c>u</c> twice that sine or cosine: <c>e^(n cosh(a + b x)) tanh(a + b x)</c>
/// is <c>Ei(n cosh(a + b x))/b</c>, as <c>e^(n cos(x)) tan(x)</c> is <c>-Ei(n cos(x))</c>, and was
/// declined, the hyperbolic functions arriving as exponentials. Rubi's 6.7.1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[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}");
}
}
}
Loading