PREP 2013 Question Authoring - Archived

Use of MathObjects

Use of MathObjects

by Michael Doob -
Number of replies: 13
I have a couple of questions about MathObjects in general and Context('Matrix') in particular. They relate to the code that is appended.

(1) When you create a math object, is it essentially a compiled object that can be used but not changed? For example, can you change an entry within a matrix? If not, is there some type casting that might put it back to a perl array?

(2) In the attached example, the call to ans_array leave a button with a pi on it over the (1,2) entry. It obscures the answer.

(3) When a correct answer is input, it is marked as correct. However only the (1,1) entry has a green highlight.

(4) When the answer is incorrect, it is marked incorrect. However a correct entry may get a red highlight. 

(5) This is not a question but an observation. It would be nice to have a Context('rational').  I haven't done a really thorough search of the web site, so maybe I missed something.


Here is the example. In passing: I was at the Canadian Mathematical Society meeting in Halifax last week and did this on a laptop between meetings and talks. Sorry that the code is a bit sloppy, needs cleanup, and not very well documented. Nonetheless it does the job as an illustration.
----------------------------- code sample -----------------------------


##DESCRIPTION
##  Give reduced row echelon form of a 2 by 3 matrix
##ENDDESCRIPTION

## Date('06/11/2013')
## Author('Michael Doob')
## Institution('University of Manitoba')

###############################################

DOCUMENT();      

loadMacros(
   "PGstandard.pl",     # Standard macros for PG language
   "MathObjects.pl",
   "PGcourse.pl",      # Customization file for the course
);

# Print problem number and point value (weight) for the problem

TEXT(beginproblem());

# Show which answers are correct and which ones are incorrect
$showPartialCorrectAnswers = 1;


###############################################
#                                             Setup                                             #
###############################################

Context("Numeric");

@M = ([1,2,3],[4,5,6]);
@R = ([0,0,0],[0,0,0]);

foreach my $i (0..1) {
   foreach my $j (0..2) {
      $M[$i][$j] = non_zero_random(-6,6);
   }
}


# Sneaky nonzero entries makes for easy row reduction calculation

foreach my $j (0..2) {
   $R[0][$j] = $M[0][$j] / $M[0][0];
   $R[1][$j] = $M[1][$j] -  $M[1][0]*$R[0][$j];
   }

$pivot = -1;  ## flag in case the second row is all zero
$j = 3;
while ($j-- > 0) {
   if ($R[1][$j] != 0) { 
      $pivot = $j; 
   }
}


if ($pivot >= 0) {
   foreach my $j (0..2) {
      $R[1][$j] = $R[1][$j] / $R[1][$pivot];
      $R[0][$j] = $R[0][$j] - $R[0][$pivot]*$R[1][$j];
   }
}


Context('Matrix');
$N = Matrix(
   [$R[0][0],$R[0][1],$R[0][2]],
   [$R[1][0],$R[1][1],$R[1][2]],
);


###############################################
#                Text of problem plus the answer box(es)              #
###############################################

Context()->texStrings;

BEGIN_TEXT
Find \(R\), the reduced row echelon form of the matrix \(M\).

\[M=
   \begin{bmatrix} 
      $M[0][0] &$M[0][1] &$M[0][2]\\
      $M[1][0] &$M[1][1] &$M[1][2]
\end{bmatrix} 
\]

$PAR
<center>
\(R=\) \{$N->ans_array\}
</center>

END_TEXT

Context()->normalStrings;

###############################################
#                                                Answers                                       #
###############################################

ANS($N->cmp);

###############################################
#                                               Solutions                                     #
###############################################
Context()->texStrings;
BEGIN_SOLUTION

The reduced row echelon form of \(M\) is
$BR
\[
R= $N
\]

END_SOLUTION

ENDDOCUMENT();        


In reply to Michael Doob

Re: Use of MathObjects

by Gavin LaRose -
Hi Michael,

I'll go through your questions in sequence with some answers, and let others give a better answer as they can.

(1) When you create a math object, is it essentially a compiled object that can be used but not changed?...
This is a good question. My read of the documentation for the Matrix MathObject is that there isn't an obvious way to change entries in a defined Matrix object. That said, $mat->data "returns a reference to the array of data that represents the object," which should readily allow changing things. I haven't tested this, but it seems it should work to do something along the lines of

    $mat = Matrix( [[1,2], [3,4]] );
    $dat = $mat->data;
    $dat->[1]->[1] = -4;
    $newMat = Matrix( $dat );

to create a new matrix [[1,2], [3,-4]].

