"""Unit tests for Tensor class.""" import pytest import numpy as np from synor_compute import Tensor from synor_compute.types import Precision class TestTensorCreation: """Tests for tensor creation methods.""" def test_from_numpy_array(self): """Should create tensor from numpy array.""" arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) tensor = Tensor.from_numpy(arr) assert tensor.shape == (2, 3) assert tensor.size == 6 assert tensor.ndim == 2 assert tensor.dtype == Precision.FP32 def test_from_numpy_with_dtype(self): """Should create tensor with specified dtype.""" arr = np.array([1, 2, 3]) tensor = Tensor.from_numpy(arr, dtype=Precision.FP16) assert tensor.dtype == Precision.FP16 def test_infer_dtype_fp64(self): """Should infer FP64 from float64 array.""" arr = np.array([1.0, 2.0], dtype=np.float64) tensor = Tensor.from_numpy(arr) assert tensor.dtype == Precision.FP64 def test_infer_dtype_int8(self): """Should infer INT8 from int8 array.""" arr = np.array([1, 2, 3], dtype=np.int8) tensor = Tensor.from_numpy(arr) assert tensor.dtype == Precision.INT8 class TestTensorFactoryMethods: """Tests for tensor factory methods.""" def test_zeros(self): """Should create tensor of zeros.""" tensor = Tensor.zeros((2, 3)) assert tensor.shape == (2, 3) assert np.allclose(tensor.data, 0) def test_zeros_with_dtype(self): """Should create zeros tensor with specified dtype.""" tensor = Tensor.zeros((2, 2), dtype=Precision.FP64) assert tensor.dtype == Precision.FP64 assert tensor.data.dtype == np.float64 def test_ones(self): """Should create tensor of ones.""" tensor = Tensor.ones((3, 2)) assert tensor.shape == (3, 2) assert np.allclose(tensor.data, 1) def test_random(self): """Should create tensor with random values in [0, 1).""" tensor = Tensor.random((10, 10)) assert tensor.shape == (10, 10) assert tensor.data.min() >= 0 assert tensor.data.max() < 1 def test_randn(self): """Should create tensor with normal distribution.""" tensor = Tensor.randn((1000,)) # Check approximate normal distribution properties assert abs(tensor.data.mean()) < 0.2 # Mean ~= 0 assert 0.8 < tensor.data.std() < 1.2 # Std ~= 1 class TestTensorOperations: """Tests for tensor operations.""" def test_reshape(self): """Should reshape tensor.""" tensor = Tensor.from_numpy(np.arange(6)) reshaped = tensor.reshape((2, 3)) assert reshaped.shape == (2, 3) assert reshaped.size == 6 def test_reshape_preserves_data(self): """Should preserve data when reshaping.""" arr = np.array([1, 2, 3, 4, 5, 6]) tensor = Tensor.from_numpy(arr) reshaped = tensor.reshape((2, 3)) assert reshaped.data[0, 0] == 1 assert reshaped.data[1, 2] == 6 def test_to_different_dtype(self): """Should convert to different dtype.""" tensor = Tensor.from_numpy(np.array([1.5, 2.5], dtype=np.float32)) converted = tensor.to(Precision.FP64) assert converted.dtype == Precision.FP64 assert converted.data.dtype == np.float64 def test_to_same_dtype_returns_self(self): """Should return self when converting to same dtype.""" tensor = Tensor.from_numpy(np.array([1.0, 2.0], dtype=np.float32)) same = tensor.to(Precision.FP32) assert same is tensor def test_numpy_returns_underlying_array(self): """Should return underlying numpy array.""" arr = np.array([1, 2, 3], dtype=np.float32) tensor = Tensor.from_numpy(arr) assert np.array_equal(tensor.numpy(), arr) class TestTensorProperties: """Tests for tensor properties.""" def test_shape(self): """Should return correct shape.""" tensor = Tensor.zeros((3, 4, 5)) assert tensor.shape == (3, 4, 5) def test_size(self): """Should return correct size.""" tensor = Tensor.zeros((3, 4, 5)) assert tensor.size == 60 def test_ndim(self): """Should return correct number of dimensions.""" tensor = Tensor.zeros((3, 4, 5)) assert tensor.ndim == 3 def test_nbytes(self): """Should return correct byte size.""" tensor = Tensor.zeros((10,), dtype=Precision.FP32) assert tensor.nbytes == 40 # 10 * 4 bytes class TestTensorSerialization: """Tests for tensor serialization.""" def test_serialize(self): """Should serialize tensor to dict.""" tensor = Tensor.from_numpy(np.array([[1, 2], [3, 4]], dtype=np.float32)) serialized = tensor.serialize() assert "data" in serialized assert "shape" in serialized assert "dtype" in serialized assert serialized["shape"] == [2, 2] assert serialized["dtype"] == "fp32" def test_deserialize(self): """Should deserialize tensor from dict.""" original = Tensor.from_numpy(np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32)) serialized = original.serialize() restored = Tensor.deserialize(serialized) assert restored.shape == original.shape assert restored.dtype == original.dtype assert np.array_equal(restored.data, original.data) def test_serialize_deserialize_fp64(self): """Should preserve FP64 precision.""" original = Tensor.from_numpy(np.array([1.234567890123456], dtype=np.float64)) serialized = original.serialize() restored = Tensor.deserialize(serialized) assert restored.dtype == Precision.FP64 assert np.allclose(restored.data, original.data) def test_serialize_deserialize_large_tensor(self): """Should handle large tensors.""" original = Tensor.random((100, 100)) serialized = original.serialize() restored = Tensor.deserialize(serialized) assert restored.shape == original.shape assert np.allclose(restored.data, original.data) class TestTensorRepr: """Tests for tensor string representations.""" def test_repr(self): """Should return informative repr.""" tensor = Tensor.zeros((2, 3), dtype=Precision.FP32) rep = repr(tensor) assert "Tensor" in rep assert "(2, 3)" in rep assert "fp32" in rep def test_str(self): """Should return string with data.""" tensor = Tensor.from_numpy(np.array([1, 2, 3], dtype=np.float32)) s = str(tensor) assert "Tensor" in s assert "fp32" in s class TestTensorEdgeCases: """Tests for edge cases.""" def test_scalar_like_tensor(self): """Should handle 1-element tensor.""" tensor = Tensor.from_numpy(np.array([42], dtype=np.float32)) assert tensor.shape == (1,) assert tensor.size == 1 assert tensor.ndim == 1 def test_empty_shape_dimension(self): """Should handle tensors with zero-size dimensions.""" tensor = Tensor.zeros((0, 3)) assert tensor.shape == (0, 3) assert tensor.size == 0 def test_high_dimensional_tensor(self): """Should handle high-dimensional tensors.""" tensor = Tensor.zeros((2, 3, 4, 5, 6)) assert tensor.ndim == 5 assert tensor.size == 720