[orx-math] Add complex number implementation and associated test cases

This commit is contained in:
Edwin Jakobs
2025-08-15 20:46:04 +02:00
parent 4293d852fe
commit 8daef56841
10 changed files with 1050 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
package org.openrndr.extra.math.complex
import kotlin.math.PI
import kotlin.math.ln
import kotlin.math.sqrt
import kotlin.test.Test
import kotlin.test.assertEquals
class ComplexAcoshTest {
@Test
fun testAcoshOfOne() {
val z = Complex(1.0, 0.0)
val result = acosh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testAcoshOfZero() {
val z = Complex(0.0, 0.0)
val result = acosh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(PI/2, result.imaginary, 1e-10)
}
@Test
fun testAcoshOfMinusOne() {
val z = Complex(-1.0, 0.0)
val result = acosh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(PI, result.imaginary, 1e-10)
}
@Test
fun testAcoshOfTwo() {
val z = Complex(2.0, 0.0)
val result = acosh(z)
assertEquals(ln(2.0 + sqrt(3.0)), result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testAcoshOfImaginaryUnit() {
val z = Complex(0.0, 1.0)
val result = acosh(z)
assertEquals(0.8813735870195429, result.real, 1e-10)
assertEquals(PI/2, result.imaginary, 1e-10)
}
@Test
fun testAcoshOfComplexNumber() {
val z = Complex(1.0, 1.0)
val result = acosh(z)
// Expected values calculated using external reference
assertEquals(1.061275061, result.real, 1e-8)
assertEquals(0.904556894, result.imaginary, 1e-8)
}
}

View File

@@ -0,0 +1,49 @@
package org.openrndr.extra.math.complex
import kotlin.math.PI
import kotlin.test.Test
import kotlin.test.assertEquals
class ComplexAsinTest {
@Test
fun testAsinOfZero() {
val z = Complex(0.0, 0.0)
val result = asin(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testAsinOfOne() {
val z = Complex(1.0, 0.0)
val result = asin(z)
assertEquals(PI/2, result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testAsinOfMinusOne() {
val z = Complex(-1.0, 0.0)
val result = asin(z)
assertEquals(-PI/2, result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testAsinOfImaginaryUnit() {
val z = Complex(0.0, 1.0)
val result = asin(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(0.881373587, result.imaginary, 1e-8) // approximately ln(1 + sqrt(2))
}
@Test
fun testAsinOfComplexNumber() {
val z = Complex(1.0, 1.0)
val result = asin(z)
// Expected values calculated using external reference
assertEquals(0.666239432, result.real, 1e-8)
assertEquals(1.061275061, result.imaginary, 1e-8)
}
}

View File

@@ -0,0 +1,61 @@
package org.openrndr.extra.math.complex
import kotlin.math.PI
import kotlin.math.ln
import kotlin.math.sqrt
import kotlin.test.Test
import kotlin.test.assertEquals
class ComplexAsinhTest {
@Test
fun testAsinhOfZero() {
val z = Complex(0.0, 0.0)
val result = asinh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testAsinhOfOne() {
val z = Complex(1.0, 0.0)
val result = asinh(z)
// Expected value is ln(1 + sqrt(2))
assertEquals(ln(1.0 + sqrt(2.0)), result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testAsinhOfMinusOne() {
val z = Complex(-1.0, 0.0)
val result = asinh(z)
// Expected value is -ln(1 + sqrt(2))
assertEquals(-ln(1.0 + sqrt(2.0)), result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testAsinhOfImaginaryUnit() {
val z = Complex(0.0, 1.0)
val result = asinh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(PI/2, result.imaginary, 1e-10)
}
@Test
fun testAsinhOfNegativeImaginaryUnit() {
val z = Complex(0.0, -1.0)
val result = asinh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(-PI/2, result.imaginary, 1e-10)
}
@Test
fun testAsinhOfComplexNumber() {
val z = Complex(1.0, 1.0)
val result = asinh(z)
// Expected values calculated using external reference
assertEquals(1.061275061, result.real, 1e-8)
assertEquals(0.666239432, result.imaginary, 1e-8)
}
}

View File

@@ -0,0 +1,74 @@
package org.openrndr.extra.math.complex
import kotlin.math.PI
import kotlin.test.Test
import kotlin.test.assertEquals
class ComplexAtanhTest {
@Test
fun testAtanhOfZero() {
val z = Complex(0.0, 0.0)
val result = atanh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testAtanhOfHalf() {
val z = Complex(0.5, 0.0)
val result = atanh(z)
// atanh(0.5) = 0.5493061443340548
assertEquals(0.5493061443340548, result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testAtanhOfImaginaryUnit() {
val z = Complex(0.0, 1.0)
val result = atanh(z)
assertEquals(0.0, result.real, 1e-10)
// atanh(i) = i*π/2
assertEquals(PI/2, result.imaginary, 1e-10)
}
@Test
fun testAtanhOfNegativeImaginaryUnit() {
val z = Complex(0.0, -1.0)
val result = atanh(z)
assertEquals(0.0, result.real, 1e-10)
// atanh(-i) = -i*π/2
assertEquals(-PI/2, result.imaginary, 1e-10)
}
@Test
fun testAtanhOfComplexNumber() {
val z = Complex(0.5, 0.5)
val result = atanh(z)
// Calculate expected values using the formula: atanh(z) = 0.5 * ln((1+z)/(1-z))
val one = Complex(1.0, 0.0)
val numerator = one + z
val denominator = one - z
val fraction = numerator / denominator
val expected = ln(fraction) * Complex(0.5, 0.0)
assertEquals(expected.real, result.real, 1e-10)
assertEquals(expected.imaginary, result.imaginary, 1e-10)
}
@Test
fun testAtanhIdentity() {
// Test the identity: tanh(atanh(z)) = z for |z| < 1
val z = Complex(0.3, 0.4)
// Calculate atanh(z)
val atanhZ = atanh(z)
// Calculate tanh(atanh(z))
val result = tanh(atanhZ)
assertEquals(z.real, result.real, 1e-10)
assertEquals(z.imaginary, result.imaginary, 1e-10)
}
}

View File

@@ -0,0 +1,51 @@
package org.openrndr.extra.math.complex
import kotlin.math.PI
import kotlin.test.Test
import kotlin.test.assertEquals
class ComplexCoshTest {
@Test
fun testCoshOfZero() {
val z = Complex(0.0, 0.0)
val result = cosh(z)
assertEquals(1.0, result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testCoshOfOne() {
val z = Complex(1.0, 0.0)
val result = cosh(z)
assertEquals(kotlin.math.cosh(1.0), result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testCoshOfImaginaryUnit() {
val z = Complex(0.0, 1.0)
val result = cosh(z)
assertEquals(kotlin.math.cos(1.0), result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testCoshOfImaginaryPi() {
val z = Complex(0.0, PI)
val result = cosh(z)
assertEquals(kotlin.math.cos(PI), result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testCoshOfComplexNumber() {
val z = Complex(1.0, 1.0)
val result = cosh(z)
// Expected values calculated using the formula: cosh(1+i) = cosh(1)cos(1) + i·sinh(1)sin(1)
val expectedReal = kotlin.math.cosh(1.0) * kotlin.math.cos(1.0)
val expectedImaginary = kotlin.math.sinh(1.0) * kotlin.math.sin(1.0)
assertEquals(expectedReal, result.real, 1e-10)
assertEquals(expectedImaginary, result.imaginary, 1e-10)
}
}

View File

@@ -0,0 +1,95 @@
package org.openrndr.extra.math.complex
import kotlin.math.ln
import kotlin.test.Test
import kotlin.test.assertTrue
class ComplexLogTest {
// Small epsilon for floating-point comparisons
private val e = 1E-6
/**
* Tests the logarithm function with arbitrary base for complex numbers.
*
* This test verifies that the logarithm of specific complex numbers
* with different bases produces results that match the expected values
* within an acceptable error tolerance.
*/
@Test
fun testLog() {
// Test case 1: log_10(10) should be 1
val z1 = Complex(10.0, 0.0)
val result1 = log(z1, 10.0)
assertTrue(result1.real in 1.0 - e..1.0 + e)
assertTrue(result1.imaginary in 0.0 - e..0.0 + e)
// Test case 2: log_2(8) should be 3
val z2 = Complex(8.0, 0.0)
val result2 = log(z2, 2.0)
assertTrue(result2.real in 3.0 - e..3.0 + e)
assertTrue(result2.imaginary in 0.0 - e..0.0 + e)
// Test case 3: log_e(e) should be 1
val z3 = Complex(kotlin.math.E, 0.0)
val result3 = log(z3, kotlin.math.E)
assertTrue(result3.real in 1.0 - e..1.0 + e)
assertTrue(result3.imaginary in 0.0 - e..0.0 + e)
// Test case 4: log_10(i) should be ln(i)/ln(10)
val z4 = Complex(0.0, 1.0)
val result4 = log(z4, 10.0)
// ln(i) = ln(e^(i*π/2)) = i*π/2
val expectedReal4 = 0.0
val expectedImag4 = kotlin.math.PI / (2 * ln(10.0))
assertTrue(result4.real in expectedReal4 - e..expectedReal4 + e)
assertTrue(result4.imaginary in expectedImag4 - e..expectedImag4 + e)
// Test case 5: log_2(-1) should be ln(-1)/ln(2) = i*π/ln(2)
val z5 = Complex(-1.0, 0.0)
val result5 = log(z5, 2.0)
val expectedReal5 = 0.0
val expectedImag5 = kotlin.math.PI / ln(2.0)
assertTrue(result5.real in expectedReal5 - e..expectedReal5 + e)
assertTrue(result5.imaginary in expectedImag5 - e..expectedImag5 + e)
}
/**
* Tests the logarithm function with complex base for complex numbers.
*
* This test verifies that the logarithm of specific complex numbers
* with different complex bases produces results that match the expected values
* within an acceptable error tolerance.
*/
@Test
fun testLogWithComplexBase() {
// Test case 1: log_i(i) should be 1
val z1 = Complex(0.0, 1.0)
val base1 = Complex(0.0, 1.0)
val result1 = log(z1, base1)
assertTrue(result1.real in 1.0 - e..1.0 + e)
assertTrue(result1.imaginary in 0.0 - e..0.0 + e)
// Test case 2: log_(2+3i)(4+5i)
val z2 = Complex(4.0, 5.0)
val base2 = Complex(2.0, 3.0)
val result2 = log(z2, base2)
// Expected result is ln(4+5i) / ln(2+3i)
val expectedResult2 = ln(z2) / ln(base2)
assertTrue(result2.real in expectedResult2.real - e..expectedResult2.real + e)
assertTrue(result2.imaginary in expectedResult2.imaginary - e..expectedResult2.imaginary + e)
// Test case 3: log_(1+0i)(e+0i) should be 1
val z3 = Complex(kotlin.math.E, 0.0)
val base3 = Complex(kotlin.math.E, 0.0)
val result3 = log(z3, base3)
assertTrue(result3.real in 1.0 - e..1.0 + e)
assertTrue(result3.imaginary in 0.0 - e..0.0 + e)
// Test case 4: log_(2+0i)(8+0i) should be 3
val z4 = Complex(8.0, 0.0)
val base4 = Complex(2.0, 0.0)
val result4 = log(z4, base4)
assertTrue(result4.real in 3.0 - e..3.0 + e)
assertTrue(result4.imaginary in 0.0 - e..0.0 + e)
}
}

View File

@@ -0,0 +1,51 @@
package org.openrndr.extra.math.complex
import kotlin.math.PI
import kotlin.test.Test
import kotlin.test.assertEquals
class ComplexSinhTest {
@Test
fun testSinhOfZero() {
val z = Complex(0.0, 0.0)
val result = sinh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testSinhOfOne() {
val z = Complex(1.0, 0.0)
val result = sinh(z)
assertEquals(kotlin.math.sinh(1.0), result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testSinhOfImaginaryUnit() {
val z = Complex(0.0, 1.0)
val result = sinh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(kotlin.math.sin(1.0), result.imaginary, 1e-10)
}
@Test
fun testSinhOfImaginaryPi() {
val z = Complex(0.0, PI)
val result = sinh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(kotlin.math.sin(PI), result.imaginary, 1e-10)
}
@Test
fun testSinhOfComplexNumber() {
val z = Complex(1.0, 1.0)
val result = sinh(z)
// Expected values calculated using the formula: sinh(1+i) = sinh(1)cos(1) + i·cosh(1)sin(1)
val expectedReal = kotlin.math.sinh(1.0) * kotlin.math.cos(1.0)
val expectedImaginary = kotlin.math.cosh(1.0) * kotlin.math.sin(1.0)
assertEquals(expectedReal, result.real, 1e-10)
assertEquals(expectedImaginary, result.imaginary, 1e-10)
}
}

View File

@@ -0,0 +1,71 @@
package org.openrndr.extra.math.complex
import kotlin.math.PI
import kotlin.test.Test
import kotlin.test.assertEquals
class ComplexTanhTest {
@Test
fun testTanhOfZero() {
val z = Complex(0.0, 0.0)
val result = tanh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testTanhOfOne() {
val z = Complex(1.0, 0.0)
val result = tanh(z)
assertEquals(kotlin.math.tanh(1.0), result.real, 1e-10)
assertEquals(0.0, result.imaginary, 1e-10)
}
@Test
fun testTanhOfImaginaryUnit() {
val z = Complex(0.0, 1.0)
val result = tanh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(kotlin.math.tan(1.0), result.imaginary, 1e-10)
}
@Test
fun testTanhOfImaginaryPi() {
val z = Complex(0.0, PI)
val result = tanh(z)
assertEquals(0.0, result.real, 1e-10)
assertEquals(kotlin.math.tan(PI), result.imaginary, 1e-10)
}
@Test
fun testTanhOfComplexNumber() {
val z = Complex(1.0, 1.0)
val result = tanh(z)
// Calculate expected values using the formula: tanh(z) = sinh(z) / cosh(z)
val sinhZ = sinh(z)
val coshZ = cosh(z)
val expected = sinhZ / coshZ
assertEquals(expected.real, result.real, 1e-10)
assertEquals(expected.imaginary, result.imaginary, 1e-10)
}
@Test
fun testTanhIdentity() {
// Test the identity: tanh(z) = sinh(z) / cosh(z)
val z = Complex(2.0, 3.0)
// Calculate using our tanh implementation
val result = tanh(z)
// Calculate using the identity
val sinhZ = sinh(z)
val coshZ = cosh(z)
val expected = sinhZ / coshZ
assertEquals(expected.real, result.real, 1e-10)
assertEquals(expected.imaginary, result.imaginary, 1e-10)
}
}

View File

@@ -0,0 +1,97 @@
package org.openrndr.extra.math.complex
import kotlin.math.PI
import kotlin.math.exp
import kotlin.test.Test
import kotlin.test.assertTrue
class TestComplex {
// Small epsilon for floating-point comparisons
private val e = 1E-6
/**
* Tests the arc cosine function for complex numbers.
*
* This test verifies that the arc cosine of specific complex numbers
* produces results that match the expected values within an acceptable
* error tolerance.
*/
@Test
fun testAcos() {
// Test case 1: acos(1) should be 0
val z1 = Complex(1.0, 0.0)
val result1 = acos(z1)
assertTrue(result1.real in 0.0 - e..0.0 + e)
assertTrue(result1.imaginary in 0.0 - e..0.0 + e)
// Test case 2: acos(-1) should be PI
val z2 = Complex(-1.0, 0.0)
val result2 = acos(z2)
assertTrue(result2.real in PI - e..PI + e)
assertTrue(result2.imaginary in 0.0 - e..0.0 + e)
// Test case 3: acos(0) should be PI/2
val z3 = Complex(0.0, 0.0)
val result3 = acos(z3)
assertTrue(result3.real in PI/2 - e..PI/2 + e)
assertTrue(result3.imaginary in 0.0 - e..0.0 + e)
// Test case 4: acos(i) should be PI/2 - i*ln(1 + sqrt(2))
val z4 = Complex(0.0, 1.0)
val result4 = acos(z4)
val expectedImag4 = -kotlin.math.ln(1.0 + kotlin.math.sqrt(2.0))
assertTrue(result4.real in PI/2 - e..PI/2 + e)
assertTrue(result4.imaginary in expectedImag4 - e..expectedImag4 + e)
// Test case 5: acos(2) should be 0 + i*acosh(2)
// For real x > 1, acos(x) = 0 + i*acosh(x) where acosh(x) = ln(x + sqrt(x^2 - 1))
val z5 = Complex(2.0, 0.0)
val result5 = acos(z5)
val expectedImag5 = kotlin.math.ln(2.0 + kotlin.math.sqrt(3.0))
assertTrue(result5.real in 0.0 - e..0.0 + e)
assertTrue(result5.imaginary in expectedImag5 - e..expectedImag5 + e)
}
/**
* Tests the exponential function for complex numbers.
*
* This test verifies that the exponential of specific complex numbers
* produces results that match the expected values within an acceptable
* error tolerance.
*/
@Test
fun testExp() {
// Test case 1: exp(0) should be 1
val z1 = Complex(0.0, 0.0)
val result1 = exp(z1)
assertTrue(result1.real in 1.0 - e..1.0 + e)
assertTrue(result1.imaginary in 0.0 - e..0.0 + e)
// Test case 2: exp(1) should be e
val z2 = Complex(1.0, 0.0)
val result2 = exp(z2)
val expectedReal2 = exp(1.0)
assertTrue(result2.real in expectedReal2 - e..expectedReal2 + e)
assertTrue(result2.imaginary in 0.0 - e..0.0 + e)
// Test case 3: exp(i*PI) should be -1
val z3 = Complex(0.0, PI)
val result3 = exp(z3)
assertTrue(result3.real in -1.0 - e..-1.0 + e)
assertTrue(result3.imaginary in 0.0 - e..0.0 + e)
// Test case 4: exp(i*PI/2) should be i
val z4 = Complex(0.0, PI/2)
val result4 = exp(z4)
assertTrue(result4.real in 0.0 - e..0.0 + e)
assertTrue(result4.imaginary in 1.0 - e..1.0 + e)
// Test case 5: exp(1+i) = e^1 * (cos(1) + i*sin(1))
val z5 = Complex(1.0, 1.0)
val result5 = exp(z5)
val expectedReal5 = exp(1.0) * kotlin.math.cos(1.0)
val expectedImag5 = exp(1.0) * kotlin.math.sin(1.0)
assertTrue(result5.real in expectedReal5 - e..expectedReal5 + e)
assertTrue(result5.imaginary in expectedImag5 - e..expectedImag5 + e)
}
}