(2) In the attached example, the call to ans_array leave a button with a pi on it...
I believe the pi button is a trigger for an equation editor, "DragMath," that is enabled in the WeBWorK configuration files. Its location is probably determined by theme and related configuration; this is probably a question for Mike Gage.

(3),(4) When a(n in)correct answer is input, it is marked as correct. However only the (1,1) entry has a green(red) highlight...
I think this is a stylesheet bug. I thought Jason Aubrey had fixed it at some point, but it seems not to be fixed in the version of WeBWorK running on this server.

(5) ...It would be nice to have a Context('rational')...
Context('RationalFunction') might be what you want: see contextRationalFunction.pl POD docs. Davide is the expert on this type of thing.

Gavin

In reply to Gavin LaRose

Re: Use of MathObjects

by Paul Seeburger -

I have been able to turn off the DragMath feature in an individual problem using the following code line near the start of my problem.

$envir{DragMath} = 0;

But I think this version of WebWork is not using DragMath but MathView so here is what Mike Gage and I thought should turn this feature off in an individual problem.

$envir{MathView} = 0;

Hopefully this should hide this feature on this problem. I will test it now to be sure.

In reply to Paul Seeburger

Re: Use of MathObjects

by Paul Seeburger -

Ok, so this code does work for DragMath, but it is not successful yet for MathView.

Perhaps someone else has figured this one out. I will post again if I get it working.

In reply to Michael Doob

Re: Use of MathObjects

by Davide Cervone -
Gavin has already responded, but I want to add a bit more to his comments.

When you create a math object, is it essentially a compiled object that can be used but not changed? For example, can you change an entry within a matrix?

For most objects, it usually makes more sense to create a new one than to change the existing one, so there aren't methods for changing the data. A Matrix is an exception, and should have a method for assigning an entry's value. I thought there was one, but looking at the code I see it is listed in the "to do" section. Argh!

