OK,
I have just committed a slew of things to the CVS repository that will
do what you are asking for. I have implemented a MultiPart obejct (in
pg/macros/parserMultiPart.pl) that lets you handle several answer
blanks with a single answer checker. This was not too hard, and could
be done without any changes to the existing Parser, but as I was
looking at the matrix macros to see how they handled the multiple
answer blanks, I realized that I really needed something like that that
would work with the Parser objects. So I spent the weekend implementing
ans_array methods for Matrix, Vector and Point objects, and making them
interact properly with the MultiPart object. The result is pretty
general, and (I hope) useful.
Here is the documentation from the parserMultiPart.pl file. I've also
included information from the CVS log for pg/lib/Value/AnswerChecker.pm
about the new ans_array method for Matrix, Vector and Point objects.
Davide

MultiPart objects let you tie several answer blanks to a single answer
checker, so you can have the answer in one blank influence the answer
in another. The MultiPart can produce either a single result in the
answer results area, or a separate result for each blank.
To create a MultiPart pass a list of answers to MultiPart() in the order they will appear in the problem. For example: $mp = MultiPart("x^2",1,1);
or $mp = MultiPart(Vector(1,1,1),Vector(2,2,2))>with(singleResult=>1);
Then, use $mp>ans_rule to create answer blanks for the various parts just as you would ans_rule . You can pass the width of the blank, which defaults to 20 otherwise. For example: BEGIN_TEXT \(f(x)\) = \{$mp>ans_rule(20)\} produces the same value at \(x\) = \{$mp>ans_rule(10)\} as it does at \(x\) = \{$mp>ans_rule(10)\}. END_TEXT
Finally, call $mp>cmp to produce the
answer checker(s) used in the MultiPart. You need to provide a checker
routine that will be called to determine if the answers are correct or
not. The checker will only be called if the student answers have no
syntax errors and their types match the types of the professor's
answers, so you don't ahve to worry about handling bad data from the
student (at least as far as typechecking goes).
The checker routine should accept three parameters: a reference to the
array of correct answers, a reference to the array of student answers,
and a reference to the MultiPart itself. It should do whatever checking
it needs to do and then return a score for the MultiPart as a whole
(every answer blank will be given the same score), or a reference to an
array of scores, one for each blank. The routine can set error messages
via the MultiPart's setMessage() method (e.g., $mp>setMessage(1,"The function can't be the identity") would set the message for the first answer blank of the MultiPart), or can call Value::Error() to generate an error and die.
The checker routine can be supplied either when the MultiPart is created, or
when the cmp() method is called. For example: $mp = MultiPart("x^2",1,1)>with( singleResult => 1, checker => sub { my ($correct,$student,$self) = @_; # get the parameters my ($f,$x1,$x2) = @{$student}; # extract the student answers Value::Error("Function can't be the identity") if ($f == 'x'); Value::Error("Function can't be constant") if ($f>isConstant); return $f>eval(x=>$x1) == $f>eval(x=>$x2); }, ); . . . ANS($mp>cmp);
or $mp = MultiPart("x^2",1,1)>with(singleResult=>1); sub check { my ($correct,$student,$self) = @_; # get the parameters my ($f,$x1,$x2) = @{$student}; # extract the student answers Value::Error("Function can't be the identity") if ($f == 'x'); Value::Error("Function can't be constant") if ($f>isConstant); return $f>eval(x=>$x1) == $f>eval(x=>$x2); }; . . . ANS($mp>cmp(checker=>~~&check));
You can include answer arrays by using the ans_array method of the multipart rather than ans_rule when the associated object in the list supports the ans_array . For example, $mp = MultiPart(Matrix([[1,2],[3,4]]),ColumnVector(1,2,3)); BEGIN_TEXT \{$mp>ans_array\} \{$mp>ans_array\} END_TEXT ANS($mp>cmp(checker=>~~&check));
would provide answer arrays for a matrix and a vector which would both be passed to the checker routine.

To create an answer array for a matrix, vector or point, use the new ans_array() or named_ans_array() methods of these objects. For example: Context("Matrix"); $M = Matrix([1,2],[3,4]);
BEGIN_TEXT \{$M>TeX\} = \{$M>ans_array\} END_TEXT
ANS($M>cmp);
This creates a matrix, then prints it ans creates an answer array of
the appropriate size for the matrix. The answer checker will
automatically know to handle the multiple entry blanks.
To used a named answer rule, use: Context("Matrix"); $M = Matrix([1,2],[3,4]);
BEGIN_TEXT \{$M>TeX\} = \{$M>named_ans_array('fred')\} END_TEXT
NAMED_ANS(fred => $M>cmp);
Both methods also take an optional argument that specifies the width of the answer rules. The default is 5.
You can get a multiinput point or vector array as well, and you can make column vectors as follows: Context("Vector"); $V = ColumnVector("1+x","3x","1x");
BEGIN_TEXT \{$V>TeX\} = \{$V>ans_array\} END_TEXT
ANS($V>cmp);
Note that you can make answer arrays for matrices and vectors of
formulas as well as constants, provided the formula is an explicit
matrix or vector of formulas, and is not obtained by matrix arithmatic.
For concistencey, all objets now have ans_rule and named_ans_rule methods as well. The default width is 20 for these.
< Post or View Comments >
