MathObjects-based Answer Checkers

MathObjects is designed to be used in two ways. First, you can use it within your perl code when writing problems as a means of making it easier to handle formulas, and in particular, to genarate be able to use a single object to produce numeric values, TeX output and answer strings. This avoids having to type a function three different ways (which makes maintaining a problem much harder). Since MathObjects also included vector and complex arthimatic, it is easier to work with these types of values as well.

The second reason for MathObjects is to use it to process student input. This is accomplished through special answer checkers that are part of the Parser package (rather than the traditional WeBWorK answer checkers). Checkers are available for all the types of values that the parser can produce (numbers, complex numbers, infinities, points, vectors, intervals, unions, formulas, lists of numbers, lists of points, lists of intervals, lists of formulas returning numbers, lists of formulas returning points, and so on).

To use one of these checkers, simply call the ->cmp method of the object that represents the correct answer. For example:

$n = Real(sqrt(2));
ANS($n->cmp);

will produce an answer checker that matches the square root of two. Similarly,

ANS(Vector(1,2,3)->cmp);

matches the vector <1,2,3> (or any computation that produces it, e.g., i+2j+3k, or <4,4,4>-<3,2,1>), while

ANS(Interval("(-inf,3]")->cmp);

matches the given interval. Other examples include:

ANS(Infinity->cmp);
ANS(String('NONE')->cmp);
ANS(Union("(-inf,$a) U ($a,inf)")->cmp);

and so on.

Formulas are handled in the same way:

ANS(Formula("x+1")->cmp);

$a = random(-5,5,1); $b = random(-5,5,1); $x = random(-5,5,1);
$f = Formula("x^2 + $a x + $b")->reduce;
ANS($f->cmp);
ANS($f->eval(x=>$x)->cmp);

$x = Formula('x');
ANS((1+$a*$x)->cmp);

Context("Vector")->variables->are(t=>'Real');
$v = Formula("<t,t^2,t^3>"); $t = random(-5,5,1);
ANS($v->cmp);
ANS($v->eval(t=>$t)->cmp);

and so on.

Lists of items can be checked as easily:

ANS(List(1,-1,0)->cmp);
ANS(List(Point($a,$b),Point($a,-$b))->cmp);
ANS(List(Vector(1,0,0),Vector(0,1,1))->cmp);
ANS(Compute("(-inf,2),(4,5)")->cmp); # easy way to get list of intervals
ANS(Formula("x, x+1, x^2-1")->cmp);
ANS(Formula("<x,2x>,<x,-2x>,<0,x>")->cmp);
ANS(List('NONE')->cmp);

and so on. The last example may seem strange, as you could have used ANS(String('NONE')->cmp), but there is a reason for using this type of construction. You might be asking for one or more numbers (or points, or whatever) or the word 'NONE' of there are no numbers (or points). If you used String('NONE')->cmp, the student would get an error message about a type mismatch if he entered a list of numbers, but with List('NONE')->cmp, he will get appropriate error messages for the wrong entries in the list.

It is often appropriate to use the list checker in this way even when the correct answer is a single value, if the student might type a list of answers.

On the other hand, using the list checker has its disadvantages. For example, if you use

ANS(Interval("(-inf,3]")->cmp);

and the student enters (-inf,3), she will get a message indicating that the type of interval is incorrect, while that would not be the case if

ANS(List(Interval("(-inf,3]"))->cmp);

