WeBWorK Problems

Some WeBWorK (Perl) code for Matrix Math Objects

Some WeBWorK (Perl) code for Matrix Math Objects

by Christopher Heckman -
Number of replies: 2
I'm busy relearning how to author problems in WeBWorK (especially with the addition of MathObjects) and want to share some code that I've written. Feel free to use it; just don't claim it as your own. More will follow.

assign(M, [i, j], k) replaces M(i,j) with k.

random_vector (n, m) will generate a random vector of length n whose entries are at most m in absolute value

rand_pmDet1(n, m) will generate a random nxn matrix whose entries are bounded by a function of m (nm2, probably).

random_perm_matrix(n) generates a random nxn permutation matrix

zero_matrix(m,n) generates the mxn zero matrix.

rowop_multiplyrow (n, i, m)
rowop_swaprows (n, i, j)
rowop_addmultrow (n, i, j, m)

all generate elementary matrices (to simulate row operations); they all return nxn matrices. i and j represent relevant row numbers, and m is the multiplier.

random_rref (m, n, r, M, L)
random_ref (m, n, r, M, L) 

generate random matrices which are in RREF or REF. The matrix returned is mxn and has rank r. The absolute values of non-pivot entries are at most M. L is a list of columns required to have pivots. Thus, 
B = random_rref (4, 5, 3, 2, (1,3)) generates a 4x5 matrix B in RREF with rank 3, whose entries are all at most 2 in absolute value, and has pivots in columns 1 and 3. A matrix which is 4x5 and has rank 3 can be created by multiplying B on the left by an invertible 4x4 matrix (not too difficult to code; I'll probably add it later). WARNING: No common-sense checking is done, so make sure you send legitimate values (r <= min(m,n), for instance).

make_RHS (A, r, m, c?): creates a right-hand side B for a system of linear equations. A is the coefficient matrix (in REF or RREF), r is its rank, m is a bound on the size of the entries of B, and the system is made consistent iff c is nonzero. (Yes, my code for finding the rank could be used to write a version that calculates r on its own, but typically when you are writing problems, you will choose r yourself, so you may as well send along information that you already have.)

num_zeroCols (A) is the number of all-zero columns of A.

Mat2System is an item on the wish list. It takes a matrix A, a vector B, and a list of variables L, and sets up the system of linear equations nicely. Note that the order of arguments has been changed; an example of the proper way to use it is:

$s = Mat2System ($A, $b, qw(x y z));
...
Consider the system of linear equations \[$s\] 
...

In case you don't know everything about Perl (I for one don't), the qw will take what follows and turn it into a list of strings: 

qw(x y z) ---> ("x", "y", "z");

You can even put in subscripts, as in: qw(x_1 x_2 x_3 x_4)

That's all for now.

sub assign { # assign ($L, [0,2], 13);    NOT mine
      my $self = shift; my $index = shift; my $x = shift;
      my $i = shift(@$index);
      if (scalar(@$index)) {assign($self->{data}[$i],$index,$x)}
      else {$self->{data}[$i] = Value::makeValue($x)}
    }

sub random_vector { # rand_vector (n, mult)
   my $n = shift; my $m = shift;
   my $M = Value::Matrix->I($n);
   for ($i = 0; $i < $n; $i++) { assign ($M, [$i, 0], random(-$m,$m)); }
   return Vector($M->column(1));
   }

sub rand_pmDet1 { # rand_pmDet1(n, mult)
   my $n = shift; my $m = shift;
   my $L = Value::Matrix->I($n);
   my $U = Value::Matrix->I($n);
   for ($i = 0; $i < $n; $i++) { 
      assign ($L, [$i, $i], non_zero_random(-1,1));
      for ($j = $i + 1; $j < $n; $j++) { 
         assign ($U, [$i, $j], random(-$m, $m));
         assign ($L, [$j, $i], random (-$m, $m)); 
         }
      }
   return random_perm_matrix ($n) * $L * $U;
   }

sub random_perm_matrix { # random_perm_matrix(n)
   my $n = shift;
   my $P = Value::Matrix->I($n);
   my $i, $j;
   my @permutation = NchooseK($n,$n);
   for ($i = 1; $i <= $n; $i++) { 
      assign($P, [$i, $i], 0); 
      assign($P, [$i, $permutation[$i]], 1);
      }
   $P;
   }

