ComplexNumber.java

package com.morphiqlabs.wavelet.cwt;

/**
 * Immutable complex number representation for CWT analysis.
 * 
 * <p>Provides basic complex arithmetic operations needed for
 * complex wavelet transforms and phase analysis.</p>
 *
 * @param real the real component
 * @param imag the imaginary component
 */
public record ComplexNumber(double real, double imag) {
    
    /**
     * Complex zero constant.
     */
    public static final ComplexNumber ZERO = new ComplexNumber(0, 0);
    
    /**
     * Complex one constant.
     */
    public static final ComplexNumber ONE = new ComplexNumber(1, 0);
    
    /**
     * Complex i constant.
     */
    public static final ComplexNumber I = new ComplexNumber(0, 1);
    
    /**
     * Creates a complex number from polar coordinates.
     * 
     * @param magnitude the magnitude (modulus)
     * @param phase the phase angle in radians
     * @return complex number
     */
    public static ComplexNumber fromPolar(double magnitude, double phase) {
        return new ComplexNumber(
            magnitude * Math.cos(phase),
            magnitude * Math.sin(phase)
        );
    }
    
    /**
     * Creates a real-valued complex number.
     * 
     * @param real the real value
     * @return complex number with zero imaginary part
     */
    public static ComplexNumber ofReal(double real) {
        return new ComplexNumber(real, 0);
    }
    
    /**
     * Creates a purely imaginary complex number.
     * 
     * @param imag the imaginary value
     * @return complex number with zero real part
     */
    public static ComplexNumber ofImaginary(double imag) {
        return new ComplexNumber(0, imag);
    }
    
    /**
     * Computes the magnitude (modulus) of this complex number.
     * 
     * @return |z| = sqrt(real² + imag²)
     */
    public double magnitude() {
        return Math.sqrt(real * real + imag * imag);
    }
    
    /**
     * Computes the phase (argument) of this complex number.
     * 
     * @return arg(z) in radians, range [-π, π]
     */
    public double phase() {
        return Math.atan2(imag, real);
    }
    
    /**
     * Computes the complex conjugate.
     * 
     * @return z* = real - i*imag
     */
    public ComplexNumber conjugate() {
        return new ComplexNumber(real, -imag);
    }
    
    /**
     * Adds another complex number to this one.
     * 
     * @param other the other complex number
     * @return this + other
     */
    public ComplexNumber add(ComplexNumber other) {
        return new ComplexNumber(
            real + other.real,
            imag + other.imag
        );
    }
    
    /**
     * Subtracts another complex number from this one.
     * 
     * @param other the other complex number
     * @return this - other
     */
    public ComplexNumber subtract(ComplexNumber other) {
        return new ComplexNumber(
            real - other.real,
            imag - other.imag
        );
    }
    
    /**
     * Multiplies this complex number by another.
     * 
     * @param other the other complex number
     * @return this * other
     */
    public ComplexNumber multiply(ComplexNumber other) {
        return new ComplexNumber(
            real * other.real - imag * other.imag,
            real * other.imag + imag * other.real
        );
    }
    
    /**
     * Multiplies this complex number by a real scalar.
     * 
     * @param scalar the real scalar
     * @return this * scalar
     */
    public ComplexNumber multiply(double scalar) {
        return new ComplexNumber(real * scalar, imag * scalar);
    }
    
    /**
     * Divides this complex number by another.
     * 
     * @param other the other complex number
     * @return this / other
     * @throws ArithmeticException if other is zero
     */
    public ComplexNumber divide(ComplexNumber other) {
        double denominator = other.real * other.real + other.imag * other.imag;
        if (denominator == 0) {
            throw new ArithmeticException("Division by zero");
        }
        
        return new ComplexNumber(
            (real * other.real + imag * other.imag) / denominator,
            (imag * other.real - real * other.imag) / denominator
        );
    }
    
    /**
     * Computes the complex exponential e^z.
     * 
     * @return exp(z) = exp(real) * (cos(imag) + i*sin(imag))
     */
    public ComplexNumber exp() {
        double expReal = Math.exp(real);
        return new ComplexNumber(
            expReal * Math.cos(imag),
            expReal * Math.sin(imag)
        );
    }
    
    /**
     * Checks if this complex number is real (imaginary part is zero).
     * 
     * @return true if imaginary part is effectively zero
     */
    public boolean isReal() {
        return Math.abs(imag) < 1e-15;
    }
    
    /**
     * Checks if this complex number is purely imaginary (real part is zero).
     * 
     * @return true if real part is effectively zero
     */
    public boolean isImaginary() {
        return Math.abs(real) < 1e-15;
    }
    
    @Override
    public String toString() {
        if (isReal()) {
            return String.format("%.4f", real);
        } else if (isImaginary()) {
            return String.format("%.4fi", imag);
        } else if (imag >= 0) {
            return String.format("%.4f + %.4fi", real, imag);
        } else {
            return String.format("%.4f - %.4fi", real, -imag);
        }
    }
}