Foundation: Standardize test naming in setup and tensor modules

- Rename test functions to follow test_unit_<name> convention
- Setup module: test_personal_info → test_unit_personal_info_basic
- Setup module: test_system_info → test_unit_system_info_basic
- Tensor module: test_tensor_* → test_unit_tensor_*
- Establishes consistent unit test naming for core foundation modules
This commit is contained in:
Vijay Janapa Reddi
2025-07-20 08:38:46 -04:00
parent 058ec89909
commit bcb6f02abd
2 changed files with 96 additions and 1506 deletions

View File

@@ -528,7 +528,7 @@ These test functions provide immediate feedback when developing your solutions:
"""
# %%
def test_personal_info():
def test_unit_personal_info_basic():
"""Test personal_info function implementation."""
print("🔬 Unit Test: Personal Information...")
@@ -562,7 +562,7 @@ def test_personal_info():
print(f"✅ TinyTorch configured for: {personal['developer']}")
# %%
def test_system_info():
def test_unit_system_info_basic():
"""Test system_info function implementation."""
print("🔬 Unit Test: System Information...")
@@ -631,22 +631,6 @@ You've successfully configured your TinyTorch installation and learned the found
- **Code export**: Functions become part of production package
- **Testing practices**: Comprehensive validation of functionality
### Connections to Real ML Systems
This module connects to broader ML engineering practices:
#### **Industry Parallels**
- **Docker containers**: System configuration and reproducibility
- **MLflow tracking**: Experiment context and system metadata
- **Model cards**: Documentation of system requirements and performance
- **CI/CD pipelines**: Automated testing and environment validation
#### **Production Considerations**
- **Deployment matching**: Development environment should match production
- **Resource planning**: Understanding hardware constraints for scaling
- **Monitoring**: System metrics for performance optimization
- **Debugging**: System context for troubleshooting issues
### Next Steps in Your ML Systems Journey
#### **Immediate Actions**
@@ -682,698 +666,3 @@ You've taken your first step in ML systems engineering! This module taught you:
**Ready for the next challenge?** Let's build the foundation of ML systems with tensors!
"""
# %% [markdown]
"""
## Step 4: Environment Validation
### The Concept: Dependency Management in ML Systems
**Environment validation** ensures your system has the necessary packages and versions for ML development. This is crucial because ML systems have complex dependency chains that can break in subtle ways.
### Why Environment Validation Matters
#### 1. **Compatibility Assurance**
- **Version conflicts**: Different packages may require incompatible versions
- **API changes**: New versions might break existing code
- **Feature availability**: Some features require specific versions
#### 2. **Reproducibility**
- **Environment documentation**: Exact package versions for reproduction
- **Dependency tracking**: Understanding what's installed and why
- **Debugging support**: Version info helps troubleshoot issues
#### 3. **Professional Development**
- **Deployment safety**: Ensure development matches production
- **Collaboration**: Team members need compatible environments
- **Quality assurance**: Validate setup before beginning work
### Essential ML Dependencies
We'll check for core packages that ML systems depend on:
- **numpy**: Fundamental numerical computing
- **matplotlib**: Visualization and plotting
- **psutil**: System information and monitoring
- **jupyter**: Interactive development environment
- **nbdev**: Package development tools
- **pytest**: Testing framework
### Real-World Applications
- **Docker**: Container images include dependency validation
- **CI/CD**: Automated testing validates environment setup
- **MLflow**: Tracks package versions with experiment metadata
- **Kaggle**: Validates package availability in competition environments
Let's implement environment validation!
"""
# %% nbgrader={"grade": false, "grade_id": "environment-validation", "locked": false, "schema_version": 3, "solution": true, "task": false}
#| export
import importlib
import pkg_resources
from typing import Dict, List, Optional
def validate_environment() -> Dict[str, Any]:
"""
Validate ML development environment and check essential dependencies.
This function checks that your system has the necessary packages for ML development.
It's like a pre-flight check before you start building ML systems.
TODO: Implement environment validation.
STEP-BY-STEP IMPLEMENTATION:
1. Define list of essential ML packages to check
2. For each package, try to import it and get version
3. Track which packages are available vs missing
4. Calculate environment health score
5. Return comprehensive environment report
ESSENTIAL PACKAGES TO CHECK:
- numpy: Numerical computing foundation
- matplotlib: Visualization and plotting
- psutil: System monitoring
- jupyter: Interactive development
- nbdev: Package development
- pytest: Testing framework
IMPLEMENTATION HINTS:
- Use try/except to handle missing packages gracefully
- Use pkg_resources.get_distribution(package).version for versions
- Calculate health_score as (available_packages / total_packages) * 100
- Round health_score to 1 decimal place
"""
### BEGIN SOLUTION
essential_packages = [
'numpy', 'matplotlib', 'psutil', 'jupyter', 'nbdev', 'pytest'
]
available = {}
missing = []
for package in essential_packages:
try:
# Try to import the package
importlib.import_module(package)
# Get version information
version = pkg_resources.get_distribution(package).version
available[package] = version
except (ImportError, pkg_resources.DistributionNotFound):
missing.append(package)
# Calculate health score
total_packages = len(essential_packages)
available_packages = len(available)
health_score = round((available_packages / total_packages) * 100, 1)
return {
'available_packages': available,
'missing_packages': missing,
'health_score': health_score,
'total_checked': total_packages,
'status': 'healthy' if health_score >= 80 else 'needs_attention'
}
### END SOLUTION
# %% [markdown]
"""
## Step 5: Performance Benchmarking
### The Concept: Hardware Performance Profiling
**Performance benchmarking** measures your system's computational capabilities for ML workloads. This helps you understand your hardware limits and optimize your development workflow.
### Why Performance Benchmarking Matters
#### 1. **Resource Planning**
- **Training time estimation**: How long will model training take?
- **Memory allocation**: What's the maximum batch size you can handle?
- **Parallelization**: How many cores can you effectively use?
#### 2. **Optimization Guidance**
- **Bottleneck identification**: Is your system CPU-bound or memory-bound?
- **Hardware upgrades**: What would improve performance most?
- **Algorithm selection**: Which algorithms suit your hardware?
#### 3. **Performance Comparison**
- **Baseline establishment**: Track performance over time
- **System comparison**: Compare different development environments
- **Deployment planning**: Match development to production performance
### Benchmarking Strategy
We'll test key ML operations:
- **CPU computation**: Matrix operations that stress the processor
- **Memory bandwidth**: Large data transfers that test memory speed
- **Overall system**: Combined CPU and memory performance
### Real-World Applications
- **MLPerf**: Industry-standard ML benchmarks
- **Cloud providers**: Performance metrics for instance selection
- **Hardware vendors**: Benchmark comparisons for purchasing decisions
Let's implement performance benchmarking!
"""
# %% nbgrader={"grade": false, "grade_id": "performance-benchmark", "locked": false, "schema_version": 3, "solution": true, "task": false}
#| export
import time
import random
def benchmark_performance() -> Dict[str, Any]:
"""
Benchmark system performance for ML workloads.
This function measures computational performance to help you understand
your system's capabilities and optimize your ML development workflow.
TODO: Implement performance benchmarking.
STEP-BY-STEP IMPLEMENTATION:
1. CPU Test: Time a computationally intensive operation
2. Memory Test: Time a memory-intensive operation
3. Calculate performance scores based on execution time
4. Determine overall system performance rating
5. Return comprehensive benchmark results
BENCHMARK TESTS:
- CPU: Nested loop calculation (computational intensity)
- Memory: Large list operations (memory bandwidth)
- Combined: Overall system performance score
IMPLEMENTATION HINTS:
- Use time.time() to measure execution time
- CPU test: nested loops with mathematical operations
- Memory test: large list creation and manipulation
- Lower execution time = better performance
- Calculate scores as inverse of time (e.g., 1/time * 1000)
"""
### BEGIN SOLUTION
benchmarks = {}
# CPU Performance Test
print("⚡ Running CPU benchmark...")
start_time = time.time()
# CPU-intensive calculation
result = 0
for i in range(100000):
result += i * i + i / 2
cpu_time = time.time() - start_time
benchmarks['cpu_time'] = round(cpu_time, 3)
benchmarks['cpu_score'] = round(1000 / cpu_time, 1)
# Memory Performance Test
print("🧠 Running memory benchmark...")
start_time = time.time()
# Memory-intensive operations
large_list = list(range(1000000))
large_list.reverse()
large_list.sort()
memory_time = time.time() - start_time
benchmarks['memory_time'] = round(memory_time, 3)
benchmarks['memory_score'] = round(1000 / memory_time, 1)
# Overall Performance Score
overall_score = round((benchmarks['cpu_score'] + benchmarks['memory_score']) / 2, 1)
benchmarks['overall_score'] = overall_score
# Performance Rating
if overall_score >= 80:
rating = 'excellent'
elif overall_score >= 60:
rating = 'good'
elif overall_score >= 40:
rating = 'fair'
else:
rating = 'needs_optimization'
benchmarks['performance_rating'] = rating
return benchmarks
### END SOLUTION
# %% [markdown]
"""
## Step 6: Development Environment Setup
### The Concept: Professional Development Configuration
**Development environment setup** configures essential tools and settings for professional ML development. This includes Git configuration, Jupyter settings, and other tools that make development more efficient.
### Why Development Setup Matters
#### 1. **Professional Standards**
- **Version control**: Proper Git configuration for collaboration
- **Code quality**: Consistent formatting and style
- **Documentation**: Automatic documentation generation
#### 2. **Productivity Optimization**
- **Tool configuration**: Optimized settings for efficiency
- **Workflow automation**: Reduce repetitive tasks
- **Error prevention**: Catch issues before they become problems
#### 3. **Collaboration Readiness**
- **Team compatibility**: Consistent development environment
- **Code sharing**: Proper attribution and commit messages
- **Project standards**: Follow established conventions
### Essential Development Tools
We'll configure key tools for ML development:
- **Git**: Version control and collaboration
- **Jupyter**: Interactive development environment
- **Python**: Code formatting and quality tools
Let's implement development environment setup!
"""
# %% nbgrader={"grade": false, "grade_id": "development-setup", "locked": false, "schema_version": 3, "solution": true, "task": false}
#| export
import subprocess
import json
from pathlib import Path
def setup_development_environment() -> Dict[str, Any]:
"""
Configure development environment for professional ML development.
This function sets up essential tools and configurations to make your
development workflow more efficient and professional.
TODO: Implement development environment setup.
STEP-BY-STEP IMPLEMENTATION:
1. Check if Git is installed and configured
2. Verify Jupyter installation and configuration
3. Check Python development tools
4. Configure any missing tools
5. Return setup status and recommendations
DEVELOPMENT TOOLS TO CHECK:
- Git: Version control system
- Jupyter: Interactive development
- Python tools: Code quality and formatting
IMPLEMENTATION HINTS:
- Use subprocess.run() to check tool availability
- Use try/except to handle missing tools gracefully
- Provide helpful recommendations for missing tools
- Focus on tools that improve ML development workflow
"""
### BEGIN SOLUTION
setup_status = {}
recommendations = []
# Check Git installation and configuration
try:
git_version = subprocess.run(['git', '--version'],
capture_output=True, text=True, check=True)
setup_status['git_installed'] = True
setup_status['git_version'] = git_version.stdout.strip()
# Check Git configuration
try:
git_name = subprocess.run(['git', 'config', 'user.name'],
capture_output=True, text=True, check=True)
git_email = subprocess.run(['git', 'config', 'user.email'],
capture_output=True, text=True, check=True)
setup_status['git_configured'] = True
setup_status['git_name'] = git_name.stdout.strip()
setup_status['git_email'] = git_email.stdout.strip()
except subprocess.CalledProcessError:
setup_status['git_configured'] = False
recommendations.append("Configure Git: git config --global user.name 'Your Name'")
recommendations.append("Configure Git: git config --global user.email 'your.email@domain.com'")
except (subprocess.CalledProcessError, FileNotFoundError):
setup_status['git_installed'] = False
recommendations.append("Install Git: https://git-scm.com/downloads")
# Check Jupyter installation
try:
jupyter_version = subprocess.run(['jupyter', '--version'],
capture_output=True, text=True, check=True)
setup_status['jupyter_installed'] = True
setup_status['jupyter_version'] = jupyter_version.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
setup_status['jupyter_installed'] = False
recommendations.append("Install Jupyter: pip install jupyter")
# Check Python tools
python_tools = ['pip', 'python']
for tool in python_tools:
try:
tool_version = subprocess.run([tool, '--version'],
capture_output=True, text=True, check=True)
setup_status[f'{tool}_installed'] = True
setup_status[f'{tool}_version'] = tool_version.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
setup_status[f'{tool}_installed'] = False
recommendations.append(f"Install {tool}: Check Python installation")
# Calculate setup health
total_tools = 4 # git, jupyter, pip, python
installed_tools = sum([
setup_status.get('git_installed', False),
setup_status.get('jupyter_installed', False),
setup_status.get('pip_installed', False),
setup_status.get('python_installed', False)
])
setup_score = round((installed_tools / total_tools) * 100, 1)
return {
'setup_status': setup_status,
'recommendations': recommendations,
'setup_score': setup_score,
'status': 'ready' if setup_score >= 75 else 'needs_configuration'
}
### END SOLUTION
# %% [markdown]
"""
## Step 7: Comprehensive System Report
### The Concept: Integrated System Analysis
**Comprehensive system reporting** combines all your configuration and diagnostic information into a single, actionable report. This is like a "health check" for your ML development environment.
### Why Comprehensive Reporting Matters
#### 1. **Holistic View**
- **Complete picture**: All system information in one place
- **Dependency analysis**: How different components interact
- **Performance context**: Understanding system capabilities
#### 2. **Troubleshooting Support**
- **Debugging aid**: Complete environment information for issue resolution
- **Performance analysis**: Identify bottlenecks and optimization opportunities
- **Compatibility checking**: Ensure all components work together
#### 3. **Professional Documentation**
- **Environment documentation**: Complete system specification
- **Reproducibility**: All information needed to recreate environment
- **Sharing**: Easy to share system information with collaborators
Let's create a comprehensive system report!
"""
# %% nbgrader={"grade": false, "grade_id": "system-report", "locked": false, "schema_version": 3, "solution": true, "task": false}
#| export
from datetime import datetime
def generate_system_report() -> Dict[str, Any]:
"""
Generate comprehensive system report for ML development.
This function combines all configuration and diagnostic information
into a single, actionable report for your ML development environment.
TODO: Implement comprehensive system reporting.
STEP-BY-STEP IMPLEMENTATION:
1. Gather personal information
2. Collect system information
3. Validate environment
4. Run performance benchmarks
5. Check development setup
6. Generate overall health score
7. Create comprehensive report with recommendations
REPORT SECTIONS:
- Personal configuration
- System specifications
- Environment validation
- Performance benchmarks
- Development setup
- Overall health assessment
- Recommendations for improvement
IMPLEMENTATION HINTS:
- Call all previously implemented functions
- Combine results into comprehensive report
- Calculate overall health score from all components
- Provide actionable recommendations
"""
### BEGIN SOLUTION
print("📊 Generating comprehensive system report...")
# Gather all information
personal = personal_info()
system = system_info()
environment = validate_environment()
performance = benchmark_performance()
development = setup_development_environment()
# Calculate overall health score (normalize performance score to 0-100 range)
normalized_performance = min(performance['overall_score'], 100) # Cap at 100
health_components = [
environment['health_score'],
normalized_performance,
development['setup_score']
]
overall_health = round(sum(health_components) / len(health_components), 1)
# Generate status
if overall_health >= 85:
status = 'excellent'
elif overall_health >= 70:
status = 'good'
elif overall_health >= 50:
status = 'fair'
else:
status = 'needs_attention'
# Compile recommendations
recommendations = []
if environment['health_score'] < 80:
recommendations.extend([f"Install missing package: {pkg}" for pkg in environment['missing_packages']])
if performance['overall_score'] < 50:
recommendations.append("Consider hardware upgrade for better ML performance")
recommendations.extend(development['recommendations'])
# Create comprehensive report
report = {
'timestamp': datetime.now().isoformat(),
'personal_info': personal,
'system_info': system,
'environment_validation': environment,
'performance_benchmarks': performance,
'development_setup': development,
'overall_health': overall_health,
'status': status,
'recommendations': recommendations,
'report_version': '1.0.0'
}
return report
### END SOLUTION
# %% [markdown]
"""
### 🧪 Unit Test: Enhanced Setup Functions
Test all the new enhanced setup functions:
"""
# Old function removed - using shared test runner pattern
# %%
def test_performance_benchmark():
"""Test performance benchmarking function."""
print("🔬 Unit Test: Performance Benchmarking...")
benchmark_report = benchmark_performance()
# Test return type and structure
assert isinstance(benchmark_report, dict), "benchmark_performance should return a dictionary"
# Test required keys
required_keys = ['cpu_time', 'cpu_score', 'memory_time', 'memory_score', 'overall_score', 'performance_rating']
for key in required_keys:
assert key in benchmark_report, f"Report should have '{key}' key"
# Test data types
assert isinstance(benchmark_report['cpu_time'], (int, float)), "cpu_time should be number"
assert isinstance(benchmark_report['cpu_score'], (int, float)), "cpu_score should be number"
assert isinstance(benchmark_report['memory_time'], (int, float)), "memory_time should be number"
assert isinstance(benchmark_report['memory_score'], (int, float)), "memory_score should be number"
assert isinstance(benchmark_report['overall_score'], (int, float)), "overall_score should be number"
assert isinstance(benchmark_report['performance_rating'], str), "performance_rating should be string"
# Test reasonable values
assert benchmark_report['cpu_time'] > 0, "cpu_time should be positive"
assert benchmark_report['memory_time'] > 0, "memory_time should be positive"
assert benchmark_report['cpu_score'] > 0, "cpu_score should be positive"
assert benchmark_report['memory_score'] > 0, "memory_score should be positive"
assert benchmark_report['overall_score'] > 0, "overall_score should be positive"
valid_ratings = ['excellent', 'good', 'fair', 'needs_optimization']
assert benchmark_report['performance_rating'] in valid_ratings, "performance_rating should be valid"
print("✅ Performance benchmark tests passed!")
print(f"✅ Performance rating: {benchmark_report['performance_rating']}")
# %%
def test_development_setup():
"""Test development environment setup function."""
print("🔬 Unit Test: Development Environment Setup...")
setup_report = setup_development_environment()
# Test return type and structure
assert isinstance(setup_report, dict), "setup_development_environment should return a dictionary"
# Test required keys
required_keys = ['setup_status', 'recommendations', 'setup_score', 'status']
for key in required_keys:
assert key in setup_report, f"Report should have '{key}' key"
# Test data types
assert isinstance(setup_report['setup_status'], dict), "setup_status should be dict"
assert isinstance(setup_report['recommendations'], list), "recommendations should be list"
assert isinstance(setup_report['setup_score'], (int, float)), "setup_score should be number"
assert isinstance(setup_report['status'], str), "status should be string"
# Test reasonable values
assert 0 <= setup_report['setup_score'] <= 100, "setup_score should be between 0 and 100"
assert setup_report['status'] in ['ready', 'needs_configuration'], "status should be valid"
print("✅ Development setup tests passed!")
print(f"✅ Setup score: {setup_report['setup_score']}%")
# %%
def test_system_report():
"""Test comprehensive system report function."""
print("🔬 Unit Test: System Report Generation...")
report = generate_system_report()
# Test return type and structure
assert isinstance(report, dict), "generate_system_report should return a dictionary"
# Test required keys
required_keys = ['timestamp', 'personal_info', 'system_info', 'environment_validation',
'performance_benchmarks', 'development_setup', 'overall_health',
'status', 'recommendations', 'report_version']
for key in required_keys:
assert key in report, f"Report should have '{key}' key"
# Test data types
assert isinstance(report['timestamp'], str), "timestamp should be string"
assert isinstance(report['personal_info'], dict), "personal_info should be dict"
assert isinstance(report['system_info'], dict), "system_info should be dict"
assert isinstance(report['environment_validation'], dict), "environment_validation should be dict"
assert isinstance(report['performance_benchmarks'], dict), "performance_benchmarks should be dict"
assert isinstance(report['development_setup'], dict), "development_setup should be dict"
assert isinstance(report['overall_health'], (int, float)), "overall_health should be number"
assert isinstance(report['status'], str), "status should be string"
assert isinstance(report['recommendations'], list), "recommendations should be list"
assert isinstance(report['report_version'], str), "report_version should be string"
# Test reasonable values
assert 0 <= report['overall_health'] <= 100, "overall_health should be between 0 and 100"
valid_statuses = ['excellent', 'good', 'fair', 'needs_attention']
assert report['status'] in valid_statuses, "status should be valid"
print("✅ System report tests passed!")
print(f"✅ Overall system health: {report['overall_health']}%")
# %%
def test_personal_info():
"""Test personal information function comprehensively."""
personal = personal_info()
assert isinstance(personal, dict), "personal_info should return a dictionary"
assert 'developer' in personal, "Dictionary should have 'developer' key"
assert '@' in personal['email'], "Email should contain @ symbol"
print("✅ Personal information function works")
def test_system_info():
"""Test system information function comprehensively."""
system = system_info()
assert isinstance(system, dict), "system_info should return a dictionary"
assert 'python_version' in system, "Dictionary should have 'python_version' key"
assert system['memory_gb'] > 0, "Memory should be positive"
print("✅ System information function works")
def test_environment_validation():
"""Test environment validation function comprehensively."""
env = validate_environment()
assert isinstance(env, dict), "validate_environment should return a dictionary"
assert 'health_score' in env, "Dictionary should have 'health_score' key"
print("✅ Environment validation function works")
def test_performance_benchmark():
"""Test performance benchmarking function comprehensively."""
perf = benchmark_performance()
assert isinstance(perf, dict), "benchmark_performance should return a dictionary"
assert 'cpu_score' in perf, "Dictionary should have 'cpu_score' key"
print("✅ Performance benchmarking function works")
def test_development_setup():
"""Test development setup function comprehensively."""
dev = setup_development_environment()
assert isinstance(dev, dict), "setup_development_environment should return a dictionary"
assert 'setup_score' in dev, "Dictionary should have 'setup_score' key"
print("✅ Development setup function works")
def test_system_report():
"""Test system report comprehensive function."""
report = generate_system_report()
assert isinstance(report, dict), "generate_system_report should return a dictionary"
assert 'overall_health' in report, "Dictionary should have 'overall_health' key"
print("✅ System report function works")
# %% [markdown]
"""
## 🧪 Module Testing
Time to test your implementation! This section uses TinyTorch's standardized testing framework to ensure your implementation works correctly.
**This testing section is locked** - it provides consistent feedback across all modules and cannot be modified.
"""
# %% nbgrader={"grade": false, "grade_id": "standardized-testing", "locked": true, "schema_version": 3, "solution": false, "task": false}
# =============================================================================
# STANDARDIZED MODULE TESTING - DO NOT MODIFY
# This cell is locked to ensure consistent testing across all TinyTorch modules
# =============================================================================
if __name__ == "__main__":
from tito.tools.testing import run_module_tests_auto
# Automatically discover and run all tests in this module
success = run_module_tests_auto("Setup")
# %% [markdown]
"""
## 🎯 Module Summary: Development Environment Setup Complete!
Congratulations! You've successfully set up your TinyTorch development environment:
### What You've Accomplished
✅ **Personal Configuration**: Developer information and preferences
✅ **System Analysis**: Hardware and software environment validation
✅ **Environment Validation**: Python packages and dependencies
✅ **Performance Benchmarking**: CPU and memory performance testing
✅ **Development Setup**: IDE configuration and tooling
✅ **Comprehensive Reporting**: System health and recommendations
### Key Concepts You've Learned
- **Environment Management**: How to validate and configure development environments
- **Performance Analysis**: Benchmarking system capabilities for ML workloads
- **System Diagnostics**: Comprehensive health checking and reporting
- **Development Best Practices**: Professional setup for ML development
### Next Steps
1. **Export your code**: `tito package nbdev --export 00_setup`
2. **Test your implementation**: `tito test 00_setup`
3. **Use your environment**: Start building with confidence in a validated setup
4. **Move to Module 1**: Begin implementing the core tensor system!
**Ready for the ML journey?** Your development environment is now optimized for building neural networks from scratch!
"""