sub zero_matrix { # zero_matrix(m,n)
   my $m = shift; my $n = shift;
   my $M = Value::Matrix->I($m);
   assign ($M, [0,0], 0);
   my $A = $M -> column(1);
   for ($i = 0; $i < $m; $i++) { for ($j = 0; $j < $n; $j++) { assign ($A, [$i,$j], 0); } }
   $A
   }

 


sub rowop_multiplyrow { # rowop_multiplyrow (n, i, M);
   my $A = Value::Matrix->I(shift);
   my $i = shift;
   assign ($A, [$i - 1, $i - 1], shift);
   $A;
   }

sub rowop_swaprows { # rowop_swaprows (n, i, j);
   my $A = Value::Matrix->I(shift);
   my $i = shift; my $j = shift;
   assign ($A, [$i - 1, $i - 1], 0);
   assign ($A, [$j - 1, $j - 1], 0);
   assign ($A, [$i - 1, $j - 1], 1);
   assign ($A, [$j - 1, $i - 1], 1);
   $A;
   }

sub rowop_addmultrow { # rowop_swaprows (n, i, j, M);
   my $A = Value::Matrix->I(shift);
   my $i = shift; my $j = shift;
   my $M = shift;
   assign ($A, [$i - 1, $j - 1], $M);
   $A;
   }

sub multiple { my $m = non_zero_random(-5,5); redo if ($m == 1); $m; }


sub random_rref { # random_rref (m, n, r, M, L);
   my $m = shift; my $n = shift; my $r = shift; my $M = shift;
   my @L = @_;
   my @Lrest, $i, $indextoremove, $ro, $c;
   my $Llength = scalar @L;
   my @pivotCols;
   for ($i = 0; $i < $n; $i++) { $Lrest[$i] = $i + 1; }
   for ($i = 0; $i < $Llength; $i++) {
      $pivotCols[$i] = $L[$i];
      for ($j = 0; $j < $n - $i; $j++) 
         { if ($L[$i] == $Lrest[$j]) { $indextoremove = $j; } }
      splice (@Lrest, $indextoremove, 1);
      }
   my @restOfPivots = NchooseK ($n - $Llength, $r - $Llength);
   for ($i = 0; $i < $r - $Llength; $i++) 
      { $pivotCols[$i + $Llength] = $Lrest[$restOfPivots[$i]]; }
   @pivotCols = num_sort (@pivotCols);
   $R = Value::Matrix->I($m);
   for ($i = 0; $i < $m; $i ++) 
      { for ($j = 0; $j < $n; $j ++) { assign ($R, [$i,$j], 0); } }
   for ($i = 0; $i < $r; $i ++) { assign ($R, [$i, $pivotCols[$i] - 1], 1); }
   for ($i = 0; $i < $r - 1; $i++) {
      for ($c = $pivotCols[$i] + 1; $c < $pivotCols[$i + 1]; $c ++) { 
         for ($ro = 0; $ro <= $i; $ro++) 
            { assign ($R, [$ro, $c-1], random(-$M,$M)); } 
         }
      }
   for ($c = $pivotCols[$r - 1] + 1; $c <= $n; $c++) {
      for ($ro = 0; $ro < $r; $ro++) 
         { assign ($R, [$ro, $c - 1], random(-$M, $M)); }
      }
   $R;
   }

sub random_ref { # random_rref (m, n, r, M, L);
   my $m = shift; my $n = shift; my $r = shift; my $M = shift;
   my @L = @_;
   my @Lrest, $i, $indextoremove, $ro, $c;
   my $Llength = scalar @L;
   my @pivotCols;
   for ($i = 0; $i < $n; $i++) { $Lrest[$i] = $i + 1; }
   for ($i = 0; $i < $Llength; $i++) {
      $pivotCols[$i] = $L[$i];
      for ($j = 0; $j < $n - $i; $j++) 
         { if ($L[$i] == $Lrest[$j]) { $indextoremove = $j; } }
      splice (@Lrest, $indextoremove, 1);
      }
   my @restOfPivots = NchooseK ($n - $Llength, $r - $Llength);
   for ($i = 0; $i < $r - $Llength; $i++) 
      { $pivotCols[$i + $Llength] = $Lrest[$restOfPivots[$i]]; }
   @pivotCols = num_sort (@pivotCols);
   $R = Value::Matrix->I($m);
   for ($i = 0; $i < $m; $i ++) 
      { for ($j = 0; $j < $n; $j ++) { assign ($R, [$i,$j], 0); } }
   for ($i = 0; $i < $r; $i ++) { 
      assign ($R, [$i, $pivotCols[$i] - 1], 1); 
      for ($ro = 0; $ro < $i; $ro++) 
         { assign ($R, [$ro, $pivotCols[$i] - 1], random (-$M, $M)); }
      }
   for ($i = 0; $i < $r - 1; $i++) {
      for ($c = $pivotCols[$i] + 1; $c < $pivotCols[$i + 1]; $c ++) { 
         for ($ro = 0; $ro <= $i; $ro++) 
            { assign ($R, [$ro, $c-1], random(-$M,$M)); } 
         }
      }
   for ($c = $pivotCols[$r - 1] + 1; $c <= $n; $c++) {
      for ($ro = 0; $ro < $r; $ro++) 
         { assign ($R, [$ro, $c - 1], random(-$M, $M)); }
      }
   $R;
   }

