diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9a0f85b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,15 @@ +## Project knowledge +- **Tech Stack:** Python3 +- **File Structure:** + - `src/common` – Application source code (autograd engine in here) + - `src/tests/` – All tests in here + +## Commands you can use +Run tests: `pytest src/tests/differential_test.py` + +## Documentation practices +Don't make PR's to big, be smart and prioritize readability over efficiency. +Write so that a new developer to this codebase can understand your writing, don’t assume your audience are experts in the topic/area you are writing about. + +## Boundaries +- ✅ **Always do:** Make sure you are actually using the `autograd.py` and `Module.py` files in the implementation of any models. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8f62669..2d866b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,4 +49,9 @@ disallow_untyped_defs = true explicit_package_bases = true namespace_packages = true disallow_untyped_calls = false -disable_error_code = ["import-untyped"] \ No newline at end of file +disable_error_code = ["import-untyped"] +mypy_path = ["src"] + +[[tool.mypy.overrides]] +module = ["cupy"] +ignore_missing_imports = true diff --git a/src/common/MLP.py b/src/common/MLP.py index 28fbbd1..9f5643d 100644 --- a/src/common/MLP.py +++ b/src/common/MLP.py @@ -5,7 +5,7 @@ from common.Module import Module -class Neuron(Module): # type: ignore[misc] +class Neuron(Module): def __init__(self, nin: int, nonlin: bool = True) -> None: super().__init__() self.w = [Value(random.uniform(-1, 1)) for _ in range(nin)] @@ -23,7 +23,7 @@ def __repr__(self) -> str: return f"{'ReLU' if self.nonlin else 'Linear'}Neuron({len(self.w)})" -class Layer(Module): # type: ignore[misc] +class Layer(Module): def __init__(self, nin: int, nout: int, **kwargs: Any) -> None: super().__init__() self.neurons = [Neuron(nin, **kwargs) for _ in range(nout)] @@ -39,7 +39,7 @@ def __repr__(self) -> str: return f"Layer of [{', '.join(str(n) for n in self.neurons)}]" -class MLP(Module): # type: ignore[misc] +class MLP(Module): def __init__(self, nin: int, nouts: list[int]) -> None: super().__init__() sz = [nin] + nouts diff --git a/src/common/Module.py b/src/common/Module.py index 99c5a6e..59a06a5 100644 --- a/src/common/Module.py +++ b/src/common/Module.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod -from typing import Any, Generator +from collections.abc import Iterable +from typing import Any from common.autograd import Value @@ -34,17 +35,17 @@ def forward(self, x): must be made before assignment on the child.""" def __init__(self) -> None: - self.params: dict[ - str, Value | "Module" - ] = {} # real param store: name -> Value or Module + self.params: dict[str, Value | "Module"] = ( + {} + ) # real param store: name -> Value or Module @abstractmethod - def forward(self, *args: Any, **kwargs: Any) -> Value: + def forward(self, *args: Any, **kwargs: Any) -> Any: """Compute the forward pass for this module.""" raise NotImplementedError - def __call__(self, *args: Any, **kwargs: Any) -> Value: + def __call__(self, *args: Any, **kwargs: Any) -> Any: # will call self.forward! return self.forward(*args, **kwargs) @@ -67,8 +68,10 @@ def __setattr__(self, key: str, value: Any) -> None: # registry in sync with the current state of the object. if isinstance(value, (Value, Module)): self.params[key] = value + elif key in self.params: + del self.params[key] - def parameters(self) -> Generator[Value, None, None]: + def parameters(self) -> Iterable[Value]: # recursively collects parameters and yields them (use a Generator) for key, param in self.params.items(): if isinstance(param, Module): diff --git a/src/common/autograd.py b/src/common/autograd.py index a1b27c7..c588434 100644 --- a/src/common/autograd.py +++ b/src/common/autograd.py @@ -1,4 +1,4 @@ -from typing import Union +from __future__ import annotations try: import cupy as np @@ -6,13 +6,15 @@ print("CUDA not available, defaulting to numpy") import numpy as np +Scalar = float | int + class Value: """stores value's and its gradient's""" def __init__( self, - data: float | np.ndarray, + data: Scalar | np.ndarray, _children: tuple["Value", ...] = (), _op: str = "", ) -> None: @@ -65,7 +67,7 @@ def sum_to_shape( result = result.sum(axis=i, keepdims=True) return result - def __add__(self, other: "Value") -> "Value": + def __add__(self, other: Value | Scalar) -> Value: other = other if isinstance(other, Value) else Value(other) out = Value(self.data + other.data, (self, other), "+") @@ -86,11 +88,7 @@ def _backward() -> None: return out - def __mul__( - self, other: Union["Value", int] - ) -> ( - "Value" - ): # some weird python black thing, so we made it use the union from typing instead(cuz it thinks its a string) + def __mul__(self, other: Value | Scalar) -> Value: other = other if isinstance(other, Value) else Value(other) out = Value(self.data * other.data, (self, other), "*") @@ -281,22 +279,22 @@ def build_topo(v: "Value") -> None: def __neg__(self) -> "Value": # -self return self * -1 - def __radd__(self, other: "Value") -> "Value": # other + self + def __radd__(self, other: Value | Scalar) -> Value: # other + self return self + other - def __sub__(self, other: "Value") -> "Value": # self - other + def __sub__(self, other: Value | Scalar) -> Value: # self - other return self + (-other) - def __rsub__(self, other: "Value") -> "Value": # other - self + def __rsub__(self, other: Value | Scalar) -> Value: # other - self return other + (-self) - def __rmul__(self, other: "Value") -> "Value": # other * self + def __rmul__(self, other: Value | Scalar) -> Value: # other * self return self * other - def __truediv__(self, other: "Value") -> "Value": # self / other + def __truediv__(self, other: Value | Scalar) -> Value: # self / other return self * other**-1 - def __rtruediv__(self, other: "Value") -> "Value": # other / self + def __rtruediv__(self, other: Value | Scalar) -> Value: # other / self return other * self**-1 def __repr__(self) -> "str":