This commit is contained in:
Chris Pyles 2022-04-28 08:00:01 -07:00
Родитель 9f6ea46567
Коммит fd0aa15597
3 изменённых файлов: 0 добавлений и 246 удалений

Просмотреть файл

@ -1,74 +0,0 @@
Initial Conditions
==================
For some problems, it is necessary for students to choose some configurations or initial conditions
that can vary from student to student; handling all possible values of these initial conditions
would require writing quite a few references. Instead, PyBryt offers the
:py:class:`InitialCondition<pybryt.annotations.initial_condition.InitialCondition>` class to
represent a value that will be set when the student's submission is executed for use in writing
annotations.
Writing a Reference with Initial Conditions
-------------------------------------------
Using initial conditions in your references is pretty simple. Each
:py:class:`InitialCondition<pybryt.annotations.initial_condition.InitialCondition>` has a name,
the first argument to its constructor. This name is how the value in the student submission will be
identified (but more on that later).
Once you've instantiated an
:py:class:`InitialCondition<pybryt.annotations.initial_condition.InitialCondition>`, you can use it
as normal in value and other types of annotations (except attribute annotations).
.. code-block:: python
ic = pybryt.InitialCondition("foo")
pybryt.Value(ic)
Sometimes, however, you may want to look for a value derived from an initial condition. For this
reason, :py:class:`InitialCondition<pybryt.annotations.initial_condition.InitialCondition>`
supports all of Python's arithmetic operators, and you can also apply transformations by writing
functions:
.. code-block:: python
pybryt.Value(ic + 2)
pybryt.Value(2 * ic - 3)
pybryt.Value(ic.apply(np.transpose).apply(np.linalg.norm))
pybryt.Value(ic.apply(lambda v: pow(v, 10, 73)))
Each statement inside the :py:class:`Value<pybryt.annotations.value.Value>` constructors above
returns an :py:class:`InitialCondition<pybryt.annotations.initial_condition.InitialCondition>`.
Initial conditions maintain a list of transformations that need to be applied to reach the final
value for the annotation, and when a value is supplied from the submission, the transformations
are applied in sequence to determine the value that the value annotations should look for.
Collecting Initial Conditions in Submissions
--------------------------------------------
In order to link the initial conditions in the reference implementation to the values in the
submission, you must call
:py:func:`pybryt.set_initial_conditions<pybryt.execution.set_initial_conditions>` in the
submission. This function accepts a dictionary as its only argument mapping strings corresponding
to the names of initial conditions to the values of those initial conditions. When PyBryt is not
actively tracing, it has no effect; but, when called while PyBryt executes the submission, it tells
PyBryt to store the initial conditions passed to it in the memory footprint for later use by the
annotations.
For example, continuing the example from above, you would need to call
.. code-block:: python
pybryt.set_initial_conditions({
"foo": 2,
})
to set the initial condition.
This function can be called multiple times, but the memory footprint only retains a single store of
initial conditions, so calling it multiple times with the same key will overwrite any old values
of that key. (For example, calling it with ``{"foo": 1, "bar": 3}`` and then ``{"foo": 2}``
will result in initial conditions ``{"foo": 2, "bar": 3}``).

Просмотреть файл

@ -1,36 +0,0 @@
.. _invariants:
Invariants
==========
Invariants provide logic for determining whether a value satisfies a value annotation. They can be
used to ensure that annotations can still be satisfied independent of the different ways students
format their answers.
Invariant Structure
-------------------
Invariants are subclasses of the abstract base class
:py:class:`invariants.invariant<pybryt.annotations.invariants.invariant>`. All subclasses implement
the static :py:meth:`run<pybryt.annotations.invariants.invariant.run>` method, which takes in a list of
objects and returns a transformed list of objects that contains all versions of every object in the
input list which are considered equal.
Consider an invariant for string capitalization. The ``run`` method of this invariant takes in a list
of objects and returns the same list but with every string lowercased. For a matrix transposition
invariant, the ``run`` method might return a list with every 2D array's transpose included, as well
as the original array.
Invariants have a custom ``__new__`` method that calls the ``run`` method, so that they function as
callables rather than classes.
PyBryt supports custom invariants. To create your own invariant, subclass
:py:class:`invariants.invariant<pybryt.annotations.invariants.invariant>` and implement the ``run``
method.
Built-In Invariants
-------------------
A list of built-in invariants can be found :ref:`here<invariants_ref>`.

Просмотреть файл