sub make_RHS { # make_RHS (LHS, rank, multiplier, consistent?)
   my $A = shift; my $r = shift; my $m = shift; my $b = shift;
   my $mA = ($A->dimensions)[0];
   my $RHS = zero_matrix ($mA, 0);
   for ($i = 0; $i < $r; $i++) { assign ($RHS, [$i,0], random(-$m, $m)); }
   if ((! $b) && ($r < $mA)) { assign ($RHS, [$r,0], 1); }
   $RHS;
   } 

sub num_zeroCols { # num_zeroCols (A)
   my $A = shift;
   my $s, $nz, $m;
   $nz = 0;
   $m = ($A->dimensions)[0];
   for ($i = 1; $i <= ($A->dimensions)[1]; $i++) {
      $s = 0;
      for ($j = 1; $j <= $m; $j++) { $s += ($A->element($j,$i)) ** 2; }
      if ($s < 10 ** (-8)) { $nz ++; }
      } 
   $nz;
   }

sub Mat2System{ # Mat2System (A, b, qw(x y z w))
   my $coeffs = shift;
   my $vname = shift;
   my @vec = @_;
   my $srow = ($coeffs->dimensions)[0];
   my $scol = ($coeffs->dimensions)[1];
   my $vnamerow = scalar @vec;
   my $vrow = ($vname->dimensions)[0];
   die "Wrong number of rows or columns2" if  ($vrow != $srow);
   die "Wrong number of rows or columns4" if ($scol != $vnamerow);
   my $outstr="\begin{array}";
   my $s;
   $outstr .= '{r';
   for (my $j=0; $j<$scol; $j++) { $outstr .= 'rr'; }
   $outstr .= 'r}';
   for (my $j = 0; $j < $srow; $j ++) { 
      $s = 0; 
      for (my $i = 0, my $vn = 1; $i < $scol; $i++, $vn++) { 
         my $varname = $vec [$vn - 1];
         my $a=$coeffs->element($j+1,$i+1); 
         if ($a == 0) {
            if (($s > 0) || ($i < $scol - 1)) { $outstr .= '&&'; }
            else { $outstr .= '&0'; }
            }
         elsif ($a > 0) { 
            if ($a == 1) { $a = ""; } 
            if ($s==0) {$outstr .= "& $a \,$varname"; $s = 1; }
            else {$outstr .= "&+& $a \, $varname"; } 
            }
         else { 
            if ($s == 1) { 
               $a = -$a; 
               if ($a == 1) { $a = ""; } 
               $outstr .= "&- &$a \,$varname"; 
               }
            else {
               if ($a == -1) { $a = "-"; }
               $outstr = $outstr . "& $a \, $varname"; $s = 1;
               }
            } 
         } 
      $outstr .= "&=&" . $vname->element($j+1, 1). "\\"; 
      } 
   $outstr . ' \end{array}'; 
   }

In reply to Christopher Heckman

Re: Some WeBWorK (Perl) code for Matrix Math Objects

by Christopher Heckman -

