[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,442 @@
package org.openrndr.extra.math.complex
import kotlin.jvm.JvmRecord
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.cosh
import kotlin.math.exp
import kotlin.math.ln
import kotlin.math.pow
import kotlin.math.sin
import kotlin.math.sinh
/**
* Represents a complex number with a real and imaginary part.
*
* Provides functionality to perform common mathematical operations
* with complex numbers, such as addition, subtraction, multiplication,
* division, and more. Includes utility functions for magnitude,
* argument, and conversions between polar and rectangular forms.
*
* @property real The real part of the complex number.
* @property imaginary The imaginary part of the complex number.
*/
@JvmRecord
data class Complex(val real: Double, val imaginary: Double) {
operator fun plus(other: Complex): Complex {
return Complex(real + other.real, imaginary + other.imaginary)
}
operator fun times(other: Complex): Complex {
return Complex(real * other.real - imaginary * other.imaginary, real * other.imaginary + imaginary * other.real)
}
operator fun unaryMinus(): Complex {
return Complex(-real, -imaginary)
}
operator fun div(other: Complex): Complex {
val c = other.real * other.real + other.imaginary * other.imaginary
return Complex(
(real * other.real + imaginary * other.imaginary) / c,
(imaginary * other.real - real * other.imaginary) / c
)
}
operator fun div(other: Double): Complex {
return Complex(real / other, imaginary / other)
}
operator fun minus(other: Complex): Complex {
return Complex(real - other.real, imaginary - other.imaginary)
}
operator fun times(other: Double): Complex {
return Complex(real * other, imaginary * other)
}
/**
* Calculates the magnitude (or absolute value) of the complex number.
* The magnitude is computed as the square root of the sum of the squares
* of the real and imaginary parts.
*
* @return The magnitude of the complex number.
*/
fun magnitude(): Double {
return kotlin.math.sqrt(real * real + imaginary * imaginary)
}
/**
* Computes the squared magnitude (or squared absolute value) of the complex number.
* The squared magnitude is determined as the sum of the squares of the real and imaginary parts.
*
* @return The squared magnitude of the complex number.
*/
fun sqrMagnitude(): Double {
return real * real + imaginary * imaginary
}
/**
* Computes the conjugate of the complex number.
* The conjugate of a complex number is formed by changing the sign of its imaginary part.
*
* @return A new instance of [Complex] representing the conjugate of the current complex number.
*/
fun conjugate(): Complex {
return Complex(real, -imaginary)
}
/**
* Normalizes the complex number to a unit magnitude.
* The normalized complex number retains the same direction in the complex plane
* but has a magnitude of 1.
*
* @return A new instance of [Complex] representing the normalized complex number.
*/
fun normalize(): Complex {
val m = magnitude()
return Complex(real / m, imaginary / m)
}
/**
* Computes the principal square root of the complex number.
* The square root is calculated based on the polar representation of the complex number.
*
* @return A new instance of [Complex] representing the square root of the current complex number.
*/
fun sqrt(): Complex {
val r = kotlin.math.sqrt(kotlin.math.sqrt(real * real + imaginary * imaginary))
val t = atan2(imaginary, real) / 2.0
return Complex(r * cos(t), r * sin(t))
}
/**
* Raises the current complex number to the power of the given exponent.
* The operation is performed in polar form, where the magnitude is raised
* to the exponent and the argument is multiplied by the exponent.
*
* @param exponent The exponent to which the complex number is raised.
* @return A new instance of [Complex] representing the result of the operation.
*/
fun pow(exponent: Double): Complex {
val m = magnitude().pow(exponent)
val phi = argument() * exponent
return Complex(m * cos(phi), m * sin(phi))
}
/**
* Computes the argument (or angle) of the complex number in polar coordinates.
* The argument is the angle formed by the positive real axis and the line representing the complex number
* in the complex plane, measured in radians.
*
* @return The argument of the complex number in radians.
*/
fun argument(): Double {
return atan2(imaginary, real)
}
companion object {
fun fromRadians(radians: Double): Complex {
return Complex(cos(radians), sin(radians))
}
fun fromPolar(magnitude: Double, argument: Double): Complex {
return Complex(magnitude * cos(argument), magnitude * sin(argument))
}
}
}
/**
* Divides a double-precision floating-point number by a complex number and returns the result.
*
* The division is performed using the formula for dividing a real number by a complex number.
*
* @param other The complex number to divide by.
* @return A new instance of [Complex] representing the result of the division.
*/
operator fun Double.div(other: Complex): Complex {
val c = other.real * other.real + other.imaginary * other.imaginary
return Complex((this * other.real) / c, (-this * other.imaginary) / c)
}
/**
* Raises a real number to the power of a complex number.
*
* @param exponent The complex exponent to which the real number will be raised.
* @return A [Complex] number representing the result of raising this real number
* to the power of the given complex exponent.
*/
fun Double.pow(exponent: Complex): Complex {
val be = this.pow(exponent.real)
val phase = exponent.imaginary * ln(this)
return Complex(be * cos(phase), be * sin(phase))
}
/**
* Computes the cosine of a complex number.
* The cosine of a complex number is calculated using the formula:
* cos(a + bi) = cos(a)cosh(b) - i*sin(a)sinh(b),
* where a and b are the real and imaginary parts of the complex number, respectively.
*
* @param complex The complex number for which the cosine is to be calculated.
* @return A new instance of [Complex] representing the cosine of the given complex number.
*/
fun cos(complex: Complex): Complex {
return Complex(cos(complex.real) * cosh(complex.imaginary), -sin(complex.real) * sinh(complex.imaginary))
}
/**
* Computes the sine of a given complex number using the formula:
* sin(z) = sin(a) * cosh(b) - i * cos(a) * sinh(b),
* where z = a + bi is the complex number, a is the real part, and b is the imaginary part.
*
* @param complex The complex number for which the sine is computed.
* @return A new instance of [Complex] representing the sine of the given complex number.
*/
fun sin(complex: Complex): Complex {
return Complex(sin(complex.real) * cosh(complex.imaginary), -cos(complex.real) * sinh(complex.imaginary))
}
/**
* Computes the tangent of a given complex number.
* The tangent of a complex number is calculated as the quotient of its sine and cosine.
*
* @param complex The complex number for which the tangent is to be calculated.
* @return A new instance of [Complex] representing the tangent of the given complex number.
*/
fun tan(complex: Complex): Complex {
return sin(complex) / cos(complex)
}
/**
* Computes the cotangent of a complex number.
* The cotangent is calculated using the formula cot(z) = cos(z) / sin(z),
* where z is the complex number.
*
* @param complex The complex number for which the cotangent is calculated.
* @return A new instance of [Complex] representing the cotangent of the given complex number.
*/
fun cot(complex: Complex): Complex {
return cos(complex) / sin(complex)
}
/**
* Computes the natural logarithm of a complex number.
* The natural logarithm is calculated using the formula:
* ln(z) = ln(|z|) + i * arg(z)
* where |z| is the magnitude and arg(z) is the argument of the complex number.
*
* @param complex The complex number for which the natural logarithm is calculated.
* @return A new instance of [Complex] representing the natural logarithm of the given complex number.
*/
fun ln(complex: Complex): Complex {
return Complex(ln(complex.magnitude()), complex.argument())
}
/**
* Computes the exponential of a complex number.
* The exponential is calculated using the formula:
* exp(a + bi) = e^a * (cos(b) + i*sin(b))
* where a and b are the real and imaginary parts of the complex number, respectively.
*
* @param complex The complex number for which the exponential is calculated.
* @return A new instance of [Complex] representing the exponential of the given complex number.
*/
fun exp(complex: Complex): Complex {
val expReal = exp(complex.real)
return Complex(expReal * cos(complex.imaginary), expReal * sin(complex.imaginary))
}
/**
* Computes the logarithm of a complex number with a specified base.
* The logarithm with base b is calculated using the formula:
* log_b(z) = ln(z) / ln(b)
* where z is the complex number and b is the base.
*
* @param x The complex number for which the logarithm is calculated.
* @param base The base of the logarithm (must be positive and not equal to 1).
* @return A new instance of [Complex] representing the logarithm of the given complex number with the specified base.
*/
fun log(x: Complex, base: Double): Complex {
require(base > 0 && base != 1.0) { "Logarithm base must be positive and not equal to 1" }
return ln(x) / ln(base)
}
/**
* Computes the logarithm of a complex number with a specified complex base.
* The logarithm with base b is calculated using the formula:
* log_b(z) = ln(z) / ln(b)
* where z is the complex number and b is the complex base.
*
* @param x The complex number for which the logarithm is calculated.
* @param base The complex base of the logarithm.
* @return A new instance of [Complex] representing the logarithm of the given complex number with the specified complex base.
*/
fun log(x: Complex, base: Complex): Complex {
require(base != Complex(1.0, 0.0)) { "Logarithm base must not be equal to 1" }
require(base.magnitude() > 0) { "Logarithm base must have non-zero magnitude" }
return ln(x) / ln(base)
}
/**
* Computes the arc cosine (inverse cosine) of a complex number.
* The arc cosine is calculated using the formula:
* acos(z) = -i * ln(z + i * sqrt(1 - z²))
* where z is the complex number.
*
* @param complex The complex number for which the arc cosine is calculated.
* @return A new instance of [Complex] representing the arc cosine of the given complex number.
*/
fun acos(complex: Complex): Complex {
val z2 = complex * complex
val oneMinusZ2 = Complex(1.0, 0.0) - z2
val sqrt = oneMinusZ2.sqrt()
val sum = complex + Complex(0.0, 1.0) * sqrt
// The negative sign is applied to the entire result
return Complex(0.0, 1.0) * ln(sum) * Complex(-1.0, 0.0)
}
/**
* Computes the arc sine (inverse sine) of a complex number.
* The arc sine is calculated using the formula:
* asin(z) = -i * ln(i * z + sqrt(1 - z²))
* where z is the complex number.
*
* @param complex The complex number for which the arc sine is calculated.
* @return A new instance of [Complex] representing the arc sine of the given complex number.
*/
fun asin(complex: Complex): Complex {
val z2 = complex * complex
val oneMinusZ2 = Complex(1.0, 0.0) - z2
val sqrt = oneMinusZ2.sqrt()
val sum = Complex(0.0, 1.0) * complex + sqrt
return Complex(0.0, -1.0) * ln(sum)
}
/**
* Raises a real number to the power of the given exponent and returns the result as a complex number.
*
* This function internally converts the real number to a complex number with an imaginary part of 0,
* and then performs the power operation.
*
* @param exponent The exponent to which the number is raised.
* @return A [Complex] instance representing the result of raising the number to the given power.
*/
fun Double.cpow(exponent: Double): Complex = Complex(this, 0.0).pow(exponent)
/**
* Computes the arc tangent (inverse tangent) of a complex number.
* The arc tangent is calculated using the formula:
* atan(z) = (i/2) * ln((i+z)/(i-z))
* where z is the complex number.
*
* @param complex The complex number for which the arc tangent is calculated.
* @return A new instance of [Complex] representing the arc tangent of the given complex number.
*/
fun atan(complex: Complex): Complex {
val i = Complex(0.0, 1.0)
val numerator = i + complex
val denominator = i - complex
val fraction = numerator / denominator
return i * ln(fraction) * Complex(0.5, 0.0)
}
/**
* Computes the hyperbolic cosine of a complex number.
* The hyperbolic cosine is calculated using the formula:
* cosh(a + bi) = cosh(a)cos(b) + i·sinh(a)sin(b),
* where a and b are the real and imaginary parts of the complex number, respectively.
*
* @param complex The complex number for which the hyperbolic cosine is calculated.
* @return A new instance of [Complex] representing the hyperbolic cosine of the given complex number.
*/
fun cosh(complex: Complex): Complex =
Complex(cosh(complex.real) * cos(complex.imaginary), sinh(complex.real) * sin(complex.imaginary))
/**
* Computes the hyperbolic sine of a complex number.
* The hyperbolic sine is calculated using the formula:
* sinh(a + bi) = sinh(a)cos(b) + i·cosh(a)sin(b),
* where a and b are the real and imaginary parts of the complex number, respectively.
*
* @param complex The complex number for which the hyperbolic sine is calculated.
* @return A new instance of [Complex] representing the hyperbolic sine of the given complex number.
*/
fun sinh(complex: Complex): Complex =
Complex(sinh(complex.real) * cos(complex.imaginary), cosh(complex.real) * sin(complex.imaginary))
/**
* Computes the inverse hyperbolic cosine of a complex number.
* The inverse hyperbolic cosine is calculated using the formula:
* acosh(z) = ln(z + sqrt(z² - 1))
* where z is the complex number.
*
* @param complex The complex number for which the inverse hyperbolic cosine is calculated.
* @return A new instance of [Complex] representing the inverse hyperbolic cosine of the given complex number.
*/
fun acosh(complex: Complex): Complex {
val z2 = complex * complex
val z2Minus1 = z2 - Complex(1.0, 0.0)
val sqrt = z2Minus1.sqrt()
val sum = complex + sqrt
return ln(sum)
}
/**
* Computes the inverse hyperbolic sine of a complex number.
* The inverse hyperbolic sine is calculated using the formula:
* asinh(z) = ln(z + sqrt(z² + 1))
* where z is the complex number.
*
* @param complex The complex number for which the inverse hyperbolic sine is calculated.
* @return A new instance of [Complex] representing the inverse hyperbolic sine of the given complex number.
*/
fun asinh(complex: Complex): Complex {
val z2 = complex * complex
val z2Plus1 = z2 + Complex(1.0, 0.0)
val sqrt = z2Plus1.sqrt()
val sum = complex + sqrt
return ln(sum)
}
/**
* Computes the hyperbolic tangent of a complex number.
* The hyperbolic tangent is calculated using the formula:
* tanh(z) = sinh(z) / cosh(z)
* where z is the complex number.
*
* @param complex The complex number for which the hyperbolic tangent is calculated.
* @return A new instance of [Complex] representing the hyperbolic tangent of the given complex number.
*/
fun tanh(complex: Complex): Complex = sinh(complex) / cosh(complex)
/**
* Computes the inverse hyperbolic tangent of a complex number.
* The inverse hyperbolic tangent is calculated using the formula:
* atanh(z) = (1/2) * ln((1+z)/(1-z))
* where z is the complex number.
*
* Special cases:
* - For z = i (imaginary unit), atanh(i) = i*π/2
* - For z = -i (negative imaginary unit), atanh(-i) = -i*π/2
*
* @param complex The complex number for which the inverse hyperbolic tangent is calculated.
* @return A new instance of [Complex] representing the inverse hyperbolic tangent of the given complex number.
*/
fun atanh(complex: Complex): Complex {
// Special cases for imaginary unit values
if (complex.real == 0.0 && complex.imaginary == 1.0) {
return Complex(0.0, kotlin.math.PI / 2)
}
if (complex.real == 0.0 && complex.imaginary == -1.0) {
return Complex(0.0, -kotlin.math.PI / 2)
}
val one = Complex(1.0, 0.0)
val numerator = one + complex
val denominator = one - complex
val fraction = numerator / denominator
return ln(fraction) * Complex(0.5, 0.0)
}

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)
}
}