WeBWorK Main Forum

Matrix Object ans_array

Matrix Object ans_array

by Andrew Dabrowski -
Number of replies: 8
Am I doing something wrong in the code below? When the correct answer is entered only the 1,1 entry in the matrix turns green.

Also, is there a way to give partial credit for getting some of the entries right? At my school we're used to giving 75% if 3 out 4 entries are correct.

DOCUMENT();

loadMacros(
"PG.pl",
"PGbasicmacros.pl",
"MathObjects.pl"
);

TEXT(beginproblem);
Context("Matrix");

$M = Matrix([[1,0],[0,1]]);

BEGIN_TEXT

What is the identity Matrix?
\{ $M->ans_array \}

END_TEXT

&ANS( $M->cmp() );


ENDDOCUMENT();

In reply to Andrew Dabrowski

Re: Matrix Object ans_array

by Davide Cervone -
This was fixed last August, and the fix is included in WeBWorK version 2.10, so you would need to update to that version in order to get the coloration for ans_array.

As for partial credit, there is no current mechanism for checking the answer if not all the entries are filled in (the corresponding MathObject can't actually be created in that case). While it would be possible to add such functionality, there is nothing currently in place to allow it.
In reply to Davide Cervone

Re: Matrix Object ans_array

by Andrew Dabrowski -
OK, our server has 2.8 I think.

What if all entries are filled in but all are correct? Should that give partial credit?
In reply to Andrew Dabrowski

Re: Matrix Object ans_array

by Davide Cervone -
What if all entries are filled in but all are correct? Should that give partial credit?

Currently it does not. (You can get entry hints for vectors and points, but not matrices). But you can certainly arrange for it with a custom checker or a post-filter.

Here is an example that uses the post-filter approach (for reasons I'll explain below).

sub MatrixPartialCredit {
  my $N = shift;   # number of entries that must be right for partial credit
  return sub {
    my $ans = shift; $ans->{_filter_name} = "Matrix Partial Credit";
    my $correct = $ans->{correct_value};
    my $student = $ans->{student_value};
    if ($ans->{score} < 1 && defined($correct) &&
          defined($student) && $student->class eq "Matrix") {
      my @c = map {@$_} ($correct->value);   # flatten matrix
      my @s = map {@$_} ($student->value);   # flatten matrix
      my $score = 0; my $n = @c;
      foreach my $i (0..$#c) {$score ++ if $c[$i] == $s[$i]}
      $ans->score($score/$n) if $score >= $N;
    }
    return $ans;
  }
}

Context("Matrix");

$M = Matrix([1,2],[3,4]);

BEGIN_TEXT
\{$M->ans_array\}
END_TEXT

ANS($M->cmp->withPostFilter(MatrixPartialCredit(3)));
Here, the MatrixPartialCredit function takes an argument that is the number of entries that must be correct and returns a post filter that checks that the student has at least that many correct. The filter is passed an AnswerHash object, and it gets the correct and student answers from that. It checks that the score isn't 100 percent already (score = 1), and that the correct and student answers are actually valid (if the student has a syntax error, for example, there will be no $ans->{student_value}, or if not all the entries are filled, $student will not be a Matrix object (it will either be empty or a Real that is the top-left entry).

If both are valid, then we get an array of all the entries of the matrix ($M->value returns an array of references to arrays, where the references are to arrays that are the rows of the matrix; the entries of the rows are Real MathObjects). We then compare the correct and student arrays entry by entry (and since these are Real objects, they will use the fuzzy checks controlled by the tolerance and tolType context flags), and add up the number of entries that are equal. If enough of them are equal, then we adjust the score to be the proper percentage. Finally, we return the AnswerHash.

You could put the MatrixPartialCredit() filter in a separate file and load that into whatever problems need it.

You could have done this in a custom checker without having to do the checks of whether the values are defined or the correct type, but there are two reasons to use the filter approach instead, even though there is a little more checking to do.

First, you can more easily add messages like "Your entry (2,3) is incorrect" if you wanted to help the student out more. (But because there are so many entries, it might be better to make an array-like layout that has X for incorrect entries or something like that.)

Second, it would allow you to check matrices that are not completely filled in, though you would have to work a bit to get the data about the matrix in that case, because the Matrix MathObject is not actually created when there are blank entries. So that is more sophisticated. But if you use the post-filter approach with a macro file for MatrixPartialCredit(), then you could update that macro file at some time in the future to handle partially filled matrices, and all your existing problems would take advantage of that automatically.

Hope that does what you need.

In reply to Andrew Dabrowski

Re: Matrix Object ans_array

by Davide Cervone -
Well, it turns out that most of what you need to handle the partly filled in matrix and generate a correct/incorrect matrix message is already in place. Here is another example like the one above that implements a message indicating which entries are correct, which incorrect, and which not filled in.
TEXT(MODES(TeX=>"",HTML=>"<style>table.ArrayLayout td {background-color:transparent}</style>")); 

sub MatrixMarkIncorrect {
  my $right = "&#x2714;";
  my $wrong = "&#x2718;";
  return sub {
    my $ans = shift; $ans->{_filter_name} = "Matrix Mark Incorrect";
    my $correct = $ans->{correct_value};
    my $student = $ans->{student_value};
    if ($ans->{score} < 1 && defined($correct) && defined($student)) {
      my @c = $correct->value;
      my @s = $student->value;
      @s = @{$ans->{student_formula}} if ref($ans->{student_formula}) eq 'ARRAY';
      foreach my $i (0..$#c) {
        foreach my $j (0..scalar(@{$c[$i]})-1) {
          my $ca = $c[$i][$j]; my $sa = $s[$i][$j];
          $s[$i][$j] = ($ca == $sa ? "[$right]" : "[$wrong]") if ref($sa);
        }
      }
      $ans->{ans_message} = "Incorrect entries are marked with '$wrong':<p>".
        $correct->format_matrix([@s],@{$correct->{format_options}},tth_delims=>1)."<br><br>";
      $ans->{ans_message} =~ s/~~n//g;
    }
    return $ans;
  }
}

Context("Matrix");
$M = Matrix([1,2],[3,4]);

BEGIN_TEXT
\{$M->ans_array\}
END_TEXT
ANS($M->cmp->withPostFilter(MatrixMarkIncorrect));
The important details here are that $ans->{student_formula} usually holds the Formula object that came from parsing the student answer, but in the case of an answer array that included empty values, it will be an array that contains either the strings used for the underlines for missing entries or the MathObject Reals for the filled in ones.

The code above goes through the correct answer and the student answer and (when the student answer is filled in) checks it against the correct answer and changes the array to a checkmark or an "x". At the end, it uses the format_matrix() method to turn the array into the HTML for displaying the matrix (just like in the "Entered" column). One caveat is that you have to remove the newlines or they will be turned into <br> elements automatically by the code that creates the table. I also added some CSS to get the coloration to be correct for the table.

You should be able to combine this with the previous filter to get partial credit as well. E.g.,

ANS($M->cmp->withPostFilter(MatrixPartialCredit(3))->withPostFilter(MatrixMarkIncorrect));
Of course, you can modify this to suite your tastes, so it is just an example to get you started. Note that if you were to move this to a macro file, you would need to change the ~~ to \ in the substitution.
In reply to Davide Cervone

Re: Matrix Object ans_array

by Andrew Dabrowski -
Thanks for the suggestions, I'm just coming back to this issue now and will use them.

What's a good reference for post-filters?
In reply to Andrew Dabrowski

Re: Matrix Object ans_array

by Andrew Dabrowski -
I'm also looking at the custom answer checker route. Does that work correctly in WW 2.9? I tried

sub matrixAnsChecker{
my ( $correct, $student, $ansHash ) = @_;
return 0.001; }

which you will note also returns a score of 0.001 whatever the student's answer.

&ANS( $P->cmp( 'tol' => 0.0001, checker => ~~&matrixAnsChecker ) );

But then the student always gets full credit. In other words it seems that webwork is interpreting the output from the checker as a Boolean rather than a float.

In reply to Andrew Dabrowski

Re: Matrix Object ans_array

by Davide Cervone -
Yes, it turns out that the result of the custom checker is treated as boolean, not a percentage (that should be changed).

You can get the desired effect, however, by using

sub matrixAnsChecker {
  my ($correct,$student,$ansHash) = @_;
  ...
  $ansHash->score(.5);
  return 0;
}
if you want to award 50%. (Note that the scores are only shown as whole number percents, so .001 would end up being 0%.) You need to return 0 in order to prevent the score from being changed to 1 later.

As an aside, you are using the old-style &ANS(), but the ampersand is no longer necessary, and is deprecated. So ANS(...) is preferred.

Also, you can do

$matrixAnsChecker = sub {
 ...
}
...
ANS($P->cmp(checker => $matrixAnsChecker));
which avoids the ~~& syntax, which always looks so strane to me (as it is not standard perl).
In reply to Andrew Dabrowski

Re: Matrix Object ans_array

by Davide Cervone -
What's a good reference for post-filters?

As usual, there is very little documentation in place for such things. The main information is in the POD file for the AnswerHash object. The AnswerHash is the object passed to the filter, and its properties and methods are described at the top of the POD file. Filters are discussed (briefly) near the bottom.

Basically, you get passed the AnswerHash, and can get the data you are interested in out of that object, manipulate it in whatever way you need, and return the modified AnswerHash so that it can be passed on to the next filter.

Earlier filters can modify the data, or add to it (as can the pre-filters and answer checkers themselves). So there may be there data in the AnswerHash that is not described in the POD file. For example, the MathObject answer checkers add the student_value, student_formula, and correct_value properties, which store the MathObjects for the student's answer and the correct answer. So it is often easiest to just pretty-print the contents of the AnswerHash during your debugging so that you can see what it contains. If you set debug=>1 in the cmp() call (e.g., ANS($m->cmp(debug=>1)))), I think that is done automatically before each filter is run.