(Sorry about the down-thumbs; they should be ( n )'s instead.

Here's more code:

random_invertible (n, M) returns an invertible nxn matrix whose entries are at most M in absolute value.

random_rank_matrix (m, n, r, M, L) (same parameter list as random_rref) returns an mxn matrix whose rank is r. Matrix entries are "not too big".

latex_mat (A) provides the LaTeX necessary to show the matrix A, with no delimiters.

latex_det (A) uses it to draw determinant bars around the matrix A.

latex_augmat (A, b) uses it to display a system of linear equations in matrix form [A|b].

---------

sub random_invertible { # random_invertible (n, M)
   my ($n, $M) = @_;
   my @A;
   my $B;
   while (1)  {
      for (my $i = 0; $i < $n; $i++) { 
         for (my $j = 0; $j < $n; $j++) { $A[$i][$j] = random (-$M, $M); }
         }
      $B = Matrix(@A);
      if ($B->det != 0) { return ($B) };
      }
   }

sub random_rank_matrix { # random_rank_matrix (m, n, r, M, L)
   random_invertible (@_[0], @_[3]) * random_rref (@_);
   }

sub latex_mat{ # latex_mat (A); returns LaTeX (no delimiters)
   my $coeffs = shift;
   my ($srow, $scol) = $coeffs -> dimensions;
   my $outstr="\begin{array}";
   $outstr .= '{' . ('r' x $scol) . '}';
   for (my $j = 0; $j <= $srow; $j ++) {
      $outstr .= $coeffs -> element($j, 0); 
      for (my $i = 1; $i <= $scol; $i++) 
         { $outstr .= '&' . ($coeffs -> element ($j, $i)); }
      $outstr .= '\\';
      } 
   $outstr . ' \end{array}'; 
   }

sub latex_det { "\left|" . latex_mat (@_[0]) . "\right|"; } # latex_det (A)

sub latex_augmat { # latex_augmat (A, b)
   '\left[\left. ' . latex_mat (@_[0]) . '\right|' . latex_mat (@_[1]) . '\right]';
   }


In reply to Christopher Heckman

Re: Some WeBWorK (Perl) code for Matrix Math Objects

by Christopher Heckman -
In this post: a Mat2System that works with MathObjects.

Syntax: Mat2System ($A, @variable_list, $b) displays a system of linear equations, where

$A is a Matrix
$b is a Matrix or a Vector (or a ColumnVector)
@variable_list is a (Perl) list of strings (variable names)

Example usage: 

Mat2System (Matrix ([[1,2],[3,4]], "x", "y", Vector([5,6]))
Mat2System (Matrix ([[1,2,3],[4,5,6]], qw ("x_1 x_2 x_3 "), Matrix([[1],[2]]))

Code: (Free to use, as long as you credit me and Thomas Hagedorn (who wrote the original version))

sub Mat2System{ # Mat2System (A, qw(x y z w), b)
   my $coeffs = shift;
   my @vec = @_;
   my $vname = pop @vec;
   my ($srow, $scol) = $coeffs->dimensions;
   my $vnamerow = scalar @vec;
   my $vrow;
   if ($vname -> class eq "Matrix") { $vrow = ($vname->dimensions)[0] }
   if ($vname -> class eq "Vector") { $vrow = $vname -> length; }
   die "Wrong number of rows or columns2" if  ($vrow != $srow);
   die "Wrong number of rows or columns4" if ($scol != $vnamerow);
   my $outstr="\begin{array}";
   my $s;
   $outstr .= '{' . ('r' x (2 * $scol + 2)) . '}';
   for (my $j = 0; $j < $srow; $j ++) { 
      $s = 0; 
      for (my $i = 0; $i < $scol; $i++) { 
         my $varname = $vec [$i]; 
         my $a = $coeffs->element ($j + 1, $i + 1); 
         if ($a == 0) {
            if (($s > 0) || ($i < $scol - 1)) { $outstr .= '&&'; }
            else { $outstr .= '&0'; }
            }
         elsif ($a > 0) { 
            if ($a == 1) { $a = ""; } 
            if ($s==0) {$outstr .= "& $a \,$varname"; $s = 1; }
            else {$outstr .= "&+& $a \, $varname"; } 
            }
         else { 
            if ($s == 1) { 
               $a = -$a; 
               if ($a == 1) { $a = ""; } 
               $outstr .= "&- &$a \,$varname"; 
               }
            else {
               if ($a == -1) { $a = "-"; }
               $outstr = $outstr . "& $a \, $varname"; $s = 1;
               }
            } 
         } 
      if ($vname -> class eq "Matrix") { $outstr .= "&=&" . $vname->element($j+1, 1). "\\"; }
      if ($vname -> class eq "Vector") { $outstr .= "&=&" . $vname->extract ($j+1). "\\"; }
      } 
   $outstr . ' \end{array}'; 
   }