@ -1,136 +0,0 @@
.. _value:
Value Annotations
=================
Value annotations are the most basic type of annotation. They expect a specific
value to appear while executing the student's code. To create a value
annotation, create an instance of :py:class:`Value<pybryt.annotations.value.Value>` and pass to
its constructor the value expected in the student's code:
.. code-block:: python
arr = np.linspace(0, 10, 11)
pybryt.Value(arr)
Note that when an instance of :py:class:`Value<pybryt.annotations.value.Value>` is created,
:py:meth:`copy.copy` is called on the argument passed to it, so values cannot be
affected by mutability.
Attribute Annotations
---------------------
PyBryt supports checking for the presence of an object with a specific attribute value using the
:py:class:`Attribute<pybryt.annotations.value.Attribute>` annotation. This annotation takes in an
object and one or more strings representing an instance variable, and asserts that the student's
memory footprint should contain an object with that attribute such that the value of the attribute
equals the value of the attribute in the annotation.
For example, this can be useful in checking that students have correctly fitted in ``sklearn``
regression model by checking that the coefficients are correct:
.. code-block:: python
import sklearn.linear_model as lm
model = lm.LinearRegression()
model.fit(X, y)
pybryt.Attribute(model, "coef_",
failure_message="Your model doesn't have the correct coefficients.")
Note that, by default, PyBryt doesn't check that the object satisfying the attribute annotation has
the same type as the object the annotation was created for. If a student knew the coefficient values
in the above example, the following student code would satisfy that annotation:
.. code-block:: python
class Foo:
coef_ = ... # the array of coefficients
f = Foo() # f will satisfy the annotation
To ensure that the annotation is only satisfied when the object is of the same type, set
``enforce_type=True`` in the constructor:
.. code-block:: python
pybryt.Attribute(model, "coef_", enforce_type=True,
failure_message="Your model doesn't have the correct coefficients.")
Return Value Annotations
------------------------
In addition to checking for the presence of a value, PyBryt also has an annotation that asserts that
a value was returned by a student's function. The annotation does not specify anything about the
function that returns it except that the function is part of the submission code (i.e. not part of
an imported library); it merely checks that the value was seen at least once in a return event
passed to the trace function.
You can create a return value annotation with the
:py:class:`ReturnValue<pybryt.annotations.value.ReturnValue>` constructor; it accepts all the same
arguments as the :py:class:`Value<pybryt.annotations.value.Value>` constructor:
.. code-block:: python
pybryt.ReturnValue(df, success_message="...", failure_message="...")
Numerical Tolerances
---------------------
For numerical values, or iterables of numerical values that support vectorized
math, it is also possible to define an absolute tolerance (``atol``) and/or a
relative tolerance (``rtol``) to allow the student's solution to deviate from
the reference. Numerical tolerances are computed as with ``numpy.allcose``,
where the value is "equal enough" if it is within :math:`v \pm (\texttt{atol}
+ \texttt{rtol} \cdot |v|)`, where :math:`v` is the value of the annotation.
Both ``atol`` and ``rtol`` default to zero and have to be specified when value
annotation is defined:
.. code-block:: python
pybryt.Value(arr, atol=1e-3, rtol=1e-5)
Invariants
---------------------
Similar to numerical tolerances, which allow the student's solution to deviate
from the reference value, PyBryt allows specifying conditions when other data
types should be considered equal. PyBryt supports defining these conditions
using :ref:`invariants<invariants>`. To use invariants on a value, we need to
pass the invariant objects as a list to the ``invariants`` argument of the
:py:class:`Value<pybryt.Value>` constructor. For instance, let's say we want to
allow the student's solution (a string) to be case-insensitive.
.. code-block:: python
correct_answer = "a CasE-inSensiTiVe stRING"
pybryt.Value(correct_answer, invariants=[pybryt.invariants.string_capitalization])
More information about invariants can be found :ref:`here<invariants>`.
Custom Equivalence Functions
----------------------------
In some cases, the algorithm that value annotations use for checking if two objects are
equivalent may not be suitable to the problem at hand. For cases like this, you can provide a
custom equivalence function that the value annotation will use instead to determine if two
objects are equal. The equivalence function should return ``True`` if the objects are equal and
``False`` otherwise. If the equivalence function raises an error, this will be interpeted as
``False`` (unless :ref:`debug mode<debugging>` is enabled).
For example, we could implement the ``string_capitalization`` invariant using a custom
equivalence function:
.. code-block:: python
def string_lower_eq(s1, s2):
return s1.lower() == s2.lower()
pybryt.Value(correct_answer, equivalence_fn=string_lower_eq)