====== Autograding Jupyter Notebook assignments with Otter - quickstart ======
Otter uses raw notebook cells, together with the first comment line in each of these, for delimitation between blocks, i.e. where questions, tests or configurations begin and/or end. Ex:
# BEGIN SOLUTION
def my_func(a, b):
# BEGIN SOLUTION
c = (a+b)/2
# END SOLUTION
return c
# END SOLUTION
In the above, a solution block is started and ended with the first and last lines, as they each lead a raw cell. Inside my_func, # BEGIN SOLUTION and # END SOLUTION are used to hide a specific part of code from the notebooks generated for students(but they still belong to the same block as they’re not in sperate raw cells).
=====Creating assignments=====
The first cell shall be a raw cell containing a YAML-formatted configuration block. Use ''# ASSIGNMENT CONFIG'' to de fine this. This specifies how the assignments should be generated , tests run, etc. [[https://otter-grader.readthedocs.io/en/latest/otter_assign/notebook_format.html|Find more details directly from Otter here]] or [[otter-grader|on this wiki here]].
Example:
# ASSIGNMENT CONFIG
requirements: requirements.txt
solutions_pdf: true
export_cell:
instructions: "These are some submission instructions."
generate:
pdf: true
filtering: true
pagebreaks: true
zips: false
To write question descriptions you can use a simple markdown cell, no additional syntax required \\
====Question blocks====
Use ''# BEGIN QUESTION'' and ''# END QUESTION'' to define a question block. Within it, use ''# BEGIN SOLUTION'', ''# END SOLUTION'' to specify the answer, and ''# BEGIN TESTS'' and ''# END TESTS'' to specify tests. Example:
# BEGIN QUESTION
name: q1
points: 2
Question 1. Write a function called sieve that takes in a positive integer n and returns a set of the prime numbers less than or equal to n. Use the Sieve of Eratosthenes to find the primes.
# BEGIN SOLUTION
def sieve(n):
"""
Generate a set of prime numbers less than or equal to a positive integer.
"""
# BEGIN SOLUTION
is_prime = [True for _ in range(n + 1)]
p = 2
while p ** 2 <= n:
if is_prime[p]:
for i in range(p ** 2, n + 1, p):
is_prime[i] = False
p += 1
is_prime[0]= False
is_prime[1]= False
return set(i for i in range(n + 1) if is_prime[i])
# END SOLUTION
# END SOLUTION
# BEGIN TESTS
def test_low_primes(sieve):
assert sieve(1) == set()
assert sieve(2) == {2}
assert sieve(3) == {2, 3}
test_low_primes(sieve) # IGNORE
# HIDDEN
def test_higher_primes(sieve):
assert sieve(20) == {2, 3, 5, 7, 11, 13, 17, 19}
assert sieve(100) == {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97}
test_higher_primes(sieve) # IGNORE
# END TESTS
# END QUESTION
As seen above, you can specify a name and points for a question after declaring its block. Same can be done for tests. [[https://otter-grader.readthedocs.io/en/latest/otter_assign/notebook_format.html#autograded-questions|See here for details]]
**Generate assignment**
Once finished writing the assignment, you can generate the student version, along with the autograding zip, PDF and other files with:
otter assign
Where the first argument is the filepath to your Notebook and the second is the location where you want the generated files to be saved.
=====Autograding assignments=====
Open a terminal(in Jupyter: File->New Launcher->Other->Terminal), go to where you have saved assignment_grader.py and run:
python assignment_grader.py -a -s -c -tp [0,1] -p
Each argument is optional, the script looks into or creates default locations for all its arguments. Feedback is generated by default(tp=1). \\
Example:
python assignment_grader.py -a ml_hw1/dist/autograder/autograder.zip -s ml_hw1/submissions -c ml_hw1/results -tp 1 -p /ml_hw1/test_feedback