Files
TinyTorch/tinytorch/core/tensor.py
Vijay Janapa Reddi 7c6f0e5681 Complete migration from modules/ to assignments/source/ structure
- Migrated all Python source files to assignments/source/ structure
- Updated nbdev configuration to use assignments/source as nbs_path
- Updated all tito commands (nbgrader, export, test) to use new structure
- Fixed hardcoded paths in Python files and documentation
- Updated config.py to use assignments/source instead of modules
- Fixed test command to use correct file naming (short names vs full module names)
- Regenerated all notebook files with clean metadata
- Verified complete workflow: Python source → NBGrader → nbdev export → testing

All systems now working: NBGrader (14 source assignments, 1 released), nbdev export (7 generated files), and pytest integration.

The modules/ directory has been retired and replaced with standard NBGrader structure.
2025-07-12 12:06:56 -04:00

602 lines
22 KiB
Python

# AUTOGENERATED! DO NOT EDIT! File to edit: ../../assignments/source/01_tensor/tensor_dev.ipynb.
# %% auto 0
__all__ = ['Tensor', 'add_tensors', 'multiply_tensors']
# %% ../../assignments/source/01_tensor/tensor_dev.ipynb 4
class Tensor:
"""
TinyTorch Tensor: N-dimensional array with ML operations.
The fundamental data structure for all TinyTorch operations.
Wraps NumPy arrays with ML-specific functionality.
TODO: Implement the core Tensor class with data handling and properties.
APPROACH:
1. Store the input data as a NumPy array internally
2. Handle different input types (scalars, lists, numpy arrays)
3. Implement properties to access shape, size, and data type
4. Create a clear string representation
EXAMPLE:
Input: Tensor([1, 2, 3])
Expected: Tensor with shape (3,), size 3, dtype int32
HINTS:
- Use NumPy's np.array() to convert inputs
- Handle dtype parameter for type conversion
- Store the array in a private attribute like self._data
- Properties should return information about the stored array
"""
def __init__(self, data: Union[int, float, List, np.ndarray], dtype: Optional[str] = None):
"""
Create a new tensor from data.
Args:
data: Input data (scalar, list, or numpy array)
dtype: Data type ('float32', 'int32', etc.). Defaults to auto-detect.
TODO: Implement tensor creation with proper type handling.
STEP-BY-STEP:
1. Check if data is a scalar (int/float) - convert to numpy array
2. Check if data is a list - convert to numpy array
3. Check if data is already a numpy array - use as-is
4. Apply dtype conversion if specified
5. Store the result in self._data
EXAMPLE:
Tensor(5) → stores np.array(5)
Tensor([1, 2, 3]) → stores np.array([1, 2, 3])
Tensor(np.array([1, 2, 3])) → stores the array directly
"""
raise NotImplementedError("Student implementation required")
@property
def data(self) -> np.ndarray:
"""
Access underlying numpy array.
TODO: Return the stored numpy array.
HINT: Return self._data (the array you stored in __init__)
"""
raise NotImplementedError("Student implementation required")
@property
def shape(self) -> Tuple[int, ...]:
"""
Get tensor shape.
TODO: Return the shape of the stored numpy array.
HINT: Use .shape attribute of the numpy array
EXAMPLE: Tensor([1, 2, 3]).shape should return (3,)
"""
raise NotImplementedError("Student implementation required")
@property
def size(self) -> int:
"""
Get total number of elements.
TODO: Return the total number of elements in the tensor.
HINT: Use .size attribute of the numpy array
EXAMPLE: Tensor([1, 2, 3]).size should return 3
"""
raise NotImplementedError("Student implementation required")
@property
def dtype(self) -> np.dtype:
"""
Get data type as numpy dtype.
TODO: Return the data type of the stored numpy array.
HINT: Use .dtype attribute of the numpy array
EXAMPLE: Tensor([1, 2, 3]).dtype should return dtype('int32')
"""
raise NotImplementedError("Student implementation required")
def __repr__(self) -> str:
"""
String representation.
TODO: Create a clear string representation of the tensor.
APPROACH:
1. Convert the numpy array to a list for readable output
2. Include the shape and dtype information
3. Format: "Tensor([data], shape=shape, dtype=dtype)"
EXAMPLE:
Tensor([1, 2, 3]) → "Tensor([1, 2, 3], shape=(3,), dtype=int32)"
"""
raise NotImplementedError("Student implementation required")
# %% ../../assignments/source/01_tensor/tensor_dev.ipynb 5
class Tensor:
"""
TinyTorch Tensor: N-dimensional array with ML operations.
The fundamental data structure for all TinyTorch operations.
Wraps NumPy arrays with ML-specific functionality.
"""
def __init__(self, data: Union[int, float, List, np.ndarray], dtype: Optional[str] = None):
"""
Create a new tensor from data.
Args:
data: Input data (scalar, list, or numpy array)
dtype: Data type ('float32', 'int32', etc.). Defaults to auto-detect.
"""
# Convert input to numpy array
if isinstance(data, (int, float, np.number)):
# Handle Python and NumPy scalars
if dtype is None:
# Auto-detect type: int for integers, float32 for floats
if isinstance(data, int) or (isinstance(data, np.number) and np.issubdtype(type(data), np.integer)):
dtype = 'int32'
else:
dtype = 'float32'
self._data = np.array(data, dtype=dtype)
elif isinstance(data, list):
# Let NumPy auto-detect type, then convert if needed
temp_array = np.array(data)
if dtype is None:
# Keep NumPy's auto-detected type, but prefer common ML types
if np.issubdtype(temp_array.dtype, np.integer):
dtype = 'int32'
elif np.issubdtype(temp_array.dtype, np.floating):
dtype = 'float32'
else:
dtype = temp_array.dtype
self._data = temp_array.astype(dtype)
elif isinstance(data, np.ndarray):
self._data = data.astype(dtype or data.dtype)
else:
raise TypeError(f"Cannot create tensor from {type(data)}")
@property
def data(self) -> np.ndarray:
"""Access underlying numpy array."""
return self._data
@property
def shape(self) -> Tuple[int, ...]:
"""Get tensor shape."""
return self._data.shape
@property
def size(self) -> int:
"""Get total number of elements."""
return self._data.size
@property
def dtype(self) -> np.dtype:
"""Get data type as numpy dtype."""
return self._data.dtype
def __repr__(self) -> str:
"""String representation."""
return f"Tensor({self._data.tolist()}, shape={self.shape}, dtype={self.dtype})"
def add(self, other: 'Tensor') -> 'Tensor':
"""
Add another tensor to this tensor.
TODO: Implement tensor addition as a method.
APPROACH:
1. Use the add_tensors function you already implemented
2. Or implement the addition directly using self._data + other._data
3. Return a new Tensor with the result
EXAMPLE:
Tensor([1, 2, 3]).add(Tensor([4, 5, 6])) → Tensor([5, 7, 9])
HINTS:
- You can reuse add_tensors(self, other)
- Or implement directly: Tensor(self._data + other._data)
"""
raise NotImplementedError("Student implementation required")
def multiply(self, other: 'Tensor') -> 'Tensor':
"""
Multiply this tensor by another tensor.
TODO: Implement tensor multiplication as a method.
APPROACH:
1. Use the multiply_tensors function you already implemented
2. Or implement the multiplication directly using self._data * other._data
3. Return a new Tensor with the result
EXAMPLE:
Tensor([1, 2, 3]).multiply(Tensor([4, 5, 6])) → Tensor([4, 10, 18])
HINTS:
- You can reuse multiply_tensors(self, other)
- Or implement directly: Tensor(self._data * other._data)
"""
raise NotImplementedError("Student implementation required")
# Arithmetic operators for natural syntax (a + b, a * b, etc.)
def __add__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Addition: tensor + other"""
if isinstance(other, Tensor):
return Tensor(self._data + other._data)
else: # scalar
return Tensor(self._data + other)
def __radd__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse addition: scalar + tensor"""
return Tensor(other + self._data)
def __sub__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Subtraction: tensor - other"""
if isinstance(other, Tensor):
return Tensor(self._data - other._data)
else: # scalar
return Tensor(self._data - other)
def __rsub__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse subtraction: scalar - tensor"""
return Tensor(other - self._data)
def __mul__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Multiplication: tensor * other"""
if isinstance(other, Tensor):
return Tensor(self._data * other._data)
else: # scalar
return Tensor(self._data * other)
def __rmul__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse multiplication: scalar * tensor"""
return Tensor(other * self._data)
def __truediv__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Division: tensor / other"""
if isinstance(other, Tensor):
return Tensor(self._data / other._data)
else: # scalar
return Tensor(self._data / other)
def __rtruediv__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse division: scalar / tensor"""
return Tensor(other / self._data)
# %% ../../assignments/source/01_tensor/tensor_dev.ipynb 9
def add_tensors(a: Tensor, b: Tensor) -> Tensor:
"""
Add two tensors element-wise.
TODO: Implement element-wise addition of two tensors.
APPROACH:
1. Extract the numpy arrays from both tensors
2. Use NumPy's + operator for element-wise addition
3. Return a new Tensor with the result
EXAMPLE:
add_tensors(Tensor([1, 2, 3]), Tensor([4, 5, 6]))
→ Tensor([5, 7, 9])
HINTS:
- Use a.data and b.data to get the numpy arrays
- NumPy handles broadcasting automatically
- Return Tensor(result) to wrap the result
"""
raise NotImplementedError("Student implementation required")
# %% ../../assignments/source/01_tensor/tensor_dev.ipynb 10
def add_tensors(a: Tensor, b: Tensor) -> Tensor:
"""Add two tensors element-wise."""
return Tensor(a.data + b.data)
# %% ../../assignments/source/01_tensor/tensor_dev.ipynb 11
def multiply_tensors(a: Tensor, b: Tensor) -> Tensor:
"""
Multiply two tensors element-wise.
TODO: Implement element-wise multiplication of two tensors.
APPROACH:
1. Extract the numpy arrays from both tensors
2. Use NumPy's * operator for element-wise multiplication
3. Return a new Tensor with the result
EXAMPLE:
multiply_tensors(Tensor([1, 2, 3]), Tensor([4, 5, 6]))
→ Tensor([4, 10, 18])
HINTS:
- Use a.data and b.data to get the numpy arrays
- NumPy handles broadcasting automatically
- Return Tensor(result) to wrap the result
"""
raise NotImplementedError("Student implementation required")
# %% ../../assignments/source/01_tensor/tensor_dev.ipynb 12
def multiply_tensors(a: Tensor, b: Tensor) -> Tensor:
"""Multiply two tensors element-wise."""
return Tensor(a.data * b.data)
# %% ../../assignments/source/01_tensor/tensor_dev.ipynb 16
class Tensor:
"""
TinyTorch Tensor: N-dimensional array with ML operations.
The fundamental data structure for all TinyTorch operations.
Wraps NumPy arrays with ML-specific functionality.
"""
def __init__(self, data: Union[int, float, List, np.ndarray], dtype: Optional[str] = None):
"""
Create a new tensor from data.
Args:
data: Input data (scalar, list, or numpy array)
dtype: Data type ('float32', 'int32', etc.). Defaults to auto-detect.
"""
# Convert input to numpy array
if isinstance(data, (int, float, np.number)):
# Handle Python and NumPy scalars
if dtype is None:
# Auto-detect type: int for integers, float32 for floats
if isinstance(data, int) or (isinstance(data, np.number) and np.issubdtype(type(data), np.integer)):
dtype = 'int32'
else:
dtype = 'float32'
self._data = np.array(data, dtype=dtype)
elif isinstance(data, list):
# Let NumPy auto-detect type, then convert if needed
temp_array = np.array(data)
if dtype is None:
# Keep NumPy's auto-detected type, but prefer common ML types
if np.issubdtype(temp_array.dtype, np.integer):
dtype = 'int32'
elif np.issubdtype(temp_array.dtype, np.floating):
dtype = 'float32'
else:
dtype = temp_array.dtype
self._data = temp_array.astype(dtype)
elif isinstance(data, np.ndarray):
self._data = data.astype(dtype or data.dtype)
else:
raise TypeError(f"Cannot create tensor from {type(data)}")
@property
def data(self) -> np.ndarray:
"""Access underlying numpy array."""
return self._data
@property
def shape(self) -> Tuple[int, ...]:
"""Get tensor shape."""
return self._data.shape
@property
def size(self) -> int:
"""Get total number of elements."""
return self._data.size
@property
def dtype(self) -> np.dtype:
"""Get data type as numpy dtype."""
return self._data.dtype
def __repr__(self) -> str:
"""String representation."""
return f"Tensor({self._data.tolist()}, shape={self.shape}, dtype={self.dtype})"
def add(self, other: 'Tensor') -> 'Tensor':
"""
Add another tensor to this tensor.
TODO: Implement tensor addition as a method.
APPROACH:
1. Use the add_tensors function you already implemented
2. Or implement the addition directly using self._data + other._data
3. Return a new Tensor with the result
EXAMPLE:
Tensor([1, 2, 3]).add(Tensor([4, 5, 6])) → Tensor([5, 7, 9])
HINTS:
- You can reuse add_tensors(self, other)
- Or implement directly: Tensor(self._data + other._data)
"""
raise NotImplementedError("Student implementation required")
def multiply(self, other: 'Tensor') -> 'Tensor':
"""
Multiply this tensor by another tensor.
TODO: Implement tensor multiplication as a method.
APPROACH:
1. Use the multiply_tensors function you already implemented
2. Or implement the multiplication directly using self._data * other._data
3. Return a new Tensor with the result
EXAMPLE:
Tensor([1, 2, 3]).multiply(Tensor([4, 5, 6])) → Tensor([4, 10, 18])
HINTS:
- You can reuse multiply_tensors(self, other)
- Or implement directly: Tensor(self._data * other._data)
"""
raise NotImplementedError("Student implementation required")
# Arithmetic operators for natural syntax (a + b, a * b, etc.)
def __add__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Addition: tensor + other"""
if isinstance(other, Tensor):
return Tensor(self._data + other._data)
else: # scalar
return Tensor(self._data + other)
def __radd__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse addition: scalar + tensor"""
return Tensor(other + self._data)
def __sub__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Subtraction: tensor - other"""
if isinstance(other, Tensor):
return Tensor(self._data - other._data)
else: # scalar
return Tensor(self._data - other)
def __rsub__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse subtraction: scalar - tensor"""
return Tensor(other - self._data)
def __mul__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Multiplication: tensor * other"""
if isinstance(other, Tensor):
return Tensor(self._data * other._data)
else: # scalar
return Tensor(self._data * other)
def __rmul__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse multiplication: scalar * tensor"""
return Tensor(other * self._data)
def __truediv__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Division: tensor / other"""
if isinstance(other, Tensor):
return Tensor(self._data / other._data)
else: # scalar
return Tensor(self._data / other)
def __rtruediv__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse division: scalar / tensor"""
return Tensor(other / self._data)
# %% ../../assignments/source/01_tensor/tensor_dev.ipynb 17
class Tensor:
"""
TinyTorch Tensor: N-dimensional array with ML operations.
The fundamental data structure for all TinyTorch operations.
Wraps NumPy arrays with ML-specific functionality.
"""
def __init__(self, data: Union[int, float, List, np.ndarray], dtype: Optional[str] = None):
"""
Create a new tensor from data.
Args:
data: Input data (scalar, list, or numpy array)
dtype: Data type ('float32', 'int32', etc.). Defaults to auto-detect.
"""
# Convert input to numpy array
if isinstance(data, (int, float, np.number)):
# Handle Python and NumPy scalars
if dtype is None:
# Auto-detect type: int for integers, float32 for floats
if isinstance(data, int) or (isinstance(data, np.number) and np.issubdtype(type(data), np.integer)):
dtype = 'int32'
else:
dtype = 'float32'
self._data = np.array(data, dtype=dtype)
elif isinstance(data, list):
# Let NumPy auto-detect type, then convert if needed
temp_array = np.array(data)
if dtype is None:
# Keep NumPy's auto-detected type, but prefer common ML types
if np.issubdtype(temp_array.dtype, np.integer):
dtype = 'int32'
elif np.issubdtype(temp_array.dtype, np.floating):
dtype = 'float32'
else:
dtype = temp_array.dtype
self._data = temp_array.astype(dtype)
elif isinstance(data, np.ndarray):
self._data = data.astype(dtype or data.dtype)
else:
raise TypeError(f"Cannot create tensor from {type(data)}")
@property
def data(self) -> np.ndarray:
"""Access underlying numpy array."""
return self._data
@property
def shape(self) -> Tuple[int, ...]:
"""Get tensor shape."""
return self._data.shape
@property
def size(self) -> int:
"""Get total number of elements."""
return self._data.size
@property
def dtype(self) -> np.dtype:
"""Get data type as numpy dtype."""
return self._data.dtype
def __repr__(self) -> str:
"""String representation."""
return f"Tensor({self._data.tolist()}, shape={self.shape}, dtype={self.dtype})"
def add(self, other: 'Tensor') -> 'Tensor':
"""Add another tensor to this tensor."""
return Tensor(self._data + other._data)
def multiply(self, other: 'Tensor') -> 'Tensor':
"""Multiply this tensor by another tensor."""
return Tensor(self._data * other._data)
# Arithmetic operators for natural syntax (a + b, a * b, etc.)
def __add__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Addition: tensor + other"""
if isinstance(other, Tensor):
return Tensor(self._data + other._data)
else: # scalar
return Tensor(self._data + other)
def __radd__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse addition: scalar + tensor"""
return Tensor(other + self._data)
def __sub__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Subtraction: tensor - other"""
if isinstance(other, Tensor):
return Tensor(self._data - other._data)
else: # scalar
return Tensor(self._data - other)
def __rsub__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse subtraction: scalar - tensor"""
return Tensor(other - self._data)
def __mul__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Multiplication: tensor * other"""
if isinstance(other, Tensor):
return Tensor(self._data * other._data)
else: # scalar
return Tensor(self._data * other)
def __rmul__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse multiplication: scalar * tensor"""
return Tensor(other * self._data)
def __truediv__(self, other: Union['Tensor', int, float]) -> 'Tensor':
"""Division: tensor / other"""
if isinstance(other, Tensor):
return Tensor(self._data / other._data)
else: # scalar
return Tensor(self._data / other)
def __rtruediv__(self, other: Union[int, float]) -> 'Tensor':
"""Reverse division: scalar / tensor"""
return Tensor(other / self._data)