Skip to content

Commit aba01fb

Browse files
feat: add method TanhFunction (#563)
1 parent ef3f6e6 commit aba01fb

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using Algorithms.Numeric;
2+
using NUnit.Framework;
3+
using System;
4+
5+
namespace Algorithms.Tests.Numeric;
6+
7+
[TestFixture]
8+
public static class TanhTests
9+
{
10+
// Tolerance for floating-point comparisons
11+
private const double Tolerance = 1e-9;
12+
13+
// --- SCALAR TESTS (Tanh.Compute(double)) ---
14+
15+
/// <summary>
16+
/// Tests Tanh function for specific values, including zero and symmetric positive/negative inputs.
17+
/// </summary>
18+
[TestCase(0.0, 0.0)]
19+
[TestCase(1.0, 0.7615941559557649)]
20+
[TestCase(-1.0, -0.7615941559557649)]
21+
[TestCase(5.0, 0.999909204262595)]
22+
[TestCase(-5.0, -0.999909204262595)]
23+
public static void TanhFunction_Scalar_ReturnsCorrectValue(double input, double expected)
24+
{
25+
var result = Tanh.Compute(input);
26+
Assert.That(result, Is.EqualTo(expected).Within(Tolerance));
27+
}
28+
29+
/// <summary>
30+
/// Ensures the Tanh output approaches 1.0 for positive infinity and -1.0 for negative infinity.
31+
/// </summary>
32+
[Test]
33+
public static void TanhFunction_Scalar_ApproachesLimits()
34+
{
35+
Assert.That(Tanh.Compute(double.PositiveInfinity), Is.EqualTo(1.0).Within(Tolerance));
36+
Assert.That(Tanh.Compute(double.NegativeInfinity), Is.EqualTo(-1.0).Within(Tolerance));
37+
Assert.That(Tanh.Compute(double.NaN), Is.NaN);
38+
}
39+
40+
/// <summary>
41+
/// Checks that the Tanh result is always bounded between -1.0 and 1.0.
42+
/// </summary>
43+
[TestCase(100.0)]
44+
[TestCase(-100.0)]
45+
[TestCase(0.0001)]
46+
public static void TanhFunction_Scalar_ResultIsBounded(double input)
47+
{
48+
var result = Tanh.Compute(input);
49+
Assert.That(result, Is.GreaterThanOrEqualTo(-1.0));
50+
Assert.That(result, Is.LessThanOrEqualTo(1.0));
51+
}
52+
53+
// --- VECTOR TESTS (Tanh.Compute(double[])) ---
54+
55+
/// <summary>
56+
/// Tests the element-wise computation for a vector input.
57+
/// </summary>
58+
[Test]
59+
public static void TanhFunction_Vector_ReturnsCorrectValues()
60+
{
61+
// Input: [0.0, 1.0, -2.0]
62+
var input = new[] { 0.0, 1.0, -2.0 };
63+
// Expected: [Tanh(0.0), Tanh(1.0), Tanh(-2.0)]
64+
var expected = new[] { 0.0, 0.7615941559557649, -0.9640275800758169 };
65+
66+
var result = Tanh.Compute(input);
67+
68+
// Assert deep equality within tolerance
69+
Assert.That(result, Is.EqualTo(expected).Within(Tolerance));
70+
}
71+
72+
/// <summary>
73+
/// Tests vector handling of edge cases like infinity and NaN.
74+
/// </summary>
75+
[Test]
76+
public static void TanhFunction_Vector_HandlesLimitsAndNaN()
77+
{
78+
var input = new[] { double.PositiveInfinity, 0.0, double.NaN };
79+
var expected = new[] { 1.0, 0.0, double.NaN };
80+
81+
var result = Tanh.Compute(input);
82+
83+
Assert.That(result.Length, Is.EqualTo(expected.Length));
84+
Assert.That(result[0], Is.EqualTo(expected[0]).Within(Tolerance)); // Pos Inf -> 1.0
85+
Assert.That(result[2], Is.NaN); // NaN
86+
}
87+
88+
// --- EXCEPTION TESTS ---
89+
90+
/// <summary>
91+
/// Checks if the vector computation throws ArgumentNullException for null input.
92+
/// </summary>
93+
[Test]
94+
public static void TanhFunction_Vector_ThrowsOnNullInput()
95+
{
96+
double[]? input = null;
97+
Assert.Throws<ArgumentNullException>(() => Tanh.Compute(input!));
98+
}
99+
100+
/// <summary>
101+
/// Checks if the vector computation throws ArgumentException for an empty input array.
102+
/// </summary>
103+
[Test]
104+
public static void TanhFunction_Vector_ThrowsOnEmptyInput()
105+
{
106+
var input = Array.Empty<double>();
107+
Assert.Throws<ArgumentException>(() => Tanh.Compute(input));
108+
}
109+
}

Algorithms/Numeric/Tanh.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
namespace Algorithms.Numeric;
2+
3+
/// <summary>
4+
/// Implementation of the Hyperbolic Tangent (Tanh) function.
5+
/// Tanh is an activation function that takes a real number as input and squashes
6+
/// the output to a range between -1 and 1.
7+
/// It is defined as: tanh(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x)).
8+
/// https://en.wikipedia.org/wiki/Hyperbolic_function#Hyperbolic_tangent.
9+
/// </summary>
10+
public static class Tanh
11+
{
12+
/// <summary>
13+
/// Compute the Hyperbolic Tangent (Tanh) function for a single value.
14+
/// The Math.Tanh() method is used for efficient and accurate computation.
15+
/// </summary>
16+
/// <param name="input">The input real number.</param>
17+
/// <returns>The output real number in the range [-1, 1].</returns>
18+
public static double Compute(double input)
19+
{
20+
// For a single double, we can directly use the optimized Math.Tanh method.
21+
return Math.Tanh(input);
22+
}
23+
24+
/// <summary>
25+
/// Compute the Hyperbolic Tangent (Tanh) function element-wise for a vector.
26+
/// </summary>
27+
/// <param name="input">The input vector of real numbers.</param>
28+
/// <returns>The output vector of real numbers, where each element is in the range [-1, 1].</returns>
29+
public static double[] Compute(double[] input)
30+
{
31+
if (input is null)
32+
{
33+
throw new ArgumentNullException(nameof(input));
34+
}
35+
36+
if (input.Length == 0)
37+
{
38+
throw new ArgumentException("Array is empty.");
39+
}
40+
41+
var outputVector = new double[input.Length];
42+
43+
for (var index = 0; index < input.Length; index++)
44+
{
45+
// Apply Tanh to each element using the optimized Math.Tanh method.
46+
outputVector[index] = Math.Tanh(input[index]);
47+
}
48+
49+
return outputVector;
50+
}
51+
}

0 commit comments

Comments
 (0)