diff --git a/.github/.keep b/.github/.keep new file mode 100644 index 00000000..e69de29b diff --git a/.github/workflows/classroom.yml b/.github/workflows/classroom.yml new file mode 100644 index 00000000..694e0c44 --- /dev/null +++ b/.github/workflows/classroom.yml @@ -0,0 +1,67 @@ +name: Autograding Tests +'on': +- workflow_dispatch +- repository_dispatch +permissions: + checks: write + actions: read + contents: read +jobs: + run-autograding-tests: + runs-on: ubuntu-latest + if: github.actor != 'github-classroom[bot]' + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup + id: setup + uses: classroom-resources/autograding-command-grader@v1 + with: + test-name: Setup + setup-command: sudo -H pip3 install -qr requirements.txt; sudo -H pip3 install + flake8==5.0.4 + command: flake8 --ignore "N801, E203, E266, E501, W503, F812, E741, N803, + N802, N806" minitorch/ tests/ project/; mypy minitorch/* + timeout: 10 + - name: Task 0.1 + id: task-0-1 + uses: classroom-resources/autograding-command-grader@v1 + with: + test-name: Task 0.1 + setup-command: sudo -H pip3 install -qr requirements.txt + command: pytest -m task0_1 + timeout: 10 + - name: Task 0.2 + id: task-0-2 + uses: classroom-resources/autograding-command-grader@v1 + with: + test-name: Task 0.2 + setup-command: sudo -H pip3 install -qr requirements.txt + command: pytest -m task0_2 + timeout: 10 + - name: Task 0.3 + id: task-0-3 + uses: classroom-resources/autograding-command-grader@v1 + with: + test-name: Task 0.3 + setup-command: sudo -H pip3 install -qr requirements.txt + command: pytest -m task0_3 + timeout: 10 + - name: Task 0.4 + id: task-0-4 + uses: classroom-resources/autograding-command-grader@v1 + with: + test-name: Task 0.4 + setup-command: sudo -H pip3 install -qr requirements.txt + command: pytest -m task0_4 + timeout: 10 + - name: Autograding Reporter + uses: classroom-resources/autograding-grading-reporter@v1 + env: + SETUP_RESULTS: "${{steps.setup.outputs.result}}" + TASK-0-1_RESULTS: "${{steps.task-0-1.outputs.result}}" + TASK-0-2_RESULTS: "${{steps.task-0-2.outputs.result}}" + TASK-0-3_RESULTS: "${{steps.task-0-3.outputs.result}}" + TASK-0-4_RESULTS: "${{steps.task-0-4.outputs.result}}" + with: + runners: setup,task-0-1,task-0-2,task-0-3,task-0-4 diff --git a/README.md b/README.md index 62e4d6ba..5b674014 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Open in Visual Studio Code](https://classroom.github.com/assets/open-in-vscode-2e0aaae1b6195c2367325f4f02e2d04e9abb55f0b24a779b69b11b9e10269abc.svg)](https://classroom.github.com/online_ide?assignment_repo_id=15734874&assignment_repo_type=AssignmentRepo) # MiniTorch Module 0 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/operators.py b/minitorch/operators.py index 37cc7c09..a89159f4 100644 --- a/minitorch/operators.py +++ b/minitorch/operators.py @@ -3,28 +3,28 @@ import math # ## Task 0.1 -from typing import Callable, Iterable # # Implementation of a prelude of elementary functions. # Mathematical functions: -# - mul -# - id -# - add -# - neg -# - lt -# - eq -# - max -# - is_close -# - sigmoid -# - relu -# - log -# - exp -# - log_back -# - inv -# - inv_back -# - relu_back + +# mul - Multiplies two numbers +# id - Returns the input unchanged +# add - Adds two numbers +# neg - Negates a number +# lt - Checks if one number is less than another +# eq - Checks if two numbers are equal +# max - Returns the larger of two numbers +# is_close - Checks if two numbers are close in value +# sigmoid - Calculates the sigmoid function +# relu - Applies the ReLU activation function +# log - Calculates the natural logarithm +# exp - Calculates the exponential function +# inv - Calculates the reciprocal +# log_back - Computes the derivative of log times a second arg +# inv_back - Computes the derivative of reciprocal times a second arg +# relu_back - Computes the derivative of ReLU times a second arg # # For sigmoid calculate as: # $f(x) = \frac{1.0}{(1.0 + e^{-x})}$ if x >=0 else $\frac{e^x}{(1.0 + e^{x})}$ @@ -35,15 +35,86 @@ # TODO: Implement for Task 0.1. +def mul(a, b): + return a * b + + +def id(a): + return a + + +def add(a, b): + return a + b + + +def neg(a): + return -1 * a + + +def lt(a, b): + return a < b + + +def eq(a, b): + return a == b + + +def max(a, b): + return a if (a > b) else b + + +def is_close(a, b): + return abs(a - b) < 1e-2 + + +def sigmoid(a): + if a >= 0: + return 1 / (1 + math.exp(-a)) + else: + return math.exp(a) / (1 + math.exp(a)) + + +def relu(a): + return max(0, a) + + +def log(a): + return math.log(a) + + +def exp(a): + return math.exp(a) + + +def inv(a): + return 1 / a + + +def log_back(a, b): + return inv(a) * b + + +def inv_back(a, b): + return (-1) / (a**2) * b + + +def relu_back(a, b): + if a <= 0: + return 0 + else: + return b + + # ## Task 0.3 # Small practice library of elementary higher-order functions. # Implement the following core functions -# - map -# - zipWith -# - reduce -# + +# map - Higher-order function that applies a given function to each element of an iterable +# zipWith - Higher-order function that combines elements from two iterables using a given function +# reduce - Higher-order function that reduces an iterable to a single value using a given function + # Use these to implement # - negList : negate a list # - addLists : add two lists together @@ -52,3 +123,36 @@ # TODO: Implement for Task 0.3. + + +def map(fn, iter): + return [fn(item) for item in iter] + + +def zipWith(fn, iter1, iter2): + return [fn(x, y) for x, y in zip(iter1, iter2)] + + +def reduce(fn, iter): + if len(iter) == 0: + return 0 + result = iter[0] + for item in iter[1:]: + result = fn(result, item) + return result + + +def negList(ls): + return map(neg, ls) + + +def addLists(ls1, ls2): + return zipWith(add, ls1, ls2) + + +def sum(ls): + return reduce(add, ls) + + +def prod(ls): + return reduce(mul, ls) 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..86bf05a8 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -23,6 +23,7 @@ relu, relu_back, sigmoid, + is_close, ) from .strategies import assert_close, small_floats @@ -108,7 +109,13 @@ 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") + epsilon = 1e-6 + assert 0 <= sigmoid(a) <= 1 + assert is_close(1 - sigmoid(a), sigmoid(-a)) + assert is_close(sigmoid(0), 0.5) + assert epsilon + sigmoid(a) > sigmoid(a) + + # raise NotImplementedError("Need to implement for Task 0.2") @pytest.mark.task0_2 @@ -116,7 +123,10 @@ 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 a < c + # raise NotImplementedError("Need to implement for Task 0.2") @pytest.mark.task0_2 @@ -125,7 +135,20 @@ def test_symmetric() -> None: 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") + # Test some edge cases + edge_cases = [ + (0, 0), + (1, 0), + (-1, 0), + (1, 1), + (-1, -1), + (float("inf"), 2), + (float("-inf"), 2), + ] + + for a, b in edge_cases: + assert mul(a, b) == mul(b, a) + # raise NotImplementedError("Need to implement for Task 0.2") @pytest.mark.task0_2 @@ -134,14 +157,33 @@ def test_distribute() -> None: :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") + edge_cases = [ + (0, 0, 1), + (1, 0, 1), + (-1, 0, 3), + (1, 1, 4), + (-1, -1, 6), + ] + + for a, b, c in edge_cases: + assert mul(c, (add(a, b))) == (c * a) + (c * b) + # raise NotImplementedError("Need to implement for Task 0.2") @pytest.mark.task0_2 def test_other() -> 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") + edge_cases = [ + (0, 0, 1), + (1, 0, 1), + (-1, 0, 3), + (1, 1, 4), + (-1, -1, 6), + ] + + for a, b, c in edge_cases: + assert (a * (b * c)) == (a * b) * c # ## Task 0.3 - Higher-order functions @@ -169,7 +211,15 @@ 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") + if len(ls1) != len(ls2): + raise ValueError("Lists must be of the same length") + if len(ls1) == 0 or len(ls2) == 0: + raise ValueError("List can't be empty") + lhs = sum(ls1) + sum(ls2) + rhs = sum(addLists(ls1, ls2)) + assert_close(lhs, rhs) + + # raise NotImplementedError("Need to implement for Task 0.3") @pytest.mark.task0_3