diff --git a/TODO b/TODO index 82f37e562a901d56d9c3e7d64438177aa4a77d3d..a4d0e208bf7677baf2e111f039d059ecb9f1a339 100644 --- a/TODO +++ b/TODO @@ -13,6 +13,11 @@ Ideas ===== +- Auto-expire + +- Do print() and plot() from testing code count? + Make a setting. + - Session reset UI - Allow just viewing flow without any possibility for submission diff --git a/cfrunpy/cfrunpy_backend.py b/cfrunpy/cfrunpy_backend.py index aab583eb3e72a06ec94af7a30de061e042db953d..ddcbbb429d8e344cfa89f30c122e68c57c0e14fe 100644 --- a/cfrunpy/cfrunpy_backend.py +++ b/cfrunpy/cfrunpy_backend.py @@ -152,6 +152,55 @@ class Feedback: self.set_points(points) raise GradingComplete() + def _check_numpy_array_base(self, name, ref, data): + import numpy as np + assert isinstance(ref, np.ndarray) + + if not isinstance(data, np.ndarray): + self.finish(0, "'%s' is not a numpy array" % name) + + if ref.shape != data.shape: + self.finish( + 0, "'%s' does not have correct shape--" + "got: '%s', expected: '%s'" % ( + name, data.shape, ref.shape)) + + if ref.dtype.kind != data.dtype.kind: + self.finish( + 0, "'%s' does not have correct data type--" + "got: '%s', expected: '%s'" % ( + name, data.dtype.kind, ref.dtype.kind)) + + def check_numpy_array_allclose(self, name, ref, data, accuracy_critical=True, + rtol=1e-05, atol=1e-08): + import numpy as np + + self._check_numpy_array_base(name, ref, data) + good = np.allclose(ref, data, rtol=rtol, atol=atol) + + if not good: + self.add_feedback("'%s' is inaccurate" % name) + + if accuracy_critical and not good: + self.set_points(0) + raise GradingComplete() + + return good + + def check_list(self, name, ref, data, entry_type=None): + assert isinstance(ref, list) + if not isinstance(data, list): + self.finish(0, "'%s' is not a list" % name) + + if len(ref) != len(data): + self.finish(0, "'%s' has the wrong length--expected %d, got %d" + % (name, len(ref), len(list))) + + if entry_type is not None: + for i, entry in enumerate(data): + if not isinstance(entry, entry_type): + self.finish(0, "'%s[i]' has the wrong type" % (name, i)) + def run_code(result, run_req): # {{{ compile code diff --git a/course/page/code.py b/course/page/code.py index d13681abbb5949d33e0f220cc8840a4c2ef6fb59..9e7e8e31e1b4c489aa5a4bd677df07f10ceb8651 100644 --- a/course/page/code.py +++ b/course/page/code.py @@ -351,7 +351,7 @@ class PythonCodeQuestion(PageBaseWithTitle, PageBaseWithValue): * ``GradingComplete``: An exception class that can be raised to indicated that the grading code has concluded. - * ``feedback``: A class instance with three methods:: + * ``feedback``: A class instance with the following interface:: feedback.set_points(0.5) # 0<=points<=1 (usually) feedback.add_feedback("This was wrong") @@ -359,6 +359,11 @@ class PythonCodeQuestion(PageBaseWithTitle, PageBaseWithValue): # combines the above two and raises GradingComplete feedback.finish(0, "This was wrong") + feedback.check_numpy_array_allclose(name, ref, data, + accuracy_critical=True, rtol=1e-5, atol=1e-8) + + feedback.check_list(name, ref, data, entry_type=None) + * ``data_files``: A dictionary mapping file names from :attr:`data_files` to :class:`bytes` instances with that file's contents.