were used. (This is because the student doesn't know how many intervals there are, so saying that the type of interval is wrong would inform her that there is only one.)

The rule of thumb is: the individual checkers can give more detailed information about what is wrong with the student's answer; the list checker allows a wider range of answers to be given without giving away how many answers there are. If the student knows there's only one, use the individual checker; if there may or may not be more than one, use the list checker.

Note that you can form lists of formulas as well. The following all produce the same answer checker:

ANS(List(Formula("x+1"),Formula("x-1"))->cmp);

ANS(Formula("x+1,x-1")->cmp); # easier

$f = Formula("x+1"); $g = Formula("x-1");
ANS(List($f,$g)->cmp);

$x = Formula('x');
ANS(List($x+1,$x-1)->cmp);

See the files in webwork2/doc/parser/problems for more examples of using the parser's answer checkers.

Controlling the Details of the Answer Checkers

The action of the answer checkers can be modified by passing flags to the cmp() method. For example:

ANS(Real(pi)->cmp(showTypeWarnings=>0));

will prevent the answer checker from reporting errors due to the student entering in the wrong type of answer (say a vector rather than a number).

Flags common to all answer checkers

There are a number of flags common to all the checkers:

showTypeWarnings=>1 or 0

show/don't show messages about student answers not being of the right type. (default: 1)

showEqualErrors=>1 or 0

show/don't show messages produced by trying to compare the professor and student values for equality, e.g., conversion errors between types. (default: 1)

ignoreStrings=>1 or 0

show/don't show type mismatch errors produced by strings (so that 'NONE' will not cause a type mismatch in a checker looking for a list of numbers, for example). (default: 1)

In addition to these, the individual types have their own flags:

Flags for Real()->cmp

ignoreInfinity=>1 or 0

Don't report type mismatches if the student enters an infinity. (default: 1)

Flags for String()->cmp

typeMatch=>value

Specifies the type of object that the student should be allowed to enter (in addition the string). (default: 'Value::Real')

Flags for Point()->cmp

showDimensionHints=>1 or 0

show/don't show messages about the wrong number of coordinates. (default: 1)

showCoordinateHints=>1 or 0

show/don't show message about which coordinates are right. (default: 1)

Flags for Vector()->cmp

showDimensionHints=>1 or 0

show/don't show messages about the wrong number of coordinates. (default: 1)

showCoordinateHints=>1 or 0

show/don't show message about which coordinates are right. (default: 1)

promotePoints=>1 or 0

do/don't allow the student to enter a point rather than a vector. (default: 1)

parallel=>1 or 0

Mark the answer as correct if it is parallel to the professor's answer. Note that a value of 1 forces showCoordinateHints to be 0. (default: 0)

sameDirection=>1 or 0

During a parallel check, mark the answer as correct only if it is in the same (not the opposite) direction as the professor's answer. (default: 0)

Flags for Matrix()->cmp

showDimensionHints=>1 or 0

show/don't show messages about the wrong number of coordinates. (default: 1)

The default for showEqualErrors is set to 0 for Matrices, since these errors usually are dimension errors, and that is handled separately (and after the equality check).

Flags for Interval()->cmp

showEndpointHints=>1 or 0

do/don't show messages about which endpoints are correct. (default: 1)

showEndTypeHints=>1 or 0

do/don't show messages about whether the open/closed status of the enpoints are correct (only shown when the endpoints themselves are correct). (default: 1)

Flags for Union()->cmp and List()->cmp

all the flags from the Real()->cmp, plus:

showHints=>1 or 0

do/don't show messages about which entries are incorrect. (default: $showPartialCorrectAnswers)

showLengthHints=>1 or 0

do/don't show messages about having the correct number of entries (only shown when all the student answers are correct but there are more needed, or all the correct answsers are among the ones given, but some extras were given). (default: $showPartialCorrectAnswers)

partialCredit=>1 or 0

do/don't give partial credit for when some answers are right, but not all. (default: $showPartialCorrectAnswers) (currently the default is 0 since WW can't handle partial credit properly).

ordered=>1 or 0

give credit only if the student answers are in the same order as the professor's answers. (default: 0)

entry_type=>'a (name)'

The string to use in error messages about type mismatches. (default: dynamically determined from list)

list_type=>'a (name)'

The string to use in error messages about numbers of entries in the list. (default: dynamically determined from list)

typeMatch=>value

Specifies the type of object that the student should be allowed to enter in the list (determines what constitutes a type mismatch error). (default: dynamically determined from list)

requireParenMatch=>1 or 0

Do/don't require the parentheses in the student's answer to match those in the professor's answer exactly. (default: 1)

removeParens=>1 or 0

Do/don't remove the parentheses from the professor's list as part of the correct answer string. This is so that if you use List() to create the list (which doesn't allow you to control the parens directly), you can still get a list with no parentheses. (default: 0 for List() and 1 for Formula())

Flags for Formula()->cmp

The flags for formulas are dependent on the type of the result of the formula. If the result is a list or union, it gets the flags for that type above, otherwise it gets that flags of the Real type above.

More flags need to be added in order to allow more control over the answer checkers to give the full flexibility of the traditional WeBWorK answer checkers. Note that some things, like whether trig functions are allowed in the answer, are controlled through the Context() rather than the answer checker itself. For example,

Context()->functions->undefine('sin','cos','tan');

would remove those three functions from use. (One would need to remove cot, sec, csc, arcsin, asin, etc., to do this properly; there could be a function call to do this.)

Similarly, which arithmetic operations are available is controlled through Context()->operations.

The tolerances used in comparing numbers are part of the Context as well. You can set these via:

Context()->flags->set(
    tolerance    => .0001,       # the relative or absolute tolerance
    tolType      => 'relative',  # or 'absolute'
    zeroLevel    => 1E-14,       # when to use zeroLevelTol
    zeroLevelTol => 1E-12,       # smaller than this matches zero
                                 #  when one of the two is less
                                 #  than zeroLevel
    limits       => [-2,2],      # limits for variables in formulas
    num_points   => 5,           # the number of test points
);

[These need to be handled better.]

Note that for testing formulas, you can override the limits and num_points settings by setting these fields of the formula itself:

$f = Formula("sqrt(x-10)");
$f->{limits} = [10,12];

$f = Formula("log(xy)");
$f->{limits} = [[.1,2],[.1,2]]; # x and y limits

You can also specify the test points explicitly:

$f = Formula("sqrt(x-10)");
$f->{test_points} = [[11],[11.5],[12]];

$f = Formula("log(xy)");
$f->{test_points} = [[.1,.1],[.1,.5],[.1,.75],
                     [.5,.1],[.5,.5],[.5,.75]];

[There still needs to be a means of handling the tolerances similarly, and through the ->cmp() call itself.]