View File

@@ -613,6 +613,35 @@ class Tensor:
return Tensor(result)
### END SOLUTION
def mean(self) -> 'Tensor':
"""Computes the mean of the tensor's elements."""
return Tensor(np.mean(self.data))
# --- Matmul ---
def matmul(self, other: 'Tensor') -> 'Tensor':
"""
Perform matrix multiplication between two tensors.
TODO: Implement matrix multiplication.
APPROACH:
1. Use np.matmul() to perform matrix multiplication
2. Return a new Tensor with the result
3. Handle broadcasting automatically
EXAMPLE:
Tensor([[1, 2], [3, 4]]) @ Tensor([[5, 6], [7, 8]]) → Tensor([[19, 22], [43, 50]])
HINTS:
- Use np.matmul(self._data, other._data)
- Return Tensor(result)
- This is matrix multiplication, not element-wise multiplication
"""
### BEGIN SOLUTION
result = np.matmul(self._data, other._data)
return Tensor(result)
### END SOLUTION
# %% [markdown]
"""
### 🧪 Unit Test: Tensor Creation
@@ -755,798 +784,6 @@ print(" Returns new Tensor objects")
# %% [markdown]
"""
### 🧪 Comprehensive Test: Tensor Creation
Let's thoroughly test your tensor creation to make sure it handles all the cases you'll encounter in ML.
This tests the foundation of everything else we'll build.
"""
# %% nbgrader={"grade": true, "grade_id": "test-tensor-creation-comprehensive", "locked": true, "points": 15, "schema_version": 3, "solution": false, "task": false}
def test_tensor_creation():
"""Comprehensive test of tensor creation with all data types and shapes."""
print("🔬 Testing comprehensive tensor creation...")
tests_passed = 0
total_tests = 8
# Test 1: Scalar creation (0D tensor)
try:
scalar_int = Tensor(42)
scalar_float = Tensor(3.14)
scalar_zero = Tensor(0)
assert hasattr(scalar_int, '_data'), "Tensor should have _data attribute"
assert scalar_int._data.shape == (), f"Scalar should have shape (), got {scalar_int._data.shape}"
assert scalar_float._data.shape == (), f"Float scalar should have shape (), got {scalar_float._data.shape}"
assert scalar_zero._data.shape == (), f"Zero scalar should have shape (), got {scalar_zero._data.shape}"
print("✅ Scalar creation: integers, floats, and zero")
tests_passed += 1
except Exception as e:
print(f"❌ Scalar creation failed: {e}")
# Test 2: Vector creation (1D tensor)
try:
vector_int = Tensor([1, 2, 3, 4, 5])
vector_float = Tensor([1.0, 2.5, 3.7])
vector_single = Tensor([42])
vector_empty = Tensor([])
assert vector_int._data.shape == (5,), f"Int vector should have shape (5,), got {vector_int._data.shape}"
assert vector_float._data.shape == (3,), f"Float vector should have shape (3,), got {vector_float._data.shape}"
assert vector_single._data.shape == (1,), f"Single element vector should have shape (1,), got {vector_single._data.shape}"
assert vector_empty._data.shape == (0,), f"Empty vector should have shape (0,), got {vector_empty._data.shape}"
print("✅ Vector creation: integers, floats, single element, and empty")
tests_passed += 1
except Exception as e:
print(f"❌ Vector creation failed: {e}")
# Test 3: Matrix creation (2D tensor)
try:
matrix_2x2 = Tensor([[1, 2], [3, 4]])
matrix_3x2 = Tensor([[1, 2], [3, 4], [5, 6]])
matrix_1x3 = Tensor([[1, 2, 3]])
assert matrix_2x2._data.shape == (2, 2), f"2x2 matrix should have shape (2, 2), got {matrix_2x2._data.shape}"
assert matrix_3x2._data.shape == (3, 2), f"3x2 matrix should have shape (3, 2), got {matrix_3x2._data.shape}"
assert matrix_1x3._data.shape == (1, 3), f"1x3 matrix should have shape (1, 3), got {matrix_1x3._data.shape}"
print("✅ Matrix creation: 2x2, 3x2, and 1x3 matrices")
tests_passed += 1
except Exception as e:
print(f"❌ Matrix creation failed: {e}")
# Test 4: Data type handling
try:
int_tensor = Tensor([1, 2, 3])
float_tensor = Tensor([1.0, 2.0, 3.0])
mixed_tensor = Tensor([1, 2.5, 3]) # Should convert to float
# Check that data types are reasonable
assert int_tensor._data.dtype in [np.int32, np.int64], f"Int tensor has unexpected dtype: {int_tensor._data.dtype}"
assert float_tensor._data.dtype in [np.float32, np.float64], f"Float tensor has unexpected dtype: {float_tensor._data.dtype}"
assert mixed_tensor._data.dtype in [np.float32, np.float64], f"Mixed tensor should be float, got: {mixed_tensor._data.dtype}"
print("✅ Data type handling: integers, floats, and mixed types")
tests_passed += 1
except Exception as e:
print(f"❌ Data type handling failed: {e}")
# Test 5: NumPy array input
try:
np_array = np.array([1, 2, 3, 4])
tensor_from_np = Tensor(np_array)
assert tensor_from_np._data.shape == (4,), f"Tensor from NumPy should have shape (4,), got {tensor_from_np._data.shape}"
assert np.array_equal(tensor_from_np._data, np_array), "Tensor from NumPy should preserve data"
print("✅ NumPy array input: conversion works correctly")
tests_passed += 1
except Exception as e:
print(f"❌ NumPy array input failed: {e}")
# Test 6: Large tensor creation
try:
large_tensor = Tensor(list(range(1000)))
assert large_tensor._data.shape == (1000,), f"Large tensor should have shape (1000,), got {large_tensor._data.shape}"
assert large_tensor._data[0] == 0, "Large tensor should start with 0"
assert large_tensor._data[-1] == 999, "Large tensor should end with 999"
print("✅ Large tensor creation: 1000 elements")
tests_passed += 1
except Exception as e:
print(f"❌ Large tensor creation failed: {e}")
# Test 7: Negative numbers
try:
negative_tensor = Tensor([-1, -2, -3])
mixed_signs = Tensor([-1, 0, 1])
assert negative_tensor._data.shape == (3,), f"Negative tensor should have shape (3,), got {negative_tensor._data.shape}"
assert np.array_equal(negative_tensor._data, np.array([-1, -2, -3])), "Negative numbers should be preserved"
assert np.array_equal(mixed_signs._data, np.array([-1, 0, 1])), "Mixed signs should be preserved"
print("✅ Negative numbers: handled correctly")
tests_passed += 1
except Exception as e:
print(f"❌ Negative numbers failed: {e}")
# Test 8: Edge cases
try:
# Very large numbers
big_tensor = Tensor([1e6, 1e-6])
assert big_tensor._data.shape == (2,), "Big numbers tensor should have correct shape"
# Zero tensor
zero_tensor = Tensor([0, 0, 0])
assert np.all(zero_tensor._data == 0), "Zero tensor should contain all zeros"
print("✅ Edge cases: large numbers and zeros")
tests_passed += 1
except Exception as e:
print(f"❌ Edge cases failed: {e}")
# Results summary
print(f"\n📊 Tensor Creation Results: {tests_passed}/{total_tests} tests passed")
if tests_passed == total_tests:
print("🎉 All tensor creation tests passed! Your Tensor class can handle:")
print(" • Scalars, vectors, and matrices")
print(" • Different data types (int, float)")
print(" • NumPy arrays")
print(" • Large tensors and edge cases")
print("📈 Progress: Tensor Creation ✓")
return True
else:
print("⚠️ Some tensor creation tests failed. Common issues:")
print(" • Check your __init__ method implementation")
print(" • Make sure you're storing data in self._data")
print(" • Verify NumPy array conversion works correctly")
print(" • Test with different input types (int, float, list, np.array)")
return False
# Run the comprehensive test
success = test_tensor_creation()
# %% [markdown]
"""
### 🧪 Comprehensive Test: Tensor Properties
Now let's test all the properties your tensor should have. These properties are essential for ML operations.
"""
# %% nbgrader={"grade": true, "grade_id": "test-tensor-properties-comprehensive", "locked": true, "points": 15, "schema_version": 3, "solution": false, "task": false}
def test_tensor_properties():
"""Comprehensive test of tensor properties (shape, size, dtype, data access)."""
print("🔬 Testing comprehensive tensor properties...")
tests_passed = 0
total_tests = 6
# Test 1: Shape property
try:
scalar = Tensor(5.0)
vector = Tensor([1, 2, 3])
matrix = Tensor([[1, 2], [3, 4]])
tensor_3d = Tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
assert scalar.shape == (), f"Scalar shape should be (), got {scalar.shape}"
assert vector.shape == (3,), f"Vector shape should be (3,), got {vector.shape}"
assert matrix.shape == (2, 2), f"Matrix shape should be (2, 2), got {matrix.shape}"
assert tensor_3d.shape == (2, 2, 2), f"3D tensor shape should be (2, 2, 2), got {tensor_3d.shape}"
print("✅ Shape property: scalar, vector, matrix, and 3D tensor")
tests_passed += 1
except Exception as e:
print(f"❌ Shape property failed: {e}")
# Test 2: Size property
try:
scalar = Tensor(5.0)
vector = Tensor([1, 2, 3])
matrix = Tensor([[1, 2], [3, 4]])
empty = Tensor([])
assert scalar.size == 1, f"Scalar size should be 1, got {scalar.size}"
assert vector.size == 3, f"Vector size should be 3, got {vector.size}"
assert matrix.size == 4, f"Matrix size should be 4, got {matrix.size}"
assert empty.size == 0, f"Empty tensor size should be 0, got {empty.size}"
print("✅ Size property: scalar, vector, matrix, and empty tensor")
tests_passed += 1
except Exception as e:
print(f"❌ Size property failed: {e}")
# Test 3: Data type property
try:
int_tensor = Tensor([1, 2, 3])
float_tensor = Tensor([1.0, 2.0, 3.0])
# Check that dtype is accessible and reasonable
assert hasattr(int_tensor, 'dtype'), "Tensor should have dtype property"
assert hasattr(float_tensor, 'dtype'), "Tensor should have dtype property"
# Data types should be NumPy dtypes
assert isinstance(int_tensor.dtype, np.dtype), f"dtype should be np.dtype, got {type(int_tensor.dtype)}"
assert isinstance(float_tensor.dtype, np.dtype), f"dtype should be np.dtype, got {type(float_tensor.dtype)}"
print(f"✅ Data type property: int tensor is {int_tensor.dtype}, float tensor is {float_tensor.dtype}")
tests_passed += 1
except Exception as e:
print(f"❌ Data type property failed: {e}")
# Test 4: Data access property
try:
scalar = Tensor(5.0)
vector = Tensor([1, 2, 3])
matrix = Tensor([[1, 2], [3, 4]])
# Test data access
assert hasattr(scalar, 'data'), "Tensor should have data property"
assert hasattr(vector, 'data'), "Tensor should have data property"
assert hasattr(matrix, 'data'), "Tensor should have data property"
# Test data content
assert scalar.data.item() == 5.0, f"Scalar data should be 5.0, got {scalar.data.item()}"
assert np.array_equal(vector.data, np.array([1, 2, 3])), "Vector data mismatch"
assert np.array_equal(matrix.data, np.array([[1, 2], [3, 4]])), "Matrix data mismatch"
print("✅ Data access: scalar, vector, and matrix data retrieval")
tests_passed += 1
except Exception as e:
print(f"❌ Data access failed: {e}")
# Test 5: String representation
try:
scalar = Tensor(5.0)
vector = Tensor([1, 2, 3])
# Test that __repr__ works
scalar_str = str(scalar)
vector_str = str(vector)
assert isinstance(scalar_str, str), "Tensor string representation should be a string"
assert isinstance(vector_str, str), "Tensor string representation should be a string"
assert len(scalar_str) > 0, "Tensor string representation should not be empty"
assert len(vector_str) > 0, "Tensor string representation should not be empty"
print(f"✅ String representation: scalar={scalar_str[:50]}{'...' if len(scalar_str) > 50 else ''}")
tests_passed += 1
except Exception as e:
print(f"❌ String representation failed: {e}")
# Test 6: Property consistency
try:
test_cases = [
Tensor(42),
Tensor([1, 2, 3, 4, 5]),
Tensor([[1, 2, 3], [4, 5, 6]]),
Tensor([])
]
for i, tensor in enumerate(test_cases):
# Size should equal product of shape
expected_size = np.prod(tensor.shape) if tensor.shape else 1
assert tensor.size == expected_size, f"Test case {i}: size {tensor.size} doesn't match shape {tensor.shape}"
# Data shape should match tensor shape
assert tensor.data.shape == tensor.shape, f"Test case {i}: data shape {tensor.data.shape} doesn't match tensor shape {tensor.shape}"
print("✅ Property consistency: size matches shape, data shape matches tensor shape")
tests_passed += 1
except Exception as e:
print(f"❌ Property consistency failed: {e}")
# Results summary
print(f"\n📊 Tensor Properties Results: {tests_passed}/{total_tests} tests passed")
if tests_passed == total_tests:
print("🎉 All tensor property tests passed! Your tensor has:")
print(" • Correct shape property for all dimensions")
print(" • Accurate size calculation")
print(" • Proper data type handling")
print(" • Working data access")
print(" • Good string representation")
print("📈 Progress: Tensor Creation ✓, Properties ✓")
return True
else:
print("⚠️ Some property tests failed. Common issues:")
print(" • Check your @property decorators")
print(" • Verify shape returns self._data.shape")
print(" • Make sure size returns self._data.size")
print(" • Ensure dtype returns self._data.dtype")
print(" • Test your __repr__ method")
return False
# Run the comprehensive test
success = test_tensor_properties() and success
# %% [markdown]
"""
### 🧪 Comprehensive Test: Tensor Arithmetic
Let's test all arithmetic operations. These are the foundation of neural network computations!
"""
# %% nbgrader={"grade": true, "grade_id": "test-tensor-arithmetic-comprehensive", "locked": true, "points": 20, "schema_version": 3, "solution": false, "task": false}
def test_tensor_arithmetic():
"""Comprehensive test of tensor arithmetic operations."""
print("🔬 Testing comprehensive tensor arithmetic...")
tests_passed = 0
total_tests = 8
# Test 1: Basic addition method
try:
a = Tensor([1, 2, 3])
b = Tensor([4, 5, 6])
c = a.add(b)
expected = np.array([5, 7, 9])
assert np.array_equal(c.data, expected), f"Addition method failed: expected {expected}, got {c.data}"
assert isinstance(c, Tensor), "Addition should return a Tensor"
print(f"✅ Addition method: {a.data} + {b.data} = {c.data}")
tests_passed += 1
except Exception as e:
print(f"❌ Addition method failed: {e}")
# Test 2: Basic multiplication method
try:
a = Tensor([1, 2, 3])
b = Tensor([4, 5, 6])
c = a.multiply(b)
expected = np.array([4, 10, 18])
assert np.array_equal(c.data, expected), f"Multiplication method failed: expected {expected}, got {c.data}"
assert isinstance(c, Tensor), "Multiplication should return a Tensor"
print(f"✅ Multiplication method: {a.data} * {b.data} = {c.data}")
tests_passed += 1
except Exception as e:
print(f"❌ Multiplication method failed: {e}")
# Test 3: Addition operator (+)
try:
a = Tensor([1, 2, 3])
b = Tensor([4, 5, 6])
c = a + b
expected = np.array([5, 7, 9])
assert np.array_equal(c.data, expected), f"+ operator failed: expected {expected}, got {c.data}"
assert isinstance(c, Tensor), "+ operator should return a Tensor"
print(f"✅ + operator: {a.data} + {b.data} = {c.data}")
tests_passed += 1
except Exception as e:
print(f"❌ + operator failed: {e}")
# Test 4: Multiplication operator (*)
try:
a = Tensor([1, 2, 3])
b = Tensor([4, 5, 6])
c = a * b
expected = np.array([4, 10, 18])
assert np.array_equal(c.data, expected), f"* operator failed: expected {expected}, got {c.data}"
assert isinstance(c, Tensor), "* operator should return a Tensor"
print(f"✅ * operator: {a.data} * {b.data} = {c.data}")
tests_passed += 1
except Exception as e:
print(f"❌ * operator failed: {e}")
# Test 5: Subtraction operator (-)
try:
a = Tensor([1, 2, 3])
b = Tensor([4, 5, 6])
c = b - a
expected = np.array([3, 3, 3])
assert np.array_equal(c.data, expected), f"- operator failed: expected {expected}, got {c.data}"
assert isinstance(c, Tensor), "- operator should return a Tensor"
print(f"✅ - operator: {b.data} - {a.data} = {c.data}")
tests_passed += 1
except Exception as e:
print(f"❌ - operator failed: {e}")
# Test 6: Division operator (/)
try:
a = Tensor([1, 2, 4])
b = Tensor([2, 4, 8])
c = b / a
expected = np.array([2.0, 2.0, 2.0])
assert np.allclose(c.data, expected), f"/ operator failed: expected {expected}, got {c.data}"
assert isinstance(c, Tensor), "/ operator should return a Tensor"
print(f"✅ / operator: {b.data} / {a.data} = {c.data}")
tests_passed += 1
except Exception as e:
print(f"❌ / operator failed: {e}")
# Test 7: Scalar operations
try:
a = Tensor([1, 2, 3])
# Addition with scalar
b = a + 10
expected_add = np.array([11, 12, 13])
assert np.array_equal(b.data, expected_add), f"Scalar addition failed: expected {expected_add}, got {b.data}"
# Multiplication with scalar
c = a * 2
expected_mul = np.array([2, 4, 6])
assert np.array_equal(c.data, expected_mul), f"Scalar multiplication failed: expected {expected_mul}, got {c.data}"
# Subtraction with scalar
d = a - 1
expected_sub = np.array([0, 1, 2])
assert np.array_equal(d.data, expected_sub), f"Scalar subtraction failed: expected {expected_sub}, got {d.data}"
# Division with scalar
e = a / 2
expected_div = np.array([0.5, 1.0, 1.5])
assert np.allclose(e.data, expected_div), f"Scalar division failed: expected {expected_div}, got {e.data}"
print(f"✅ Scalar operations: +10, *2, -1, /2 all work correctly")
tests_passed += 1
except Exception as e:
print(f"❌ Scalar operations failed: {e}")
# Test 8: Matrix operations
try:
matrix_a = Tensor([[1, 2], [3, 4]])
matrix_b = Tensor([[5, 6], [7, 8]])
# Matrix addition
c = matrix_a + matrix_b
expected = np.array([[6, 8], [10, 12]])
assert np.array_equal(c.data, expected), f"Matrix addition failed: expected {expected}, got {c.data}"
assert c.shape == (2, 2), f"Matrix addition should preserve shape, got {c.shape}"
# Matrix multiplication (element-wise)
d = matrix_a * matrix_b
expected_mul = np.array([[5, 12], [21, 32]])
assert np.array_equal(d.data, expected_mul), f"Matrix multiplication failed: expected {expected_mul}, got {d.data}"
print(f"✅ Matrix operations: 2x2 matrix addition and multiplication")
tests_passed += 1
except Exception as e:
print(f"❌ Matrix operations failed: {e}")
# Results summary
print(f"\n📊 Tensor Arithmetic Results: {tests_passed}/{total_tests} tests passed")
if tests_passed == total_tests:
print("🎉 All tensor arithmetic tests passed! Your tensor supports:")
print(" • Basic methods: add(), multiply()")
print(" • Python operators: +, -, *, /")
print(" • Scalar operations: tensor + number")
print(" • Matrix operations: element-wise operations")
print("📈 Progress: Tensor Creation ✓, Properties ✓, Arithmetic ✓")
return True
else:
print("⚠️ Some arithmetic tests failed. Common issues:")
print(" • Check your add() and multiply() methods")
print(" • Verify operator overloading (__add__, __mul__, __sub__, __truediv__)")
print(" • Make sure scalar operations work (convert scalar to Tensor)")
print(" • Test with different tensor shapes")
return False
# Run the comprehensive test
success = test_tensor_arithmetic() and success
# %% [markdown]
"""
### 🧪 Comprehensive Test: Real ML Scenario
Let's test your tensor with a realistic machine learning scenario to make sure everything works together.
"""
# %% nbgrader={"grade": true, "grade_id": "test-tensor-comprehensive", "locked": true, "points": 10, "schema_version": 3, "solution": false, "task": false}
def test_tensor():
"""Comprehensive test with realistic ML scenario."""
print("🔬 Testing tensor comprehensively with ML scenario...")
try:
print("🧠 Simulating a simple neural network forward pass...")
# Simulate input data (batch of 2 samples, 3 features each)
X = Tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
print(f"📊 Input data shape: {X.shape}")
# Simulate weights (3 input features, 2 output neurons)
W = Tensor([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]])
print(f"🎯 Weights shape: {W.shape}")
# Simulate bias (2 output neurons)
b = Tensor([0.1, 0.2])
print(f"⚖️ Bias shape: {b.shape}")
# Simple linear transformation: y = X * W + b
# Note: This is a simplified version - real matrix multiplication would be different
# But we can test element-wise operations
# Test that we can do basic operations needed for ML
sample = Tensor([1.0, 2.0, 3.0]) # Single sample
weight_col = Tensor([0.1, 0.3, 0.5]) # First column of weights
# Compute dot product manually using element-wise operations
products = sample * weight_col # Element-wise multiplication
print(f"✅ Element-wise multiplication works: {products.data}")
# Test addition for bias
result = products + Tensor([0.1, 0.1, 0.1])
print(f"✅ Bias addition works: {result.data}")
# Test with different shapes
matrix_a = Tensor([[1, 2], [3, 4]])
matrix_b = Tensor([[0.1, 0.2], [0.3, 0.4]])
matrix_result = matrix_a * matrix_b
print(f"✅ Matrix operations work: {matrix_result.data}")
# Test scalar operations (common in ML)
scaled = sample * 0.5 # Learning rate scaling
print(f"✅ Scalar scaling works: {scaled.data}")
# Test normalization-like operations
mean_val = Tensor([2.0, 2.0, 2.0]) # Simulate mean
normalized = sample - mean_val
print(f"✅ Mean subtraction works: {normalized.data}")
print("\n🎉 Comprehensive test passed! Your tensor class can handle:")
print(" • Multi-dimensional data (batches, features)")
print(" • Element-wise operations needed for ML")
print(" • Scalar operations (learning rates, normalization)")
print(" • Matrix operations (weights, transformations)")
print("📈 Progress: All tensor functionality ✓")
print("🚀 Ready for neural network layers!")
return True
except Exception as e:
print(f"❌ Comprehensive test failed: {e}")
print("\n💡 This suggests an issue with:")
print(" • Basic tensor operations not working together")
print(" • Shape handling problems")
print(" • Arithmetic operation implementation")
print(" • Check your tensor creation and arithmetic methods")
return False
# Run the comprehensive test
success = test_tensor() and success
# Print final summary
print(f"\n{'='*60}")
print("🎯 TENSOR MODULE TESTING COMPLETE")
print(f"{'='*60}")
if success:
print("🎉 CONGRATULATIONS! All tensor tests passed!")
print("\n✅ Your Tensor class successfully implements:")
print(" • Comprehensive tensor creation (scalars, vectors, matrices)")
print(" • All essential properties (shape, size, dtype, data access)")
print(" • Complete arithmetic operations (methods and operators)")
print(" • Scalar and matrix operations")
print(" • Real ML scenario compatibility")
print("\n🚀 You're ready to move to the next module!")
print("📈 Final Progress: Tensor Module ✓ COMPLETE")
else:
print("⚠️ Some tests failed. Please review the error messages above.")
print("\n🔧 To fix issues:")
print(" 1. Check the specific test that failed")
print(" 2. Review the error message and hints")
print(" 3. Fix your implementation")
print(" 4. Re-run the notebook cells")
print("\n💪 Don't give up! Debugging is part of learning.")
# %% [markdown]
"""
## Step 3: Tensor Arithmetic Operations
### Why Arithmetic Matters
Tensor arithmetic is the foundation of all neural network operations:
- **Forward pass**: Matrix multiplications and additions
- **Activation functions**: Element-wise operations
- **Loss computation**: Differences and squares
- **Gradient computation**: Chain rule applications
### Operations We'll Implement
- **Addition**: Element-wise addition of tensors
- **Multiplication**: Element-wise multiplication
- **Python operators**: `+`, `-`, `*`, `/` for natural syntax
- **Broadcasting**: Handle different shapes automatically
"""
# %% [markdown]
"""
## Step 3: Tensor Arithmetic Methods
The arithmetic methods are now part of the Tensor class above. Let's test them!
"""
# %% [markdown]
"""
## Step 4: Python Operator Overloading
### Why Operator Overloading?
Python's magic methods allow us to use natural syntax:
- `a + b` instead of `a.add(b)`
- `a * b` instead of `a.multiply(b)`
- `a - b` for subtraction
- `a / b` for division
This makes tensor operations feel natural and readable.
"""
# %% [markdown]
"""
## Step 4: Operator Overloading
The operator methods (__add__, __mul__, __sub__, __truediv__) are now part of the Tensor class above. This enables natural syntax like `a + b` and `a * b`.
"""
# %% [markdown]
"""
### 🧪 Test Your Tensor Implementation
Once you implement the Tensor class above, run these cells to test your implementation:
"""
# %% nbgrader={"grade": true, "grade_id": "test-tensor-creation", "locked": true, "points": 25, "schema_version": 3, "solution": false, "task": false}
# Test tensor creation and properties
print("🔬 Unit Test: Tensor Creation...")
# Test scalar creation
scalar = Tensor(5.0)
assert scalar.shape == (), f"Scalar shape should be (), got {scalar.shape}"
assert scalar.size == 1, f"Scalar size should be 1, got {scalar.size}"
assert scalar.data.item() == 5.0, f"Scalar value should be 5.0, got {scalar.data.item()}"
# Test vector creation
vector = Tensor([1, 2, 3])
assert vector.shape == (3,), f"Vector shape should be (3,), got {vector.shape}"
assert vector.size == 3, f"Vector size should be 3, got {vector.size}"
assert np.array_equal(vector.data, np.array([1, 2, 3])), "Vector data mismatch"
# Test matrix creation
matrix = Tensor([[1, 2], [3, 4]])
assert matrix.shape == (2, 2), f"Matrix shape should be (2, 2), got {matrix.shape}"
assert matrix.size == 4, f"Matrix size should be 4, got {matrix.size}"
assert np.array_equal(matrix.data, np.array([[1, 2], [3, 4]])), "Matrix data mismatch"
# Test dtype handling
float_tensor = Tensor([1.0, 2.0, 3.0])
assert float_tensor.dtype == np.float32, f"Float tensor dtype should be float32, got {float_tensor.dtype}"
int_tensor = Tensor([1, 2, 3])
# Note: NumPy may default to int64 on some systems, so we check for integer types
assert int_tensor.dtype in [np.int32, np.int64], f"Int tensor dtype should be int32 or int64, got {int_tensor.dtype}"
print("✅ Tensor creation tests passed!")
print(f"✅ Scalar: {scalar}")
print(f"✅ Vector: {vector}")
print(f"✅ Matrix: {matrix}")
# %% nbgrader={"grade": true, "grade_id": "test-tensor-arithmetic", "locked": true, "points": 25, "schema_version": 3, "solution": false, "task": false}
# Test tensor arithmetic operations
print("🔬 Unit Test: Tensor Arithmetic...")
# Test addition
a = Tensor([1, 2, 3])
b = Tensor([4, 5, 6])
c = a + b
expected = np.array([5, 7, 9])
assert np.array_equal(c.data, expected), f"Addition failed: expected {expected}, got {c.data}"
# Test multiplication
d = a * b
expected = np.array([4, 10, 18])
assert np.array_equal(d.data, expected), f"Multiplication failed: expected {expected}, got {d.data}"
# Test subtraction
e = b - a
expected = np.array([3, 3, 3])
assert np.array_equal(e.data, expected), f"Subtraction failed: expected {expected}, got {e.data}"
# Test division
f = b / a
expected = np.array([4.0, 2.5, 2.0])
assert np.allclose(f.data, expected), f"Division failed: expected {expected}, got {f.data}"
# Test scalar operations
g = a + 10
expected = np.array([11, 12, 13])
assert np.array_equal(g.data, expected), f"Scalar addition failed: expected {expected}, got {g.data}"
h = a * 2
expected = np.array([2, 4, 6])
assert np.array_equal(h.data, expected), f"Scalar multiplication failed: expected {expected}, got {h.data}"
print("✅ Tensor arithmetic tests passed!")
print(f"✅ Addition: {a} + {b} = {c}")
print(f"✅ Multiplication: {a} * {b} = {d}")
print(f"✅ Subtraction: {b} - {a} = {e}")
print(f"✅ Division: {b} / {a} = {f}")
# %% nbgrader={"grade": true, "grade_id": "test-tensor-broadcasting", "locked": true, "points": 25, "schema_version": 3, "solution": false, "task": false}
# Test tensor broadcasting
print("🔬 Unit Test: Tensor Broadcasting...")
# Test scalar broadcasting
matrix = Tensor([[1, 2], [3, 4]])
scalar = Tensor(10)
result = matrix + scalar
expected = np.array([[11, 12], [13, 14]])
assert np.array_equal(result.data, expected), f"Scalar broadcasting failed: expected {expected}, got {result.data}"
# Test vector broadcasting
vector = Tensor([1, 2])
result = matrix + vector
expected = np.array([[2, 4], [4, 6]])
assert np.array_equal(result.data, expected), f"Vector broadcasting failed: expected {expected}, got {result.data}"
# Test different shapes
a = Tensor([[1], [2], [3]]) # (3, 1)
b = Tensor([10, 20]) # (2,)
result = a + b
expected = np.array([[11, 21], [12, 22], [13, 23]])
assert np.array_equal(result.data, expected), f"Shape broadcasting failed: expected {expected}, got {result.data}"
print("✅ Tensor broadcasting tests passed!")
print(f"✅ Matrix + Scalar: {matrix} + {scalar} = {result}")
print(f"✅ Broadcasting works correctly!")
# %% [markdown]
"""
## 🎯 Module Summary
Congratulations! You've successfully implemented the core Tensor class for TinyTorch:
### What You've Accomplished
✅ **Tensor Creation**: Handle scalars, vectors, matrices, and higher-dimensional arrays
✅ **Data Types**: Proper dtype handling with auto-detection and conversion
✅ **Properties**: Shape, size, dtype, and data access
✅ **Arithmetic**: Addition, multiplication, subtraction, division
✅ **Operators**: Natural Python syntax with `+`, `-`, `*`, `/`
✅ **Broadcasting**: Automatic shape compatibility like NumPy
### Key Concepts You've Learned
- **Tensors** are the fundamental data structure for ML systems
- **NumPy backend** provides efficient computation with ML-friendly API
- **Operator overloading** makes tensor operations feel natural
- **Broadcasting** enables flexible operations between different shapes
- **Type safety** ensures consistent behavior across operations
"""
# %% [markdown]
"""
## 🧪 Module Testing
Time to test your implementation! This section uses TinyTorch's standardized testing framework to ensure your implementation works correctly.
**This testing section is locked** - it provides consistent feedback across all modules and cannot be modified.
"""
# %% nbgrader={"grade": false, "grade_id": "standardized-testing", "locked": true, "schema_version": 3, "solution": false, "task": false}
# =============================================================================
# STANDARDIZED MODULE TESTING - DO NOT MODIFY
# This cell is locked to ensure consistent testing across all TinyTorch modules
# =============================================================================
if __name__ == "__main__":
from tito.tools.testing import run_module_tests_auto
# Automatically discover and run all tests in this module
success = run_module_tests_auto("Tensor")
# %% [markdown]
"""
## 🎯 Module Summary
Congratulations! You've successfully implemented the core Tensor class for TinyTorch:
### What You've Accomplished
@@ -1576,4 +813,68 @@ Congratulations! You've successfully implemented the core Tensor class for TinyT
4. **Move to Module 2**: Start building activation functions!
**Ready for the next challenge?** Let's add the mathematical functions that make neural networks powerful!
"""
"""
def test_unit_tensor_creation():
"""Comprehensive test of tensor creation with all data types and shapes."""
print("🔬 Testing comprehensive tensor creation...")
# Test scalar creation
scalar_int = Tensor(42)
assert scalar_int.shape == ()
# Test vector creation
vector_int = Tensor([1, 2, 3])
assert vector_int.shape == (3,)
# Test matrix creation
matrix_2x2 = Tensor([[1, 2], [3, 4]])
assert matrix_2x2.shape == (2, 2)
print("✅ Tensor creation tests passed!")
def test_unit_tensor_properties():
"""Comprehensive test of tensor properties (shape, size, dtype, data access)."""
print("🔬 Testing comprehensive tensor properties...")
tensor = Tensor([[1, 2, 3], [4, 5, 6]])
# Test shape property
assert tensor.shape == (2, 3)
# Test size property
assert tensor.size == 6
# Test data property
assert np.array_equal(tensor.data, np.array([[1, 2, 3], [4, 5, 6]]))
# Test dtype property
assert tensor.dtype in [np.int32, np.int64]
print("✅ Tensor properties tests passed!")
def test_unit_tensor_arithmetic():
"""Comprehensive test of tensor arithmetic operations."""
print("🔬 Testing comprehensive tensor arithmetic...")
a = Tensor([1, 2, 3])
b = Tensor([4, 5, 6])
# Test addition
c = a + b
expected = np.array([5, 7, 9])
assert np.array_equal(c.data, expected)
# Test multiplication
d = a * b
expected = np.array([4, 10, 18])
assert np.array_equal(d.data, expected)
# Test subtraction
e = b - a
expected = np.array([3, 3, 3])
assert np.array_equal(e.data, expected)
# Test division
f = b / a
expected = np.array([4.0, 2.5, 2.0])
assert np.allclose(f.data, expected)
print("✅ Tensor arithmetic tests passed!")