diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index fdd6a2b3b..050721360 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -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
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index ed165e985..cc998dea0 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -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;
}
+ ///
+ /// A function of the hyperbolic tangent of a linear, under u = tanh(a + b x): the parser
+ /// writes tanh(L) as (W - 1)/(W + 1) and coth(L) as (W + 1)/(W - 1)
+ /// with W = e^(2 L), so an integrand built from those alone is R(u), and
+ /// dx = du/(b (1 - u^2)). tanh(x)/sqrt(a + b tanh(x)^2 + c tanh(x)^4) is
+ /// u/((1 - u^2) sqrt(a + b u^2 + c u^4)), and sin(tanh(x)) is
+ /// sin(u)/(1 - u^2), onto the sine and cosine integrals. The trigonometric
+ /// counterparts are answered under u = tan(x), 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
+ ///
+ ///
+ /// Only where R is not a rational function: those the exponential substitution answers.
+ /// Every exponential has to be a whole power of W inside one of the two quotients;
+ /// one anywhere else, an odd power of e^L from a sinh or cosh among
+ /// them, and the rule declines. d/dx F(tanh(L)) = b (1 - tanh(L)^2) F'(tanh(L))
+ /// wherever F is differentiable, so the answer written back holds where the integrand
+ /// is defined.
+ ///
+ 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);
+ }
+ }
+
///
/// A quotient by the square of a base, by parts against the base's reciprocal, where the
/// base's derivative makes the rest exact: N/(M v^2) is g v'/v^2 with
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index 22dc0dac3..278c548e5 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -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;
diff --git a/Sources/Tests/UnitTests/Calculus/AFunctionOfTheHyperbolicTangentIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AFunctionOfTheHyperbolicTangentIntegralTest.cs
new file mode 100644
index 000000000..0a9af10c9
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/AFunctionOfTheHyperbolicTangentIntegralTest.cs
@@ -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
+{
+ ///
+ /// A function of tanh(a + b x) or coth(a + b x) alone, not rational in it, under
+ /// u = tanh(a + b x). The trigonometric counterparts were answered under u = tan;
+ /// the hyperbolic functions arrive as exponentials, and these timed out or were declined.
+ /// Rubi's 6.3 and 6.4.
+ /// #233
+ ///
+ [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}");
+ }
+ }
+}