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
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,20 @@ becomes a polynomial in `u` times exponentials and trigonometric functions, and
| `"(a+b*atan(c*x))^2/(1+i*c*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 731 characters |
| `"(a+b*atan(c*x))^3/(d+i*c*d*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 1,202 characters |

### A function of the hyperbolic tangent alone is integrated under `u = tanh`

**Answers where there were none.** `tanh(x)/sqrt(a + b tanh(x)^2 + c tanh(x)^4)` was declined, and so were 35 more of
Rubi's 6.3 and 6.4: the coth twins, `tanh/(a + b tanh^4)^k`, `sin(tanh(a + b x))^k` and `cos(coth(...))^k`,
and `(b coth(c + d x)^4)^(k/3)`. The parser writes `tanh(L)` as `(e^(2 L) - 1)/(e^(2 L) + 1)`, so nothing read them as
a function of the tangent; under `u = tanh(L)`, `dx = du/(b (1 - u^2))`, and the question in `u` is the one the
trigonometric counterparts are answered through. Only where the integrand is not rational in `u`: rational functions of
tanh keep the answers the exponential substitution gives them ([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sin(tanh(a+b*x))".ToEntity().Integrate("x")` | `integral(...)` | `Si` and `Ci` of `1 - tanh(a + b x)` and `1 + tanh(a + b x)` |
| `"tanh(x)/sqrt(a+b*tanh(x)^2+c*tanh(x)^4)".ToEntity().Integrate("x")` | `integral(...)` | a logarithm of the root, piecewise on the sign of `a + b + c` |

### A constant or an exponential evaluated at a higher precision than before has all of its digits

**More digits where there were a hundred.** `pi`, `e` and an exponential, evaluated once at one precision, went on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,105 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab
return ((constant * MathS.Pow(determinant, Number.Integer.Create(j + k + EInteger.One))).Simplify() * sum).InnerSimplified;
}

/// <summary>
/// A function of the hyperbolic tangent of a linear, under <c>u = tanh(a + b x)</c>: the parser
/// writes <c>tanh(L)</c> as <c>(W - 1)/(W + 1)</c> and <c>coth(L)</c> as <c>(W + 1)/(W - 1)</c>
/// with <c>W = e^(2 L)</c>, so an integrand built from those alone is <c>R(u)</c>, and
/// <c>dx = du/(b (1 - u^2))</c>. <c>tanh(x)/sqrt(a + b tanh(x)^2 + c tanh(x)^4)</c> is
/// <c>u/((1 - u^2) sqrt(a + b u^2 + c u^4))</c>, and <c>sin(tanh(x))</c> is
/// <c>sin(u)/(1 - u^2)</c>, onto the sine and cosine integrals. The trigonometric
/// counterparts are answered under <c>u = tan(x)</c>, which the substitution reads in the
/// tangent as a node; the hyperbolic functions arrive as exponentials, and nothing read them
/// as a tangent. Rubi's 6.3 and 6.4.
/// https://github.com/asc-community/AngouriMath/issues/233
/// </summary>
/// <remarks>
/// Only where <c>R</c> is not a rational function: those the exponential substitution answers.
/// Every exponential has to be a whole power of <c>W</c> inside one of the two quotients;
/// one anywhere else, an odd power of <c>e^L</c> from a <c>sinh</c> or <c>cosh</c> among
/// them, and the rule declines. <c>d/dx F(tanh(L)) = b (1 - tanh(L)^2) F'(tanh(L))</c>
/// wherever <c>F</c> is differentiable, so the answer written back holds where the integrand
/// is defined.
/// </remarks>
internal static Entity? SolveAFunctionOfTheHyperbolicTangent(Entity expr, Entity.Variable x, bool integrateByParts)
{
// W from the first tangent or cotangent the parser wrote: (e^E -+ 1)/(e^E +- 1).
Entity? exponent = null;
foreach (var node in expr.Nodes)
if (node is Divf(var top, var bottom) && ATangentsExponent(top, bottom) is { } found)
{
exponent = found.Exponent;
break;
}
if (exponent is null || !TreeAnalyzer.TryGetPolyLinear(exponent, x, out var twiceSlope, out var twiceIntercept) || TreeAnalyzer.IsZero(twiceSlope))
return null;
var u = Variable.CreateUnique(expr, "u_tanh");
var w = Variable.CreateUnique(expr, "w_tanh");
// Every exponential of x a whole power of W, or decline.
var declined = false;
var inW = expr.Replace(node =>
{
if (node is not Powf(var @base, var e) || @base != MathS.e || !e.ContainsNode(x))
return node;
if (!TreeAnalyzer.TryGetPolyLinear(e, x, out var slope, out var intercept)
|| Functions.PartialFractions.Bare((slope / twiceSlope).Simplify()).Evaled is not Number.Integer k
|| !k.EInteger.CanFitInInt32() || !VanishesIdentically(intercept - k * twiceIntercept))
{
declined = true;
return node;
}
return k == Number.Integer.One ? w : MathS.Pow(w, k);
});
if (declined)
return null;
// The two quotients as u and 1/u; W anywhere else declines.
var inU = inW.Replace(node => node is Divf(var top, var bottom) && AQuotientInW(top, bottom, w) is { } tangent
? (tangent ? u : 1 / u)
: node);
if (inU.ContainsNode(w) || inU.ContainsNode(x))
return null;
// A rational function of u is the exponential substitution's.
if (IsARationalFunction(inU, u))
return null;
// Free of x and small, simplified: `2 b/2` is otherwise left as written.
var slopeOfL = Functions.PartialFractions.Bare((twiceSlope / 2).Simplify());
var question = inU / (slopeOfL * (1 - MathS.Sqr(u)));
if (Integration.ComputeAsAQuestionOfItsOwn(question, u, integrateByParts) is not { } answer
|| answer.Nodes.Any(node => node is Integralf || node == MathS.NaN))
return null;
// a + b x from its coefficients: (2 (a + b x))/2 is left as written by the simplification.
var intercept0 = Functions.PartialFractions.Bare((twiceIntercept / 2).Simplify());
var linear = intercept0.Evaled is Number.Complex { IsZero: true } ? slopeOfL * x : intercept0 + slopeOfL * x;
return answer.Substitute(u, MathS.Hyperbolic.Tanh(linear));

// (W - 1)/(W + 1) is the tangent, true, and (W + 1)/(W - 1) the cotangent, false.
static bool? AQuotientInW(Entity top, Entity bottom, Variable w)
{
if (!TreeAnalyzer.TryGetPolyLinear(top, w, out var a1, out var b1) || !TreeAnalyzer.TryGetPolyLinear(bottom, w, out var a2, out var b2)
|| a1 != Number.Integer.One || a2 != Number.Integer.One)
return null;
if (b1 == Number.Integer.MinusOne && b2 == Number.Integer.One)
return true;
if (b1 == Number.Integer.One && b2 == Number.Integer.MinusOne)
return false;
return null;
}

static (Entity Exponent, bool Tangent)? ATangentsExponent(Entity top, Entity bottom)
{
// e^E - 1 over e^E + 1, as the parser writes them, or the other way up.
(Entity, int)? Read(Entity side) => side switch
{
Minusf(Powf(var e0, var power), Number.Integer one) when e0 == MathS.e && one == Number.Integer.One => (power, -1),
Sumf(Powf(var e0, var power), Number.Integer one) when e0 == MathS.e && one == Number.Integer.One => (power, 1),
_ => null
};
if (Read(top) is not var (p1, s1) || Read(bottom) is not var (p2, s2) || p1 != p2 || s1 == s2)
return null;
return (p1, s1 < 0);
}
}

/// <summary>
/// A quotient by the square of a base, by parts against the base's reciprocal, where the
/// base's derivative makes the rest exact: <c>N/(M v^2)</c> is <c>g v'/v^2</c> with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
if ((answer = IndefiniteIntegralSolver.SolveByTheMoebiusSubstitutionOfAQuotientOfLinears(expr, x)) is { }) return answer;
// And a power of x times the logarithm of A + B sqrt(Q), by parts, its derivative over the conjugate.
if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstALogarithmOfARadicalSum(expr, x)) is { }) return answer;
// A function of tanh(L) or coth(L) alone that is not rational in it, under u = tanh(L).
if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfTheHyperbolicTangent(expr, x, integrateByParts)) 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// 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>
/// A function of <c>tanh(a + b x)</c> or <c>coth(a + b x)</c> alone, not rational in it, under
/// <c>u = tanh(a + b x)</c>. The trigonometric counterparts were answered under <c>u = tan</c>;
/// the hyperbolic functions arrive as exponentials, and these timed out or were declined.
/// Rubi's 6.3 and 6.4.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AFunctionOfTheHyperbolicTangentIntegralTest
{
[Theory]
[InlineData("tanh(x)/sqrt(a + b*tanh(x)^2 + c*tanh(x)^4)")]
[InlineData("coth(x)/sqrt(a + b*coth(x)^2 + c*coth(x)^4)")]
[InlineData("tanh(x)/(a + b*tanh(x)^4)^(3/2)")]
[InlineData("sin(tanh(a + b*x))")]
[InlineData("cos(tanh(a + b*x))^3")]
[InlineData("sin(coth(a + b*x))")]
[InlineData("(b*coth(c + d*x)^4)^(1/3)")]
public void UnderTheTangent(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
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[] { -2.9, -0.7, 0.3, 0.8, 1.3, 2.1 })
{
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}");
}
}
}
Loading