# Comparer Functions #

By default, `FormulaGrader`, `NumericalGrader`, and `MatrixGrader` compare the numerically-sampled author formula and student formula for equality (within bounds specified by tolerance). Occasionally, it can be useful to compare author and student formulas in some other way. Functions that perform the actual comparison are called "comparers", and there are a few ways to invoke them.

## Employing Comparer Functions #

When an answer is passed into `FormulaGrader`, `NumericalGrader` or `MatrixGrader`, it is automatically paired up with a comparer. For example, the default comparer is `equality_comparer`. When you construct a grader like the following,

``````>>> from mitxgraders import *
...     variables=['a', 'b']
... )``````

`FormulaGrader` automatically converts the `answers` key to the following:

``````>>> grader2 = FormulaGrader(
...         'expect': {'comparer': equality_comparer, 'comparer_params': ['a+b']},
...         'msg': ''
...     },
...     variables=['a', 'b']
... )
True``````

To specify an alternate comparer, you can simply change the relevant answer to a dictionary `{'comparer': comparer_func, 'comparer_params': [list, of, params]}`. Different comparers use `comparer_params` in different ways.

For example, if grading angles in degrees, it may be useful to compare formulas modulo 2π. You can write your own comparer functions, but for this we can use the pre-built `congruent_modulo` comparer. This grader will accept any input congruent to `'b^2/a'` modulo `2*pi`.

``````>>> grader = FormulaGrader(
...         'comparer': congruence_comparer,
...         # first parameter is expected value, second is the modulus
...         'comparer_params': ['b^2/a', '2*pi']
...     },
...     variables=['a', 'b']
... )
True
False``````

Here, the `comparer_params` (`['b^2/a', '2*pi']`) are evaluated just like the student input, and used by the comparer function during grading.

## Changing Defaults #

The default comparer function for each of `FormulaGrader`, `NumericalGrader` and `MatrixGrader` can be set by calling `set_default_comparer` as follows:

``>>> FormulaGrader.set_default_comparer(LinearComparer())``

This sets the default comparer to `LinearComparer()` (see below) for all `FormulaGrader`s in the given problem. Comparers set in this manner must only take a single comparer parameter. Using this default set approach is typically simpler than setting comparers explicitly, and allows answers to be inferred from `expect` values. However, comparers that require two or more `comparer_params` cannot use this method.

If for some reason you need to reset the default comparer, you can use

``>>> FormulaGrader.reset_default_comparer()``

which is equivalent to setting the default comparer to `equality_comparer`.

## Available Comparers #

The table below lists the pre-built comparers along with the expected comparer parameters. Note that `comparer_params` is always a list of strings, and can use any variables available to the student. When using an ordered `ListGrader`, they can also use sibling values.

`comparer` use with `comparer_params`
(a list of strings)
purpose
`equality_comparer` `FormulaGrader`
`NumericalGrader`
`MatrixGrader`
`[expected]` checks that student input and `expected` differ by less than grader's tolerance.
`congruence_comparer` `FormulaGrader`
`NumericalGrader`
`[expected, modulus]` reduces student input modulo modulus, then checks for equality within grader's tolerance.
`between_comparer` `FormulaGrader`
`NumericalGrader`
`[start, stop]` checks that student input is real and between `start` and `stop`.
`eigenvector_comparer` `MatrixGrader` `[matrix, eigenvalue]` checks that student input is an eigenvector of `matrix` with eigenvalue `eigenvalue` within grader's tolerance.
`vector_phase_comparer` `MatrixGrader` `[comparison_vector]` checks that student input is equal to `comparison_vector` up to a phase, within grader's tolerance.
`vector_span_comparer` `MatrixGrader` `[vector1, vector2, ...]` checks that student input is nonzero and in the span of the given list of vectors, within grader's tolerance. If only a single vector is given, checks if the student input is equal to the given vector up to a (possibly complex) constant of proportionality.

All of these comparers (as well as the ones below) are available when using `from mitxgraders import *`.

## Special Comparers #

There are three special built-in comparer classes that can be used as comparers that take in a single input.

### EqualityComparer #

The `EqualityComparer` class simply checks for equality up to tolerance. In fact, `equality_comparer = EqualityComparer()`. The reason this class exists, however, is to allow for an extra option to be used when desired.

The `transform` option allows the author to specify a transforming function to be called on both the answer and the student input prior to comparison. Here is an example:

``````>>> import numpy as np
>>> comparer = EqualityComparer(transform=np.real)``````

This comparer will take the real part of the answer and the student input before comparing the results. This is useful if only the real part of the answers need to agree.

### LinearComparer #

The `LinearComparer` checks if the student's answer is linearly related to the problem's answer, and can provide partial credit as appropriate. Here are all of the options:

``````comparer = LinearComparer(
equals=float,  # default 1
proportional=(None | float),  # default 0.5
offset=(None | float),  # default None
linear=(None | float),  # default None
equals_msg=(None | str),  # default None
proportional_msg=(None | str),  # default 'The submitted answer differs from an expected answer by a constant factor.'
offset_msg=(None | str),  # default None
linear_msg=(None | str),  # default None
)``````

The first four settings specify how much credit to award the different situations, while the next four describe a message to display when that credit is awarded. Note that setting credit to `None` means that that type of credit will never be awarded, while setting credit to `0` means that it can be awarded (usually to display the relevant message). When the grading is performed, the largest credit of the available settings is awarded.

Here is an example of a `LinearComparer` that can be used to award partial credit if students are off from the answer by a constant multiple.

``>>> comparer = LinearComparer()``

Easy, isn't it? You can combine this with `set_default_comparer` to enable partial credit of this sort with one line in each problem!

Here is an example of setting up a `LinearComparer` that doesn't care about shift offsets (useful when describing indefinite integration).

``>>> comparer = LinearComparer(proportional=None, linear=1)``

Note that `LinearComparer` can only perform meaningful comparisons when random variables are used. If the answer is a numerical constant, then student answers will always be proportional to that constant, which probably isn't the desired behavior. Also note that when the answer is zero or the student supplies zero as their answer, partial credit cannot be assigned.

### MatrixEntryComparer #

This comparer is used only for `MatrixGrader`s. It has a `transform` option that is exactly equivalent to `EqualityComparer`, but it also has two options related to partial credit.

``````comparer = MatrixEntryComparer(
transform=(None | func),  # default None
entry_partial_credit=('proportional' | float),  # default 0
entry_partial_msg=str,  # default "Some array entries are incorrect, marked below:\n{error_locations}"
)``````

When `entry_partial_credit` is set to a number, if at least one entry in the array is correct (but not all of them), that amount of credit is assigned. When partial credit is assigned, the message `entry_partial_msg` is displayed to the student, with the text `{error_locations}` replaced by a graphic displaying which entries are correct/incorrect. To turn off the message, simply set `entry_partial_msg=''`. Setting `entry_partial_msg=''` and `entry_partial_credit=0` makes this grader equivalent to `EqualityComparer`.

Because this ability to assign partial credit to array input is so useful, `MatrixEntryComparer` can be set as the grader for `MatrixGrader`s using configuration options.

## Custom Comparer Functions #

In addition to using the built-in comparers, you can write your own.

See comparers.py for documentation and examples.