""" Unit tests for ria_toolkit_oss.utils.array_conversion. Covers: - is_1xn / is_2xn classification - convert_to_1xn / convert_to_2xn conversion - Round-trip invariance - Error paths for invalid inputs """ import numpy as np import pytest from ria_toolkit_oss.utils.array_conversion import ( convert_to_1xn, convert_to_2xn, is_1xn, is_2xn, ) # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- COMPLEX_1XN = np.array([[1 + 2j, 3 + 4j, 5 + 6j]], dtype=np.complex128) # shape (1, 3) REAL_2XN = np.array([[1.0, 3.0, 5.0], [2.0, 4.0, 6.0]], dtype=np.float64) # shape (2, 3) # --------------------------------------------------------------------------- # is_1xn # --------------------------------------------------------------------------- def test_is_1xn_true_for_complex_1xn(): assert is_1xn(COMPLEX_1XN) is True def test_is_1xn_false_for_real_2xn(): assert is_1xn(REAL_2XN) is False def test_is_1xn_false_for_1d_complex(): arr = np.array([1 + 2j, 3 + 4j]) # 1-D assert is_1xn(arr) is False def test_is_1xn_false_for_3d(): arr = np.ones((1, 3, 3), dtype=np.complex128) assert is_1xn(arr) is False def test_is_1xn_false_for_real_1xn(): arr = np.array([[1.0, 2.0, 3.0]]) # real 1×N assert is_1xn(arr) is False def test_is_1xn_false_for_complex_2xn(): arr = np.array([[1 + 2j, 3 + 4j], [5 + 6j, 7 + 8j]]) # complex 2×N assert is_1xn(arr) is False def test_is_1xn_single_sample(): arr = np.array([[1 + 0j]]) # shape (1, 1) assert is_1xn(arr) is True # --------------------------------------------------------------------------- # is_2xn # --------------------------------------------------------------------------- def test_is_2xn_true_for_real_2xn(): assert is_2xn(REAL_2XN) is True def test_is_2xn_false_for_complex_1xn(): assert is_2xn(COMPLEX_1XN) is False def test_is_2xn_false_for_1d(): arr = np.array([1.0, 2.0, 3.0]) assert is_2xn(arr) is False def test_is_2xn_false_for_3xn(): arr = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) # shape (3, 2) assert is_2xn(arr) is False def test_is_2xn_false_for_complex_2xn(): arr = np.array([[1 + 2j, 3 + 4j], [5 + 6j, 7 + 8j]]) # complex 2×N assert is_2xn(arr) is False def test_is_2xn_single_column(): arr = np.array([[1.0], [2.0]]) # shape (2, 1) assert is_2xn(arr) is True # --------------------------------------------------------------------------- # convert_to_2xn # --------------------------------------------------------------------------- def test_convert_to_2xn_from_1xn_shape(): result = convert_to_2xn(COMPLEX_1XN) assert result.shape == (2, COMPLEX_1XN.shape[1]) def test_convert_to_2xn_from_1xn_values(): """First row is real, second row is imaginary.""" result = convert_to_2xn(COMPLEX_1XN) assert np.array_equal(result[0], COMPLEX_1XN[0].real) assert np.array_equal(result[1], COMPLEX_1XN[0].imag) def test_convert_to_2xn_from_1xn_is_real(): result = convert_to_2xn(COMPLEX_1XN) assert not np.iscomplexobj(result) def test_convert_to_2xn_from_2xn_is_copy(): """Already-2xN input returns a copy (not the same object).""" result = convert_to_2xn(REAL_2XN) assert np.array_equal(result, REAL_2XN) assert result is not REAL_2XN def test_convert_to_2xn_invalid_raises(): """1-D array is neither 1xN nor 2xN — must raise ValueError.""" arr = np.array([1.0, 2.0, 3.0]) with pytest.raises(ValueError): convert_to_2xn(arr) def test_convert_to_2xn_invalid_complex_2xn_raises(): """Complex 2×N is not a recognised format — must raise ValueError.""" arr = np.array([[1 + 2j, 3 + 4j], [5 + 6j, 7 + 8j]]) with pytest.raises(ValueError): convert_to_2xn(arr) # --------------------------------------------------------------------------- # convert_to_1xn # --------------------------------------------------------------------------- def test_convert_to_1xn_from_2xn_shape(): result = convert_to_1xn(REAL_2XN) assert result.shape == (1, REAL_2XN.shape[1]) def test_convert_to_1xn_from_2xn_values(): """Real part from row 0, imaginary from row 1.""" result = convert_to_1xn(REAL_2XN) assert np.array_equal(result[0].real, REAL_2XN[0]) assert np.array_equal(result[0].imag, REAL_2XN[1]) def test_convert_to_1xn_from_2xn_is_complex(): result = convert_to_1xn(REAL_2XN) assert np.iscomplexobj(result) def test_convert_to_1xn_from_1xn_is_copy(): """Already-1xN input returns a copy (not the same object).""" result = convert_to_1xn(COMPLEX_1XN) assert np.array_equal(result, COMPLEX_1XN) assert result is not COMPLEX_1XN def test_convert_to_1xn_invalid_raises(): """1-D array is neither 1xN nor 2xN — must raise ValueError.""" arr = np.array([1.0, 2.0, 3.0]) with pytest.raises(ValueError): convert_to_1xn(arr) def test_convert_to_1xn_invalid_3xn_raises(): """3×N array is not a recognised format — must raise ValueError.""" arr = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) with pytest.raises(ValueError): convert_to_1xn(arr) # --------------------------------------------------------------------------- # Round-trip invariance # --------------------------------------------------------------------------- def test_roundtrip_1xn_to_2xn_to_1xn(): """1xN → 2xN → 1xN should recover the original values.""" intermediate = convert_to_2xn(COMPLEX_1XN) recovered = convert_to_1xn(intermediate) assert np.allclose(recovered, COMPLEX_1XN) def test_roundtrip_2xn_to_1xn_to_2xn(): """2xN → 1xN → 2xN should recover the original values.""" intermediate = convert_to_1xn(REAL_2XN) recovered = convert_to_2xn(intermediate) assert np.allclose(recovered, REAL_2XN) def test_roundtrip_preserves_precision(): """Values survive a double conversion with full float64 precision.""" data = np.array([[1.23456789 + 9.87654321j, -0.1 - 0.2j]], dtype=np.complex128) recovered = convert_to_1xn(convert_to_2xn(data)) assert np.allclose(recovered, data, atol=1e-14)