ComplexMatrix.java

package com.morphiqlabs.wavelet.cwt;

/**
 * A matrix of complex numbers for CWT computations.
 * 
 * <p>Stores complex numbers in separate real and imaginary arrays
 * for better cache locality and SIMD optimization.</p>
 */
public final class ComplexMatrix {
    
    private final double[][] real;
    private final double[][] imaginary;
    private final int rows;
    private final int cols;
    
    /**
     * Creates a new complex matrix with given dimensions.
     * 
     * @param rows number of rows
     * @param cols number of columns
     */
    public ComplexMatrix(int rows, int cols) {
        if (rows <= 0 || cols <= 0) {
            throw new IllegalArgumentException("Matrix dimensions must be positive");
        }
        this.rows = rows;
        this.cols = cols;
        this.real = new double[rows][cols];
        this.imaginary = new double[rows][cols];
    }
    
    /**
     * Creates a complex matrix from real and imaginary parts.
     * 
     * @param real real part of the matrix
     * @param imaginary imaginary part of the matrix
     */
    public ComplexMatrix(double[][] real, double[][] imaginary) {
        if (real == null || imaginary == null) {
            throw new IllegalArgumentException("Real and imaginary parts cannot be null");
        }
        if (real.length == 0 || real[0].length == 0) {
            throw new IllegalArgumentException("Matrix cannot be empty");
        }
        if (real.length != imaginary.length || real[0].length != imaginary[0].length) {
            throw new IllegalArgumentException("Real and imaginary parts must have same dimensions");
        }
        
        this.rows = real.length;
        this.cols = real[0].length;
        this.real = new double[rows][cols];
        this.imaginary = new double[rows][cols];
        
        // Deep copy
        for (int i = 0; i < rows; i++) {
            System.arraycopy(real[i], 0, this.real[i], 0, cols);
            System.arraycopy(imaginary[i], 0, this.imaginary[i], 0, cols);
        }
    }
    
    /**
     * Sets a complex value at given position.
     * 
     * @param row row index
     * @param col column index
     * @param realValue real part
     * @param imagValue imaginary part
     */
    public void set(int row, int col, double realValue, double imagValue) {
        validateIndices(row, col);
        real[row][col] = realValue;
        imaginary[row][col] = imagValue;
    }
    
    /**
     * Gets the real part at given position.
     * 
     * @param row row index
     * @param col column index
     * @return real part
     */
    public double getReal(int row, int col) {
        validateIndices(row, col);
        return real[row][col];
    }
    
    /**
     * Gets the imaginary part at given position.
     * 
     * @param row row index
     * @param col column index
     * @return imaginary part
     */
    public double getImaginary(int row, int col) {
        validateIndices(row, col);
        return imaginary[row][col];
    }
    
    /**
     * Computes magnitude at given position.
     * 
     * @param row row index
     * @param col column index
     * @return magnitude |z| = sqrt(real² + imag²)
     */
    public double getMagnitude(int row, int col) {
        validateIndices(row, col);
        double r = real[row][col];
        double i = imaginary[row][col];
        return Math.sqrt(r * r + i * i);
    }
    
    /**
     * Computes phase at given position.
     * 
     * @param row row index
     * @param col column index
     * @return phase angle in radians
     */
    public double getPhase(int row, int col) {
        validateIndices(row, col);
        return Math.atan2(imaginary[row][col], real[row][col]);
    }
    
    /**
     * Gets all real parts as a matrix.
     * 
     * @return copy of real parts
     */
    public double[][] getReal() {
        double[][] copy = new double[rows][cols];
        for (int i = 0; i < rows; i++) {
            System.arraycopy(real[i], 0, copy[i], 0, cols);
        }
        return copy;
    }
    
    /**
     * Gets all imaginary parts as a matrix.
     * 
     * @return copy of imaginary parts
     */
    public double[][] getImaginary() {
        double[][] copy = new double[rows][cols];
        for (int i = 0; i < rows; i++) {
            System.arraycopy(imaginary[i], 0, copy[i], 0, cols);
        }
        return copy;
    }
    
    /**
     * Computes magnitude matrix.
     * 
     * @return magnitude matrix
     */
    public double[][] getMagnitude() {
        double[][] magnitude = new double[rows][cols];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                double r = real[i][j];
                double im = imaginary[i][j];
                magnitude[i][j] = Math.sqrt(r * r + im * im);
            }
        }
        return magnitude;
    }
    
    /**
     * Computes phase matrix.
     * 
     * @return phase matrix in radians
     */
    public double[][] getPhase() {
        double[][] phase = new double[rows][cols];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                phase[i][j] = Math.atan2(imaginary[i][j], real[i][j]);
            }
        }
        return phase;
    }
    
    /**
     * Gets the number of rows in the matrix.
     *
     * @return the number of rows
     */
    public int getRows() {
        return rows;
    }
    
    /**
     * Gets the number of columns in the matrix.
     *
     * @return the number of columns
     */
    public int getCols() {
        return cols;
    }
    
    private void validateIndices(int row, int col) {
        if (row < 0 || row >= rows) {
            throw new IndexOutOfBoundsException("Row index out of bounds: " + row);
        }
        if (col < 0 || col >= cols) {
            throw new IndexOutOfBoundsException("Column index out of bounds: " + col);
        }
    }
}