Important

Jupyter widgets cannot be run within in documentation. To be able to interact with the widget you must run a mybinder instance. To run a mybinder instance of this notebook please use this link https://mybinder.org/v2/gh/osscar-org/scicode-widgets/HEAD?labpath=docs%2Fsrc%2Fnbgrader.ipynb.

Integrating with nbgrader

Problem1 from the quickstart of nbgrader using scwidgets

When you initialize the quickstart project from nbgrader a problem1.ipynb file is generated. In this notebook we show how scwidgets can be used with nbgrader. We transformed the problem1.ipynb to a version that uses scwidgets.


[1]:
from scwidgets import CodeExercise, TextExercise, ExerciseRegistry, CheckRegistry, CodeInput, assert_type, assert_equal
[2]:
### BEGIN HIDDEN TESTS
if 'NAME' not in globals():
    NAME = "reference solution"
### END HIDDEN TESTS
exercise_registry = ExerciseRegistry(filename_prefix="problem1")
exercise_registry
[2]:
[3]:
check_registry = CheckRegistry()
check_registry
[3]:
[4]:
def squares(n):
    """Compute the squares of numbers from 1 to n, such that the
    ith element of the returned list equals i^2.

    """
    ### BEGIN SOLUTION
    if n < 1:
        raise ValueError("n must be greater than or equal to 1")
    return [i ** 2 for i in range(1, n + 1)]
    ### END SOLUTION

description = """
Write a function that returns a list of numbers,
such that $x_i=i^2$, for $1\leq i \leq n$.
Make sure it handles the case where $n<1$ by raising a `ValueError`.
"""

code_ex_squares = CodeExercise(
    code=squares,
    parameters={"n": (1, 10, 1)},
    update=lambda code_ex: print(code_ex.code(code_ex.parameters['n'])),
    check_registry=check_registry,
    exercise_registry=exercise_registry,
    key="Part A (2 points)",
    description=description
)

# Check that squares returns the correct output for several inputs
check_registry.add_check(
    code_ex_squares,
    asserts=[assert_type, assert_equal],
    inputs_parameters=[{"n": i} for i in [1, 2, 10, 11]],
    outputs_references=[([1],), ([1, 4],), ([1, 4, 9, 16, 25, 36, 49, 64, 81, 100],), ([1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121],)],
)

# Check that squares raises an error for invalid inputs
def assert_raise_error() -> str:
    try:
        code_ex_squares.code.unwrapped_function(0)
    except ValueError:
        return ""
    else:
        return "Did not raise error for zero"

    try:
        code_ex_squares.code.unwrapped_function(-4)
    except ValueError:
        return ""
    else:
        return "Did not error for negative number"

check_registry.add_check(
    code_ex_squares,
    asserts=[
        assert_raise_error,
    ]
)

code_ex_squares
[4]:
[5]:
### BEGIN HIDDEN TESTS
exercise_registry.load_answer_from_student_name(NAME, code_ex_squares)
checks = check_registry.check_widget(code_ex_squares)
assert checks[0].successful, checks[0].message()
### END HIDDEN TESTS
[6]:
### BEGIN HIDDEN TESTS
assert checks[1].successful, checks[1].message()
### END HIDDEN TESTS
[7]:
from scwidgets import assert_type, assert_equal

def sum_of_squares(n):
    """Compute the sum of the squares of numbers from 1 to n."""
    ### BEGIN SOLUTION
    return sum(squares(n))
    ### END SOLUTION

description = """
Using your `squares` function, write a function
that computes the sum of the squares of the numbers
from 1 to $n$. Your function should call the `squares`
function -- it should NOT reimplement its functionality.
"""

code_ex_sum_of_squares = CodeExercise(
    code=CodeInput(sum_of_squares, builtins={"squares": code_ex_squares.code.function}),
    parameters={"n": (1, 10, 1)},
    update=lambda code_ex: print(code_ex.code(code_ex.parameters['n'])),
    check_registry=check_registry,
    exercise_registry=exercise_registry,
    key="Part B (1 point)",
    description=description
)

# Check that sum_of_squares returns the correct answer for various inputs
check_registry.add_check(
    code_ex_sum_of_squares,
    asserts=[assert_type, assert_equal],
    inputs_parameters=[{"n": i} for i in [1, 2, 10, 11]],
    outputs_references=[(1,), (5,), (385,), (506,)],
)

# Check that sum_of_squares relies on squares
def assert_uses_squares() -> str:
    """Check that sum_of_squares relies on squares."""
    code_ex_sum_of_squares.code.builtins = {}
    try:
        code_ex_sum_of_squares.code.unwrapped_function(1) # not using builtins
    except NameError:
        result = ""
    else:
        result = "sum_of_squares does not use squares"
    code_ex_sum_of_squares.code.builtins = {"squares": code_ex_squares.code.function}
    return result

check_registry.add_check(
    code_ex_sum_of_squares,
    asserts=[
        assert_uses_squares,
    ]
)

code_ex_sum_of_squares
[7]:
[8]:
### BEGIN HIDDEN TESTS
exercise_registry.load_answer_from_student_name(NAME, code_ex_sum_of_squares)
checks = check_registry.check_widget(code_ex_sum_of_squares)
assert checks[0].successful, checks[0].message()
### END HIDDEN TESTS
[9]:
### BEGIN HIDDEN TESTS
assert checks[1].successful, checks[1].message()
### END HIDDEN TESTS
[10]:
value = """
### BEGIN SOLUTION
$\sum_{i=1}^n i^2$
### END SOLUTION
"""

description = """
Using LaTeX math notation, write out the equation
that is implemented by your `sum_of_squares` function."""

text_ex = TextExercise(
    value=value,
    exercise_registry=exercise_registry,
    key="Part C (1 point)",
    description=description
)
text_ex
[10]:
[11]:
### BEGIN HIDDEN TESTS
exercise_registry.load_answer_from_student_name(NAME, text_ex)
### END HIDDEN TESTS
[12]:
def pyramidal_number(n):
    """Returns the n^th pyramidal number"""
    summation = sum_of_squares(n)
    ### BEGIN SOLUTION
    ### END SOLUTION

description = """
Find a usecase for your `sum_of_squares` function and implement that usecase in the cell below.
"""

code_ex_pyramidal_number = CodeExercise(
    code=CodeInput(pyramidal_number, builtins={"sum_of_squares": code_ex_sum_of_squares.code.function}),
    parameters={"n": (1, 10, 1)},
    update=lambda code_ex: print(code_ex.code(code_ex.parameters['n'])),
    exercise_registry=exercise_registry,
    key="Part D (2 points)",
    description=description
)

code_ex_pyramidal_number
[12]:
[13]:
### BEGIN HIDDEN TESTS
exercise_registry.load_answer_from_student_name(NAME, code_ex_pyramidal_number)
### END HIDDEN TESTS
[14]:
description = """
State the formulae for an arithmetic and geometric
sum and verify them numerically for an example of
your choice."""

text_ex = TextExercise(
    value="""
### BEGIN SOLUTION
### END SOLUTION
""",
    exercise_registry=exercise_registry,
    key="Part E (4 points)",
    description=description
)

text_ex
[14]:
[15]:
### BEGIN HIDDEN TESTS
exercise_registry.load_answer_from_student_name(NAME, text_ex)
### END HIDDEN TESTS
[16]:
try:
    exercise_registry.load_file_from_student_name(NAME)
except FileNotFoundError:
    exercise_registry.create_new_file_from_student_name(NAME)