It is possible to write an assignment function (though it won't be a method of the object itself) that you can use the modify the matrix. Gavin has suggested taking the data from the original matrix and modifying, then making a new matrix from that. This is a nice idea, but there are some issues with his approach.

He suggests the following code:

    $mat = Matrix( [[1,2], [3,4]] );
    $dat = $mat->data;
    $dat->[1]->[1] = -4;
    $newMat = Matrix( $dat );
which sets $dat to the internal data used to maintain $mat. While this is a reference to an array, the elements of the array are not array references for the rows, they are MathObjects for the rows. That means $dat->[1] is a MathObject, not an array reference, so you can't do $dat->[1]->[1]. It is actually a reference to a Matrix object (one dimension smaller, in this case, a linear array representing a row of the original matrix)

You could do $dat->[1]->data->[1] and change that to -4, but even that is not a good idea for two reasons. First, the entries in the Matrix are MathObject Reals, and this assigns it a Perl real. Nothing goes wrong here, but things may go wrong trying to use the matrix later when WeBWorK tries to treat the real as a MathObject Real (e.g., trying to call its TeX method).

That could be fixed by assigning it Real(-4), but there is a second problem. The $dat variable is not a copy of the data stored by $mat, it actually is the data stored by $mat, so when you modify it, you are actually modifying $mat ($dat is a pointer to the same array that $mat holds internally, so changes to that array or its elements change $mat). You will find that after running this code, $mat will be the matrix given by [[1,2],[3,-4]].

(This does bring up a third issue, which is that the indices in the data array start at 0, not 1, so changing the 1,1 entry is actually changing the second entry in the second row, which may not be what Gavin meant to do.)

Passing $dat to Matrix() does cause $newMat to copy the data, and so from here on out $newMat will be separate from $mat, but $dat is still tied to $mat.

You could use this approach to modify the original $mat, however. For example

    $mat = Matrix( [[1,2], [3,4]] );
    $mat->data->[0]->data->[0] = Real(5);
would make $mat be the matrix [[5,2],[3,4]]. I will give you an assign() in a separate reply that works on this principle.

If not, is there some type casting that might put it back to a perl array?

Every MathObject has a value() method that returns its defining data in a non-MathObject form (more or less). For a Matrix, it gives an array of array references much like the one you use in your PG code. So

    $M = Matrix([1,2],[3,4]);
    @M = $M->value;
makes @M be the array ([1,2],[3,4]) (the only difference is that the numbers are now MathObject Reals, not perl reals). You could then modify the @M array, and make a new Matrix from the result:
    $M = Matrix([1,2],[3,4]);
    @M = $M->value;
    $M[0][1] = 8;
    $N = Matrix(@M);
This makes $N equivalent to Matrix([1,8],[3,4]).

Note that $M and @M are separate names, and don't interfere with each other (both the original Matrix and the array are still available, with $M referring to the Matrix, and $M[] referring to the array).

Note also that you can pass the array into Matrix directly, and don't have to de-reference each of its entries, as you have done in your code. So $M = Matrix(@M); and @M = $M->value undo each other (one converts an array to a Matrix, the other converts a Matrix to an array).

This makes it possible to simplify your code a bit. You could simply do

    $N = Matrix(@R);
rather than writing out the array element by element again. You can also use $R rather than $N to make the association more direct. Similarly, you could use $M = Matrix(@M) and then use
    \[ M = $M \]
in the BEGIN_TEXT/END_TEXT block rather than writing out the TeX code by hand. The Matrix $M will write the TeX code for you. This is one of the nice things about using MathObejcts -- you don't have to write both a Perl version and a TeX version of the same thing. Alternatively, if you don't want to create the $M variable, you could use
   \[ M = \{ Matrix(@M) \} \]
directly in the text block.

I'll answer the other parts separately. This has already gotten too long.

In reply to Davide Cervone

Re: Use of MathObjects

by Gavin LaRose -
Hi Davide,

Thanks for clarifying the data that are returned by $MathObject->data. This does bring up one question I have bumped into with $MathObject->value, albeit with Vectors, not Matrices.

I've found that the value method doesn't always work as expected depending on how a Vector is instantiated. In particular, I've had trouble in the past getting the expected array when creating a vector with something like $vec = $a*i + $b*j + $c*j (this may have been when $a, $b and $c were formulas). Is that (a) faulty memory on my part, (b) a known issue, or (c) now fixed?

If needed I can try and find more specifically what I was doing when I ran into the issue.

Thanks,
Gavin

In reply to Gavin LaRose

Re: Use of MathObjects

by Davide Cervone -
If $a, $b, and $c are all reals, then there should be no problem. It is the case where one ore more is a Formula that things are different. In that case, $vec will be a Formula object, not a Vector, and so the value() method just returns the Formula itself. A Vector-valued Formula is still a Formula, not a Vector, and Formulas do work differently.

While it is true that more could be done with Formulas, you are now moving into the situation where you need a CAS. For example, if your vector-valued formula were from $u x $v, it is more difficult to obtain the individual coordinates. And if $v above were the formula $M*$w for a matrix $M and vector $w, it gets even uglier.

I don't foresee MathObjects being able to take coordinates of Formula objects in general. This is just beyond its design scope.

Davide
In reply to Gavin LaRose

Re: Use of MathObjects

by Paul Pearson -
Hi Gavin,

I think the issue you're referring to also occurs for things like

Compute("<t,t^2,sin(t)>")->value;

that construct a vector of functions in a different way. The prior discussion you are alluding to is

http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2284

Notice that at the end of that discussion I gave a total "hack" of an answer that should probably never be used (so please don't read that part wink).

Best regards,

Paul


In reply to Paul Pearson

Re: Use of MathObjects

by Davide Cervone -
Actually, there is a special case that I didn't mention, and it is where the Formula is created as an explicit vector, point, or matrix. In that case, the value() method does break up the object into its components, so
    ($f1,$f2,$f3) = Compute("<t,t^2,sin(t)>")->value;
will make $f1 be t, $f2 would be t^2, and $f3 would be sin(t).

This is useful when you are using objects you have created itself, but it would not be wise to use this on student answers, since there is no guarantee that they will have entered a vector in this way (and not as a vector expression). I suppose one could test the class of the top-level node of the expression tree, but it is not clear what you would do if it weren't a Vector.

Davide

In reply to Michael Doob

Re: Use of MathObjects

by Davide Cervone -
The call to ans_array leave a button with a pi on it over the (1,2) entry.

As others have pointed out, this is a link to the MathView editor. It is relatively new, and there are still kinks being worked out. Personally, I think the icon is too big, and I'd like a user-preference for turing it off, but it is what it is. There is a course-wide preference for it in the configuration manager. In the slides for my talk, I did a sneaky thing to make the icon never appear, but I would like to see it at least be removed when the input item loses focus.

Only the first entry is highlighted in red or green for correct/incorrect.

Yes, that is true. The coloring of the entry blanks is also relatively new, and MathObjects long predates that. Mike has been making lots of new changes to the core PG functions to help make things like the coloring of answer blanks work better. I understand there is support for groups of answer blanks.

The answer array has only one answer checker, but several blanks, and really only one of them is officially tied to the answer checker (and so it get the color). The Matrix object takes care of looking for the other ones when the answer is checked. But PG doesn't know about the other ones, so doesn't know to color them, and MathObjects doesn't know about colored blanks (since it was written before they were available), so we have the present unhappy situation of bad coloring for answer arrays.

This is something I need to fix, and that means learning Mikes new features for answer grouping. This is on the list of things to do, but hasn't been done yet.

It would be nice to have a Context('rational')

I'm not sure exactly what you are after. If you are after rational numbers (i.e., fractions), then there is already a contextFraction.pl that implements that. If you are after rational functions, Gavin already pointed you to a context for that. If neither of these is what you are after, can you be more precise about what you are looking for?
In reply to Michael Doob

Re: Use of MathObjects

by Davide Cervone -
Finally, here is an assign function that you can use to modify the entries in a Matrix. The simplest form is the following:
    sub assign {
      my $self = shift; my $index = shift; my $x = shift;
      my $i = shift(@$index) - 1;
      if (scalar(@$index)) {assign($self->{data}[$i],$index,$x)}
        else {$self->{data}[$i] = Value::makeValue($x)}
    }
This takes three arguments, the Matrix to modify, an array reference of the indices of the entry to change, and the value to place at that entry. Note that the indices are shifted, so [1,1] refers to the first entry (not [0,0] as needed in Perl).

For example

    $M = Matrix([1,2],[3,4]);
    assign($M,[2,1],10);
leaves $M as the matrix [[1,2][10,4]].

Note that Matrix objects are more general than just n x m matrices; they can be any dimension (1 or 3 or whatever). For example

    $M1 = Matrix([1,2,3]);   # 1-dimensional, basically a vector
    $M3 = Matrix([[1,2],[3,4]],[[5,6],[7,8]]);  # 3D matrix
This assign() function works no matter what the dimension. For example, you could use
    assign($M3,[2,1,1],-50);
to replace the 5 in $M3 by -50.

The assignment function operates recursively by pulling off the first index, i from the list and applying assign() to a sub-matrix (the i-th entry in its list of data). When it gets to the last entry, it replaces the element with the given value.

Note that this means you could replace not just a single entry, but an entire sub-matrix. For example, in a 2D matrix, you could replace a whole row, as in

    assign($M,[1],[10,20]);
to replace the first row by the row [10,20].

Of course, the assignment function really should be more sophisticated and do some error checking to make sure that you are replacing an entry with something compatible (i.e., an entry with a single number, or a row with another row of the correct size).

Here's a modified version that gives you the idea

    sub assign {
      my $self = shift; my $index = shift; my $x = shift;
      my $i = shift(@$index) - 1;
      Value->Error('Entry doesn't exist: %d",%i) unless defined $self->{data}[$i];
      if (scalar(@$index)) {
        assign($self->{data}[$i],$index,$x);
      } else {
        $x = Value::makeValue($x);
        my $a = $self->{data}[$i];
        Value->Error("Entry type should be %s not %s",$a->type,$x->type)
          unless $a->type eq $x->type;
        if ($a->type eq 'Matrix') {
          my ($da,$dx) = (join('x',$a->dimensions),join('x',$x->dimensions));
          Value->Error("Entry dimension should be %s not %s",$da,$dx)
            unless $da eq $dx;
        }
        $self->{data}[$i] = $x;
      }
    }
Anyway, hope these help.
In reply to Davide Cervone

Re: Use of MathObjects

by Michael Doob -
Wow, thanks for all the good replies. It is a bit like trying to take a sip of water from a fire hose. I will continue to digest, if I may mix my metaphors.

I wasn't very clear on my last point about rationals.  We have among the math object classes both Real and Complex. I was suggesting the addition of the rational numbers to this set. My motivation was the consideration of the reduced row echelon form of integral matrices. They have rational entries and sometimes
makes more sense to work with them that way. Since I did the original reductions in code using perl, there was no option for rationals at that point. With the matrix as a Math Object there is at least the possibility. An entry displayed as 9/13 will surely be more informative than 0.6923077.  It would be nice to have 6/5 and 1.2 equivalent in answer evaluation as it is for reals. 

Anyway, I was just trying to throw this into the hopper of ideas.

Cheers,
Michael

In reply to Michael Doob

Re: Use of MathObjects

by Gavin LaRose -
Hi Michael,

Is contextFraction what you want?
http://webwork.maa.org/pod/pg_TRUNK/macros/contextFraction.pl.html or
http://webwork.maa.org/viewvc/system/trunk/pg/macros/contextFraction.pl?view=markup

Gavin