From 39bc5a68855d7bc5a7202d36c49330d77615ca16 Mon Sep 17 00:00:00 2001 From: AY Date: Sun, 21 Dec 2025 16:54:51 +0800 Subject: [PATCH] First commit --- minitorch/datasets.py | 24 +++++-- minitorch/module.py | 21 ++++-- minitorch/operators.py | 142 ++++++++++++++++++++++++++++++++++++++++ requirements.extra.txt | 4 +- tests/test_operators.py | 32 +++++---- 5 files changed, 199 insertions(+), 24 deletions(-) diff --git a/minitorch/datasets.py b/minitorch/datasets.py index b3bd9faa..699cad04 100644 --- a/minitorch/datasets.py +++ b/minitorch/datasets.py @@ -67,19 +67,29 @@ def circle(N): def spiral(N): - def x(t): return t * math.cos(t) / 20.0 def y(t): return t * math.sin(t) / 20.0 - X = [(x(10.0 * (float(i) / (N // 2))) + 0.5, y(10.0 * (float(i) / (N // - 2))) + 0.5) for i in range(5 + 0, 5 + N // 2)] - X = X + [(y(-10.0 * (float(i) / (N // 2))) + 0.5, x(-10.0 * (float(i) / - (N // 2))) + 0.5) for i in range(5 + 0, 5 + N // 2)] + + X = [ + (x(10.0 * (float(i) / (N // 2))) + 0.5, y(10.0 * (float(i) / (N // 2))) + 0.5) + for i in range(5 + 0, 5 + N // 2) + ] + X = X + [ + (y(-10.0 * (float(i) / (N // 2))) + 0.5, x(-10.0 * (float(i) / (N // 2))) + 0.5) + for i in range(5 + 0, 5 + N // 2) + ] y2 = [0] * (N // 2) + [1] * (N // 2) return Graph(N, X, y2) -datasets = {'Simple': simple, 'Diag': diag, 'Split': split, 'Xor': xor, - 'Circle': circle, 'Spiral': spiral} +datasets = { + "Simple": simple, + "Diag": diag, + "Split": split, + "Xor": xor, + "Circle": circle, + "Spiral": spiral, +} diff --git a/minitorch/module.py b/minitorch/module.py index 0a66058c..bd034d66 100644 --- a/minitorch/module.py +++ b/minitorch/module.py @@ -32,12 +32,16 @@ def modules(self) -> Sequence[Module]: def train(self) -> None: """Set the mode of this module and all descendent modules to `train`.""" # TODO: Implement for Task 0.4. - raise NotImplementedError("Need to implement for Task 0.4") + self.training = True + for m in self._modules.values(): + m.train() def eval(self) -> None: """Set the mode of this module and all descendent modules to `eval`.""" # TODO: Implement for Task 0.4. - raise NotImplementedError("Need to implement for Task 0.4") + self.training = False + for m in self._modules.values(): + m.eval() def named_parameters(self) -> Sequence[Tuple[str, Parameter]]: """Collect all the parameters of this module and its descendents. @@ -48,12 +52,21 @@ def named_parameters(self) -> Sequence[Tuple[str, Parameter]]: """ # TODO: Implement for Task 0.4. - raise NotImplementedError("Need to implement for Task 0.4") + out = [] + for n, p in self._parameters.items(): + out.append((n, p)) + for n, p in self._modules.items(): + for cn, cp in p.named_parameters(): + out.append((f"{n}.{cn}", cp)) + return out def parameters(self) -> Sequence[Parameter]: """Enumerate over all the parameters of this module and its descendents.""" # TODO: Implement for Task 0.4. - raise NotImplementedError("Need to implement for Task 0.4") + out = [] + for _, p in self.named_parameters(): + out.append(p) + return out def add_parameter(self, k: str, v: Any) -> Parameter: """Manually add a parameter. Useful helper for scalar parameters. diff --git a/minitorch/operators.py b/minitorch/operators.py index 37cc7c09..f4741a4e 100644 --- a/minitorch/operators.py +++ b/minitorch/operators.py @@ -33,6 +33,90 @@ # TODO: Implement for Task 0.1. +def mul(x: float, y: float) -> float: + """Multiplies two numbers""" + return x * y + + +def id(x: float) -> float: + """Returns the input unchanged""" + return x + + +def add(x: float, y: float) -> float: + """Adds two numbers""" + return x + y + + +def neg(x: float) -> float: + """Negates a number""" + return -x + + +def lt(x: float, y: float) -> bool: + """Checks if one number is less than another""" + return x < y + + +def eq(x: float, y: float) -> bool: + """Checks if two numbers are equal""" + return x == y + + +def max(x: float, y: float) -> float: + """Returns the larger of two numbers""" + if x >= y: + return x + else: + return y + + +def is_close(x: float, y: float) -> bool: + """Checks if two numbers are close in value""" + return abs(x - y) < 0.01 + + +def sigmoid(x: float) -> float: + """Calculates the sigmoid function""" + return 1 / (1 + math.exp(-x)) + + +def relu(x: float) -> float: + """Applies the ReLU activation function""" + return max(0, x) + + +def log(x: float) -> float: + """Calculates the natural logarithm""" + return math.log(x) + + +def exp(x: float) -> float: + """Calculates the exponential function""" + return math.exp(x) + + +def inv(x: float) -> float: + """Calculates the reciprocal""" + return 1 / x + + +def log_back(x: float, y: float) -> float: + """Computes the derivative of log times a second arg""" + return y / x + + +def inv_back(x: float, y: float) -> float: + """Computes the derivative of reciprocal times a second arg""" + return -y / x**2 + + +def relu_back(x: float, y: float) -> float: + """Computes the derivative of ReLU times a second arg""" + if x > 0: + return y + else: + return 0 # ## Task 0.3 @@ -52,3 +136,61 @@ # TODO: Implement for Task 0.3. +def map(fn: Callable[[float], float]) -> Callable[[Iterable[float]], list[float]]: + """Higher-order function that applies a given function to each element of an iterable""" + + def apply(xx: Iterable[float]) -> list[float]: + cur = [] + for x in xx: + cur.append(fn(x)) + return cur + + return apply + + +def zipWith( + fn: Callable[[float, float], float], +) -> Callable[[Iterable[float], Iterable[float]], list[float]]: + """Higher-order function that combines elements from two iterables using a given function""" + + def apply(xx: Iterable[float], yy: Iterable[float]) -> list[float]: + cur = [] + for x, y in zip(xx, yy): + cur.append(fn(x, y)) + return cur + + return apply + + +def reduce( + fn: Callable[[float, float], float], s: float +) -> Callable[[Iterable[float]], float]: + """Higher-order function that reduces an iterable to a single value using a given function""" + + def apply(xx: Iterable[float]) -> float: + out = s + for x in xx: + out = fn(out, x) + return out + + return apply + + +def negList(xx: Iterable[float]) -> list[float]: + """Negate all elements in a list using map""" + return map(neg)(xx) + + +def addLists(xx: Iterable[float], yy: Iterable[float]) -> list[float]: + """Add corresponding elements from two lists using zipWith""" + return zipWith(add)(xx, yy) + + +def sum(xx: Iterable[float]) -> float: + """Sum all elements in a list using reduce""" + return reduce(add, 0.0)(xx) + + +def prod(xx: Iterable[float]) -> float: + """Calculate the product of all elements in a list using reduce""" + return reduce(mul, 1.0)(xx) diff --git a/requirements.extra.txt b/requirements.extra.txt index 070fa1d0..81db5232 100644 --- a/requirements.extra.txt +++ b/requirements.extra.txt @@ -1,5 +1,7 @@ +altair==4.2.2 datasets==2.4.0 embeddings==0.0.8 +networkx==3.3 plotly==4.14.3 pydot==1.4.1 python-mnist @@ -7,5 +9,3 @@ streamlit==1.12.0 streamlit-ace torch watchdog==1.0.2 -altair==4.2.2 -networkx==3.3 diff --git a/tests/test_operators.py b/tests/test_operators.py index f6e555af..fcec7f79 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -99,8 +99,8 @@ def test_eq(a: float) -> None: @pytest.mark.task0_2 -@given(small_floats) -def test_sigmoid(a: float) -> None: +@given(small_floats, small_floats) +def test_sigmoid(a: float, b: float) -> None: """Check properties of the sigmoid function, specifically * It is always between 0.0 and 1.0. * one minus sigmoid is the same as sigmoid of the negative @@ -108,7 +108,12 @@ def test_sigmoid(a: float) -> None: * It is strictly increasing. """ # TODO: Implement for Task 0.2. - raise NotImplementedError("Need to implement for Task 0.2") + assert sigmoid(a) <= 1.0 + assert sigmoid(a) >= 0.0 + assert_close(1 - sigmoid(a), sigmoid(-a)) + assert sigmoid(0.0) == 0.5 + if a + 1e-6 < b: + assert sigmoid(a) < sigmoid(b) @pytest.mark.task0_2 @@ -116,32 +121,37 @@ def test_sigmoid(a: float) -> None: def test_transitive(a: float, b: float, c: float) -> None: """Test the transitive property of less-than (a < b and b < c implies a < c)""" # TODO: Implement for Task 0.2. - raise NotImplementedError("Need to implement for Task 0.2") + if lt(a, b) and lt(b, c): + assert lt(a, c) @pytest.mark.task0_2 -def test_symmetric() -> None: +@given(small_floats, small_floats) +def test_symmetric(a: float, b: float) -> None: """Write a test that ensures that :func:`minitorch.operators.mul` is symmetric, i.e. gives the same value regardless of the order of its input. """ # TODO: Implement for Task 0.2. - raise NotImplementedError("Need to implement for Task 0.2") + assert mul(a, b) == mul(b, a) @pytest.mark.task0_2 -def test_distribute() -> None: +@given(small_floats, small_floats, small_floats) +def test_distribute(a: float, b: float, c: float) -> None: r"""Write a test that ensures that your operators distribute, i.e. :math:`z \times (x + y) = z \times x + z \times y` """ # TODO: Implement for Task 0.2. - raise NotImplementedError("Need to implement for Task 0.2") + assert_close(mul(a, add(b, c)), add(mul(a, b), mul(a, c))) @pytest.mark.task0_2 -def test_other() -> None: +@given(small_floats, small_floats, small_floats) +def test_other(a: float, b: float, c: float) -> None: """Write a test that ensures some other property holds for your functions.""" # TODO: Implement for Task 0.2. - raise NotImplementedError("Need to implement for Task 0.2") + if a > 1e-6 or a < -1e-6: + assert_close(inv_back(a, b + c), inv_back(a, b) + inv_back(a, c)) # ## Task 0.3 - Higher-order functions @@ -169,7 +179,7 @@ def test_sum_distribute(ls1: List[float], ls2: List[float]) -> None: is the same as the sum of each element of `ls1` plus each element of `ls2`. """ # TODO: Implement for Task 0.3. - raise NotImplementedError("Need to implement for Task 0.3") + assert_close(sum(ls1 + ls2), sum(ls1) + sum(ls2)) @pytest.mark.task0_3