""" Integration Tests - Tensor and Activations Tests cross-module interfaces and compatibility between Tensor and Activation modules. Focuses on integration, not re-testing individual module functionality. """ import pytest import numpy as np from test_utils import setup_integration_test # Ensure proper setup before importing setup_integration_test() # Import ONLY from TinyTorch package from tinytorch.core.tensor import Tensor from tinytorch.core.activations import ReLU, Sigmoid, Tanh, Softmax class TestTensorActivationInterface: """Test interface compatibility between Tensor and Activation modules.""" def test_activation_accepts_tensor_input(self): """Test that activation functions accept Tensor objects as input.""" activations = [ReLU(), Sigmoid(), Tanh()] # Test with different tensor shapes test_tensors = [ Tensor([1.0, -1.0, 0.0]), # 1D tensor Tensor([[1.0, -1.0], [0.0, 2.0]]), # 2D tensor Tensor([[[1.0], [-1.0]], [[0.0], [2.0]]]), # 3D tensor ] for activation in activations: for tensor in test_tensors: # Test interface: activation should accept Tensor result = activation(tensor) # Verify interface compatibility assert isinstance(result, Tensor), f"{type(activation).__name__} should return Tensor" assert result.shape == tensor.shape, f"{type(activation).__name__} should preserve shape" assert result.dtype == tensor.dtype, f"{type(activation).__name__} should preserve dtype" def test_softmax_tensor_interface(self): """Test Softmax interface with Tensor objects.""" softmax = Softmax() # Test with different tensor configurations test_cases = [ Tensor([[1.0, 2.0, 3.0]]), # Single batch Tensor([[1.0, 2.0], [3.0, 4.0]]), # Multiple samples ] for tensor in test_cases: result = softmax(tensor) # Verify interface compatibility assert isinstance(result, Tensor), "Softmax should return Tensor" assert result.shape == tensor.shape, "Softmax should preserve shape" assert result.dtype in [np.float32, np.float64], "Softmax should return float type" def test_activation_output_tensor_compatibility(self): """Test that activation outputs are compatible with further Tensor operations.""" relu = ReLU() # Create input tensor x = Tensor([[-2.0, -1.0, 0.0, 1.0, 2.0]]) # Apply activation activated = relu(x) # Test that output can be used in Tensor operations doubled = activated * Tensor([[2.0, 2.0, 2.0, 2.0, 2.0]]) summed = activated + Tensor([[1.0, 1.0, 1.0, 1.0, 1.0]]) # Verify interface compatibility assert isinstance(doubled, Tensor), "Activation output should work with Tensor multiplication" assert isinstance(summed, Tensor), "Activation output should work with Tensor addition" assert doubled.shape == activated.shape, "Tensor operations should preserve shape" assert summed.shape == activated.shape, "Tensor operations should preserve shape" def test_chained_activations_with_tensors(self): """Test chaining multiple activations with Tensor interface.""" # Create activation chain relu = ReLU() sigmoid = Sigmoid() # Test chaining x = Tensor([[1.0, -1.0, 2.0, -2.0]]) # Chain: input → ReLU → Sigmoid relu_output = relu(x) sigmoid_output = sigmoid(relu_output) # Verify chaining works assert isinstance(relu_output, Tensor), "First activation should return Tensor" assert isinstance(sigmoid_output, Tensor), "Second activation should accept first activation output" assert sigmoid_output.shape == x.shape, "Chained activations should preserve shape" class TestTensorActivationDataTypes: """Test data type compatibility between Tensor and Activation modules.""" def test_float32_tensor_activation_compatibility(self): """Test activations with float32 Tensor inputs.""" activations = [ReLU(), Sigmoid(), Tanh()] x_f32 = Tensor(np.array([1.0, -1.0, 0.0]).astype(np.float32)) for activation in activations: result = activation(x_f32) # Verify dtype preservation assert result.dtype == np.float32, f"{type(activation).__name__} should preserve float32" assert isinstance(result, Tensor), f"{type(activation).__name__} should return Tensor" def test_float64_tensor_activation_compatibility(self): """Test activations with float64 Tensor inputs.""" activations = [ReLU(), Sigmoid(), Tanh()] x_f64 = Tensor(np.array([1.0, -1.0, 0.0]).astype(np.float64)) for activation in activations: result = activation(x_f64) # Verify dtype preservation assert result.dtype == np.float64, f"{type(activation).__name__} should preserve float64" assert isinstance(result, Tensor), f"{type(activation).__name__} should return Tensor" def test_integer_tensor_activation_compatibility(self): """Test activations with integer Tensor inputs.""" relu = ReLU() # ReLU should work with integers x_int = Tensor([1, -1, 0, 2]) # Integer tensor result = relu(x_int) # Verify interface handles integer input assert isinstance(result, Tensor), "ReLU should return Tensor for integer input" assert result.shape == x_int.shape, "ReLU should preserve shape for integer input" class TestActivationTensorSystemIntegration: """Test system-level integration scenarios with Tensor and Activation.""" def test_tensor_activation_tensor_roundtrip(self): """Test Tensor → Activation → Tensor operations roundtrip.""" activations = [ReLU(), Sigmoid(), Tanh()] original_tensor = Tensor([[1.0, -1.0, 0.5, -0.5]]) for activation in activations: # Apply activation activated = activation(original_tensor) # Use in further tensor operations scaled = activated * Tensor([[2.0, 2.0, 2.0, 2.0]]) final = scaled + Tensor([[1.0, 1.0, 1.0, 1.0]]) # Verify complete workflow assert isinstance(final, Tensor), f"{type(activation).__name__} workflow should produce Tensor" assert final.shape == original_tensor.shape, f"{type(activation).__name__} workflow should preserve shape" def test_multiple_tensor_activation_operations(self): """Test multiple activation operations in sequence.""" # Create multiple tensors tensors = [ Tensor([[1.0, 2.0]]), Tensor([[-1.0, -2.0]]), Tensor([[0.0, 3.0]]), ] relu = ReLU() # Apply activation to all tensors results = [] for tensor in tensors: result = relu(tensor) results.append(result) # Verify all operations work for i, result in enumerate(results): assert isinstance(result, Tensor), f"Activation {i} should return Tensor" assert result.shape == tensors[i].shape, f"Activation {i} should preserve shape" # Test combining results combined = results[0] + results[1] + results[2] assert isinstance(combined, Tensor), "Combined activation results should create Tensor" def test_activation_error_handling_with_tensors(self): """Test activation error handling with edge case Tensors.""" relu = ReLU() # Test with empty tensor try: empty_tensor = Tensor(np.array([])) result = relu(empty_tensor) assert isinstance(result, Tensor), "Should handle empty tensor gracefully" assert result.shape == empty_tensor.shape, "Should preserve empty shape" except (ValueError, TypeError) as e: # Expected behavior - should fail gracefully assert isinstance(e, (ValueError, TypeError)), "Should fail gracefully with empty tensor" # Test with single element single_tensor = Tensor([5.0]) result = relu(single_tensor) assert isinstance(result, Tensor), "Should handle single element tensor" assert result.shape == (1,), "Should preserve single element shape" class TestActivationInterfaceCompatibility: """Test activation function interface compatibility requirements.""" def test_activation_preserves_tensor_properties(self): """Test that activations preserve essential Tensor properties.""" activations = [ReLU(), Sigmoid(), Tanh()] # Test tensor with specific properties original = Tensor([[1.0, -1.0], [2.0, -2.0]]) for activation in activations: result = activation(original) # Verify property preservation assert hasattr(result, 'shape'), f"{type(activation).__name__} result should have shape property" assert hasattr(result, 'data'), f"{type(activation).__name__} result should have data property" assert hasattr(result, 'dtype'), f"{type(activation).__name__} result should have dtype property" # Verify properties are accessible assert result.shape == original.shape, f"{type(activation).__name__} should preserve shape property" assert isinstance(result.data, np.ndarray), f"{type(activation).__name__} should have numpy data" def test_softmax_special_interface_requirements(self): """Test Softmax special interface requirements.""" softmax = Softmax() # Softmax needs 2D input for proper operation x_2d = Tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) result = softmax(x_2d) # Verify softmax interface requirements assert isinstance(result, Tensor), "Softmax should return Tensor" assert result.shape == x_2d.shape, "Softmax should preserve 2D shape" assert len(result.shape) == 2, "Softmax should maintain 2D structure" def test_activation_batch_compatibility(self): """Test activation compatibility with batch processing.""" relu = ReLU() # Simulate batch processing batch_size = 3 feature_size = 4 batch_tensor = Tensor(np.random.randn(batch_size, feature_size)) batch_result = relu(batch_tensor) # Verify batch compatibility assert isinstance(batch_result, Tensor), "Activation should handle batch Tensors" assert batch_result.shape == (batch_size, feature_size), "Activation should preserve batch dimensions" # Test processing individual batch items for i in range(batch_size): item_tensor = Tensor(batch_tensor.data[i:i+1]) item_result = relu(item_tensor) assert isinstance(item_result, Tensor), f"Activation should handle batch item {i}" if __name__ == "__main__": pytest.main([__file__])