[system] / trunk / pg / macros / PGmorematrixmacros.pl Repository:
ViewVC logotype

Diff of /trunk/pg/macros/PGmorematrixmacros.pl

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

Revision 1896 Revision 3314
1BEGIN{ 1BEGIN{
2 be_strict(); 2 be_strict();
3} 3}
4 4
5sub _PGmorematrixmacros_init{} 5sub _PGmorematrixmacros_init{}
6 6
7sub random_inv_matrix { ## Builds and returns a random invertible \$row by \$col matrix. 7sub random_inv_matrix { ## Builds and returns a random invertible \$row by \$col matrix.
29This method returns a random nxn diagonal matrix. 29This method returns a random nxn diagonal matrix.
30 30
31=cut 31=cut
32 32
33sub random_diag_matrix{ ## Builds and returns a random diagonal \$n by \$n matrix 33sub random_diag_matrix{ ## Builds and returns a random diagonal \$n by \$n matrix
34 34
35 warn "Usage: \$new_matrix = random_diag_matrix(\$n)" if (@_ != 1); 35 warn "Usage: \$new_matrix = random_diag_matrix(\$n)" if (@_ != 1);
36 36
37 my $D = new Matrix($_[0],$_[0]); 37 my $D = new Matrix($_[0],$_[0]);
38 my $norm = 0; 38 my $norm = 0;
39 while( $norm == 0 ){ 39 while( $norm == 0 ){
40 foreach my $i (1..$_[0]){ 40 foreach my $i (1..$_[0]){
41 foreach my $j (1..$_[0]){ 41 foreach my $j (1..$_[0]){
42 if( $i != $j ){ 42 if( $i != $j ){
43 $D->assign($i,$j,0); 43 $D->assign($i,$j,0);
44 }else{ 44 }else{
45 $D->assign($i,$j,random(-9,9,1)); 45 $D->assign($i,$j,random(-9,9,1));
46 } 46 }
47 } 47 }
48 } 48 }
49 $norm = abs($D); 49 $norm = abs($D);
50 } 50 }
51 return $D; 51 return $D;
52} 52}
53 53
54sub swap_rows{ 54sub swap_rows{
55 55
56 warn "Usage: \$new_matrix = swap_rows(\$matrix,\$row1,\$row2);" 56 warn "Usage: \$new_matrix = swap_rows(\$matrix,\$row1,\$row2);"
110that to determine the existence of the change of coordinate matrix going the 110that to determine the existence of the change of coordinate matrix going the
111other way. 111other way.
112 112
113ANS( basis_cmp( vectors_as_array_ref_in_array_ref, options_hash ) ); 113ANS( basis_cmp( vectors_as_array_ref_in_array_ref, options_hash ) );
114 114
115 1. a reference to an array of correct vectors 115 1. a reference to an array of correct vectors
116 2. a hash with the following keys (all optional): 116 2. a hash with the following keys (all optional):
117 mode -- 'basis' (default) (only a basis allowed) 117 mode -- 'basis' (default) (only a basis allowed)
118 'orthogonal' (only an orthogonal basis is allowed) 118 'orthogonal' (only an orthogonal basis is allowed)
119 'unit' (only unit vectors in the basis allowed) 119 'unit' (only unit vectors in the basis allowed)
120 'orthonormal' (only orthogonal unit vectors in basis allowed) 120 'orthonormal' (only orthogonal unit vectors in basis allowed)
121 zeroLevelTol -- absolute tolerance to allow when answer is close 121 zeroLevelTol -- absolute tolerance to allow when answer is close
122 to zero 122 to zero
123 123
124 debug -- if set to 1, provides verbose listing of 124 debug -- if set to 1, provides verbose listing of
125 hash entries throughout fliters. 125 hash entries throughout fliters.
126 126
127 help -- 'none' (default) (is quiet on all errors) 127 help -- 'none' (default) (is quiet on all errors)
128 'dim' (Tells student if wrong number of vectors are entered) 128 'dim' (Tells student if wrong number of vectors are entered)
129 'length' (Tells student if there is a vector of the wrong length) 129 'length' (Tells student if there is a vector of the wrong length)
130 'orthogonal' (Tells student if their vectors are not orthogonal) 130 'orthogonal' (Tells student if their vectors are not orthogonal)
131 (This is only in orthogonal mode) 131 (This is only in orthogonal mode)
132 'unit' (Tells student if there is a vector not of unit length) 132 'unit' (Tells student if there is a vector not of unit length)
133 (This is only in unit mode) 133 (This is only in unit mode)
134 'orthonormal' (Gives errors from orthogonal and orthonormal) 134 'orthonormal' (Gives errors from orthogonal and orthonormal)
135 (This is only in orthonormal mode) 135 (This is only in orthonormal mode)
136 'verbose' (Gives all the above answer messages) 136 'verbose' (Gives all the above answer messages)
137 137
138 Returns an answer evaluator. 138 Returns an answer evaluator.
139 139
140EXAMPLES: 140EXAMPLES:
141 141
142 basis_cmp([[1,0,0],[0,1,0],[0,0,1]]) 142 basis_cmp([[1,0,0],[0,1,0],[0,0,1]])
143 -- correct answer is any basis for R^3. 143 -- correct answer is any basis for R^3.
144 basis_cmp([1,0,2,0],[0,1,0,0], 'mode'=>orthonormal ) 144 basis_cmp([1,0,2,0],[0,1,0,0], 'mode'=>orthonormal )
145 -- correct answer is any orthonormal basis 145 -- correct answer is any orthonormal basis
146 for this space such as: 146 for this space such as:
147 [1/sqrt(3),0,2/sqrt(3),0],[0,1,0,0] 147 [1/sqrt(3),0,2/sqrt(3),0],[0,1,0,0]
148 148
149=cut 149=cut
150 150
151 151
152sub basis_cmp { 152sub basis_cmp {
153 my $correctAnswer = shift; 153 my $correctAnswer = shift;
154 my %opt = @_; 154 my %opt = @_;
155 155
156 set_default_options( \%opt, 156 set_default_options( \%opt,
157 'zeroLevelTol' => $main::functZeroLevelTolDefault, 157 'zeroLevelTol' => $main::functZeroLevelTolDefault,
158 'debug' => 0, 158 'debug' => 0,
159 'mode' => 'basis', 159 'mode' => 'basis',
160 'help' => 'none', 160 'help' => 'none',
161 ); 161 );
162 162
163 # produce answer evaluator 163 # produce answer evaluator
164 BASIS_CMP( 164 BASIS_CMP(
165 'correct_ans' => $correctAnswer, 165 'correct_ans' => $correctAnswer,
166 'zeroLevelTol' => $opt{'zeroLevelTol'}, 166 'zeroLevelTol' => $opt{'zeroLevelTol'},
167 'debug' => $opt{'debug'}, 167 'debug' => $opt{'debug'},
168 'mode' => $opt{'mode'}, 168 'mode' => $opt{'mode'},
169 'help' => $opt{'help'}, 169 'help' => $opt{'help'},
170 ); 170 );
171} 171}
172 172
173=head BASIS_CMP 173=head BASIS_CMP
174 174
175Made to keep the same format as num_cmp and fun_cmp. 175Made to keep the same format as num_cmp and fun_cmp.
176 176
177=cut 177=cut
178 178
179sub BASIS_CMP { 179sub BASIS_CMP {
180 my %mat_params = @_; 180 my %mat_params = @_;
181 my $zeroLevelTol = $mat_params{'zeroLevelTol'}; 181 my $zeroLevelTol = $mat_params{'zeroLevelTol'};
182 182
183 # Check that everything is defined: 183 # Check that everything is defined:
184 $mat_params{debug} = 0 unless defined($mat_params{debug}); 184 $mat_params{debug} = 0 unless defined($mat_params{debug});
185 $zeroLevelTol = $main::functZeroLevelTolDefault unless defined $zeroLevelTol; 185 $zeroLevelTol = $main::functZeroLevelTolDefault unless defined $zeroLevelTol;
186 $mat_params{'zeroLevelTol'} = $zeroLevelTol; 186 $mat_params{'zeroLevelTol'} = $zeroLevelTol;
187 187
188## This is where the correct answer should be checked someday. 188## This is where the correct answer should be checked someday.
189 my $matrix = Matrix->new_from_col_vecs($mat_params{'correct_ans'}); 189 my $matrix = Matrix->new_from_col_vecs($mat_params{'correct_ans'});
190 190
191#construct the answer evaluator 191#construct the answer evaluator
192 my $answer_evaluator = new AnswerEvaluator; 192 my $answer_evaluator = new AnswerEvaluator;
193 193
194 $answer_evaluator->{debug} = $mat_params{debug}; 194 $answer_evaluator->{debug} = $mat_params{debug};
195 $answer_evaluator->ans_hash( 195 $answer_evaluator->ans_hash(
196 correct_ans => display_correct_vecs($mat_params{correct_ans}), 196 correct_ans => display_correct_vecs($mat_params{correct_ans}),
197 rm_correct_ans => $matrix, 197 rm_correct_ans => $matrix,
198 zeroLevelTol => $mat_params{zeroLevelTol}, 198 zeroLevelTol => $mat_params{zeroLevelTol},
199 debug => $mat_params{debug}, 199 debug => $mat_params{debug},
200 mode => $mat_params{mode}, 200 mode => $mat_params{mode},
201 help => $mat_params{help}, 201 help => $mat_params{help},
202 ); 202 );
203 203
204 $answer_evaluator->install_pre_filter( 204 $answer_evaluator->install_pre_filter(
205 sub {my $rh_ans = shift; 205 sub {my $rh_ans = shift;
206 $rh_ans->{_filter_name} = 'remove_white_space'; 206 $rh_ans->{_filter_name} = 'remove_white_space';
207 $rh_ans->{student_ans} =~ s/\s+//g; # remove all whitespace 207 $rh_ans->{student_ans} =~ s/\s+//g; # remove all whitespace
208 $rh_ans; 208 $rh_ans;
209 } 209 }
210 ); 210 );
211 $answer_evaluator->install_pre_filter( 211 $answer_evaluator->install_pre_filter(
212 sub{my $rh_ans = shift; 212 sub{my $rh_ans = shift;
213 my @options = @_; 213 my @options = @_;
214 $rh_ans->{_filter_name} = 'mung_student_answer'; 214 $rh_ans->{_filter_name} = 'mung_student_answer';
215 if( $rh_ans->{ans_label} =~ /ArRaY/ ){ 215 if( $rh_ans->{ans_label} =~ /ArRaY/ ){
216 $rh_ans = ans_array_filter($rh_ans,@options); 216 $rh_ans = ans_array_filter($rh_ans,@options);
217 my @student_array = @{$rh_ans->{ra_student_ans}}; 217 my @student_array = @{$rh_ans->{ra_student_ans}};
218 my @array = (); 218 my @array = ();
219 for( my $i = 0; $i < scalar(@student_array) ; $i ++ ) 219 for( my $i = 0; $i < scalar(@student_array) ; $i ++ )
220 { 220 {
221 push( @array, Matrix->new_from_array_ref($student_array[$i])); 221 push( @array, Matrix->new_from_array_ref($student_array[$i]));
222 } 222 }
223 $rh_ans->{ra_student_ans} = \@array; 223 $rh_ans->{ra_student_ans} = \@array;
224 $rh_ans; 224 $rh_ans;
225 }else{ 225 }else{
226 $rh_ans->{student_ans} = math_constants($rh_ans->{student_ans}); 226 $rh_ans->{student_ans} = math_constants($rh_ans->{student_ans});
227 vec_list_string($rh_ans, '_filter_name' => 'vec_list_string', @options); 227 vec_list_string($rh_ans, '_filter_name' => 'vec_list_string', @options);
228 } 228 }
229 } 229 }
230 );#ra_student_ans is now the students answer as an array of vectors 230 );#ra_student_ans is now the students answer as an array of vectors
231 # anonymous subroutine to check dimension and length of the student vectors 231 # anonymous subroutine to check dimension and length of the student vectors
232 # if either is wrong, the answer is wrong. 232 # if either is wrong, the answer is wrong.
233 $answer_evaluator->install_pre_filter( 233 $answer_evaluator->install_pre_filter(
234 sub{ 234 sub{
235 my $rh_ans = shift; 235 my $rh_ans = shift;
236 $rh_ans->{_filter_name} = 'check_vector_size'; 236 $rh_ans->{_filter_name} = 'check_vector_size';
237 my $length = $rh_ans->{rm_correct_ans}->[1]; 237 my $length = $rh_ans->{rm_correct_ans}->[1];
238 my $dim = $rh_ans->{rm_correct_ans}->[2]; 238 my $dim = $rh_ans->{rm_correct_ans}->[2];
239 if( $dim != scalar(@{$rh_ans->{ra_student_ans}}))
240 {
241
242 $rh_ans->{score} = 0;
243 if( $rh_ans->{help} =~ /dim|verbose/ )
244 {
245 $rh_ans->throw_error('EVAL','You have entered the wrong number of vectors.');
246 }else{
247 $rh_ans->throw_error('EVAL');
248 }
249 }
250 for( my $i = 0; $i < scalar( @{$rh_ans->{ra_student_ans} }) ; $i++ )
251 {
252 if( $length != $rh_ans->{ra_student_ans}->[$i]->[1])
253 {
254 $rh_ans->{score} = 0;
255 if( $rh_ans->{help} =~ /length|verbose/ )
256 {
257 $rh_ans->throw_error('EVAL','You have entered vector(s) of the wrong length.');
258 }else{
259 $rh_ans->throw_error('EVAL');
260 }
261 }
262 }
263 $rh_ans;
264 }
265 );
266 # Install prefilter for various modes
267 if( $mat_params{mode} ne 'basis' )
268 {
269 if( $mat_params{mode} =~ /orthogonal|orthonormal/ )
270 {
271 $answer_evaluator->install_pre_filter(\&are_orthogonal_vecs);
272 }
273
274 if( $mat_params{mode} =~ /unit|orthonormal/ )
275 {
276 $answer_evaluator->install_pre_filter(\&are_unit_vecs);
277
278 }
279 }
280 $answer_evaluator->install_evaluator(\&compare_basis, %mat_params);
281 $answer_evaluator->install_post_filter(
282 sub {my $rh_ans = shift;
283 if ($rh_ans->catch_error('SYNTAX') ) {
284 $rh_ans->{ans_message} = $rh_ans->{error_message};
285 $rh_ans->clear_error('SYNTAX');
286 }
287 if ($rh_ans->catch_error('EVAL') ) {
288 $rh_ans->{ans_message} = $rh_ans->{error_message};
289 $rh_ans->clear_error('EVAL');
290 }
291 $rh_ans;
292 }
293 );
294 $answer_evaluator;
295}
296
297=head4 compare_basis
298
299 compare_basis( $ans_hash,
300 %options
301 ra_student_ans # a reference to the array of students answer vectors
302 rm_correct_ans, # a reference to the correct answer matrix
303 %options
304 )
305
306
307=cut
308
309
310
311sub compare_basis {
312 my ($rh_ans, %options) = @_;
313 $rh_ans->{_filter_name} = "compare_basis";
314 my @ch_coord;
315 my @vecs = @{$rh_ans->{ra_student_ans}};
316
317 # A lot of the following code was taken from Matrix::proj_coeff
318 # calling this method recursively would be a waste of time since
319 # the prof's matrix never changes and solve_LR is an expensive
320 # operation. This way it is only done once.
321 my $matrix = $rh_ans->{rm_correct_ans};
322 my ($dim,$x_vector, $base_matrix);
323 my $errors = undef;
324 my $lin_space_tr= ~ $matrix; #transpose of the matrix
325 $matrix = $lin_space_tr * $matrix; #(~A * A)
326 my $matrix_lr = $matrix->decompose_LR();
327
328 #finds the coefficient vectors for each of the students vectors
329 for( my $i = 0; $i < scalar(@{$rh_ans->{ra_student_ans}}) ; $i++ ) {
330
331 $vecs[$i] = $lin_space_tr*$vecs[$i];
332 ($dim,$x_vector, $base_matrix) = $matrix_lr->solve_LR($vecs[$i]);
333 push( @ch_coord, $x_vector );
334 $errors = "A unique adapted answer could not be determined.
335 Possibly the parameters have coefficient zero.<br> dim = $dim base_matrix
336 is $base_matrix\n" if $dim; # only print if the dim is not zero.
337 }
338
339 if( defined($errors)) {
340 $rh_ans->throw_error('EVAL', $errors) ;
341 } else {
342 my $ch_coord_mat = Matrix->new_from_col_vecs(\@ch_coord);
343 #creates change of coordinate matrix
344 #existence of this matrix implies that
345 #the all of the students answers are a
346 #linear combo of the prof's
347 $ch_coord_mat = $ch_coord_mat->decompose_LR();
348
349 if( abs($ch_coord_mat->det_LR()) > $options{zeroLevelTol} ) {
350 # if the det of the change of coordinate matrix is
351 # non-zero, this implies the existence of an inverse
352 # which implies all of the prof's vectors are a linear
353 # combo of the students vectors, showing containment
354 # both ways.
355
356 # I think sometimes if the students space has the same dimension as the profs space it
357 # will get projected into the profs space even if it isn't a basis for that space.
358 # this just checks that the prof's matrix times the change of coordinate matrix is actually
359 #the students matrix
360 if( abs(Matrix->new_from_col_vecs(\@{$rh_ans->{ra_student_ans}}) -
361 ($rh_ans->{rm_correct_ans})*(Matrix->new_from_col_vecs(\@ch_coord)))
362 < $options{zeroLevelTol} ) {
363 $rh_ans->{score} = 1;
364 } else {
365 $rh_ans->{score} = 0;
366 }
367 } else {
368 $rh_ans->{score}=0;
369 }
370 }
371 $rh_ans;
372
373}
374
375
376=head 2 vec_list_string
377
378This is a check_syntax type method (in fact I borrowed some of that method's code) for vector input.
379The student needs to enter vectors like: [1,0,0],[1,2,3],[0,9/sqrt(10),1/sqrt(10)]
380Each entry can contain functions and operations and the usual math constants (pi and e).
381The vectors, however can not be added or multiplied or scalar multiplied by the student.
382Most errors are handled well. Any error in an entry is caught by the PG_answer_eval like it is in num_cmp or fun_cmp.
383Right now the method basically ignores every thing outside the vectors. Also, an unmatched open parenthesis is caught,
384but a unmatched close parenthesis ends the vector, and since everything outside is ignored, no error is sent (other than the
385later when the length of the vectors is checked.
386In the end, the method returns an array of Matrix objects.
387
388
389=cut
390
391sub vec_list_string{
392 my $rh_ans = shift;
393 my %options = @_;
394 my $i;
395 my $entry = "";
396 my $char;
397 my @paren_stack;
398 my $length = length($rh_ans->{student_ans});
399 my @temp;
400 my $j = 0;
401 my @answers;
402 my $paren;
403 my $display_ans;
404
405 for( $i = 0; $i < $length ; $i++ ) {
406 $char = substr($rh_ans->{student_ans},$i,1);
407
408 if( $char =~ /\(|\[|\{/ ){
409 push( @paren_stack, $char )
410 }
411
412 if( !( $char =~ /\(|\[|\{/ && scalar(@paren_stack) == 1 ) ) {
413 if( $char !~ /,|\)|\]|\}/ ){
414 $entry .= $char;
415 } else {
416 if( $char =~ /,/ || ( $char =~ /\)|\]|\}/ && scalar(@paren_stack) == 1 ) ) {
417 if( length($entry) == 0 ){
418 if( $char !~ /,/ ){
419 $rh_ans->throw_error('EVAL','There is a syntax error in your answer');
420 } else {
421 $rh_ans->{preview_text_string} .= ",";
422 $rh_ans->{preview_latex_string} .= ",";
423 $display_ans .= ",";
424 }
425 } else {
426
427 # This parser code was origianally taken from PGanswermacros::check_syntax
428 # but parts of it needed to be slighty modified for this context
429 my $parser = new AlgParserWithImplicitExpand;
430 my $ret = $parser -> parse($entry); #for use with loops
431
432 if ( ref($ret) ) { ## parsed successfully
433 $parser -> tostring();
434 $parser -> normalize();
435 $entry = $parser -> tostring();
436 $rh_ans->{preview_text_string} .= $entry.",";
437 $rh_ans->{preview_latex_string} .= $parser -> tolatex().",";
438
439 } else { ## error in parsing
440
441 $rh_ans->{'student_ans'} = 'syntax error:'.$display_ans. $parser->{htmlerror},
442 $rh_ans->{'ans_message'} = $display_ans.$parser -> {error_msg},
443 $rh_ans->{'preview_text_string'} = '',
444 $rh_ans->{'preview_latex_string'} = '',
445 $rh_ans->throw_error('SYNTAX', 'syntax error in answer:'.$display_ans.$parser->{htmlerror} . "$main::BR" .$parser -> {error_msg}.".$main::BR");
446 }
447
448 my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($entry);
449
450 if ($PG_eval_errors) {
451 $rh_ans->throw_error('EVAL','There is a syntax error in your answer.') ;
452 $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors);
453 last;
454 } else {
455 $entry = prfmt($inVal,$options{format});
456 $display_ans .= $entry.",";
457 push(@temp , $entry);
458 }
459
460 if( $char =~ /\)|\]|\}/ && scalar(@paren_stack) == 1) {
461 pop @paren_stack;
462 chop($rh_ans->{preview_text_string});
463 chop($rh_ans->{preview_latex_string});
464 chop($display_ans);
465 $rh_ans->{preview_text_string} .= "]";
466 $rh_ans->{preview_latex_string} .= "]";
467 $display_ans .= "]";
468 if( scalar(@temp) > 0 ) {
469 push( @answers,Matrix->new_from_col_vecs([\@temp]));
470 while(scalar(@temp) > 0 ){
471 pop @temp;
472 }
473 } else {
474 $rh_ans->throw_error('EVAL','There is a syntax error in your answer.');
475 }
476 }
477 }
478 $entry = "";
479 } else {
480 $paren = pop @paren_stack;
481 if( scalar(@paren_stack) > 0 ){
482 #this uses ASCII to check if the parens match up
483 # in ASCII ord ( = 40 , ord ) = 41 , ord [ = 91 ,
484 # ord ] = 93 , ord { = 123 , ord } = 125
485 if( (ord($char) - ord($paren) <= 2) ){
486 $entry = $entry . $char;
487 }else{
488 $rh_ans->throw_error('EVAL','There is a syntax error in your answer');
489 }
490 }
491 }
492 }
493 } else {
494 $rh_ans->{preview_text_string} .= "[";
495 $rh_ans->{preview_latex_string} .= "[";
496 $display_ans .= "[";
497 }
498 }
499 $rh_ans->{ra_student_ans} = \@answers;
500 $rh_ans->{student_ans} = $display_ans unless $rh_ans->{error_flag};
501 $rh_ans;
502}
503
504=head5
505
506 This filter was created to get, format, and evaluate each entry of the ans_array and ans_array_extension
507 answer entry methods. Running this filter is necessary to get all the entries out of the answer
508 hash. Each entry is evaluated and the resulting number is put in the display for student answer
509 as a string. For evaluation purposes an array of arrays of arrays is created called ra_student_ans
510 and placed in the hash. The entries are [array_number][row_number][column_number]. The latex strings
511 for each entry are taken from the parser and put, as a matrix, into the previewer. The preview text
512 string is also created, but this display method becomes confusing when large matrices are used.
513
514=cut
515
516
517sub ans_array_filter{
518 my $rh_ans = shift;
519 my %options = @_;
520# assign_option_aliases( \%opt,
521# );
522 set_default_options(\%options,
523 _filter_name => 'ans_array_filter',
524 );
525# $rh_ans->{ans_label} =~ /ArRaY(\d+)\[\d+,\d+,\d+\]/; # CHANGE made to accomodate HTML 4.01 standards for name attribute
526 $rh_ans->{ans_label} =~ /ArRaY(\d+)\_\_\d+:\d+:\d+\_\_/;
527 my $ans_num = $1;
528 my @keys = grep /ArRaY$ans_num/, keys(%{$main::inputs_ref});
529 my $key;
530 my @array = ();
531 my ($i,$j,$k) = (0,0,0);
532
533 #the keys aren't in order, so their info has to be put into the array before doing anything with it
534 foreach $key (@keys){
535# $key =~ /ArRaY\d+\[(\d+),(\d+),(\d+)\]/;
536# ($i,$j,$k) = ($1,$2,$3);
537# $array[$i][$j][$k] = ${$main::inputs_ref}{'ArRaY'.$ans_num.'['.$i.','.$j.','.$k.']'};
538 $key =~ /ArRaY\d+\_\_(\d+):(\d+):(\d+)\_\_/;
539 ($i,$j,$k) = ($1,$2,$3);
540 $array[$i][$j][$k] = ${$main::inputs_ref}{'ArRaY'.$ans_num.'__'.$i.':'.$j.':'.$k.'__'};
541
542 }
543 #$rh_ans->{debug_student_answer }= \@array;
544 my $display_ans = "";
545
546 for( $i=0; $i < scalar(@array) ; $i ++ ) {
547 $display_ans .= " [";
548 $rh_ans->{preview_text_string} .= ' [';
549 $rh_ans->{preview_latex_string} .= '\begin{pmatrix} ';
550 for( $j = 0; $j < scalar( @{$array[$i]} ) ; $j++ ) {
551 $display_ans .= " [";
552 $rh_ans->{preview_text_string} .= ' [';
553 for( $k = 0; $k < scalar( @{$array[$i][$j]} ) ; $k ++ ){
554 my $entry = $array[$i][$j][$k];
555 $entry = math_constants($entry);
556 # This parser code was origianally taken from PGanswermacros::check_syntax
557 # but parts of it needed to be slighty modified for this context
558 my $parser = new AlgParserWithImplicitExpand;
559 my $ret = $parser -> parse($entry); #for use with loops
560
561 if ( ref($ret) ) { ## parsed successfully
562 $parser -> tostring();
563 $parser -> normalize();
564 $entry = $parser -> tostring();
565 $rh_ans->{preview_text_string} .= $entry.",";
566 $rh_ans->{preview_latex_string} .= $parser -> tolatex() . '& ';
567
568 } else { ## error in parsing
569 $rh_ans->{'student_ans'} = 'syntax error:'.$display_ans. $parser->{htmlerror},
570 $rh_ans->{'ans_message'} = $display_ans.$parser -> {error_msg},
571 $rh_ans->{'preview_text_string'} = '',
572 $rh_ans->throw_error('SYNTAX', 'syntax error in answer:'.$display_ans.$parser->{htmlerror} . "$main::BR" .$parser -> {error_msg}.".$main::BR");
573 }
574
575 my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($entry);
576 if ($PG_eval_errors) {
577 $rh_ans->throw_error('EVAL','There is a syntax error in your answer.') ;
578 $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors);
579 last;
580 } else {
581 $entry = prfmt($inVal,$options{format});
582 $display_ans .= $entry.",";
583 $array[$i][$j][$k] = $entry;
584 }
585 }
586 chop($rh_ans->{preview_text_string});
587 chop($display_ans);
588 $rh_ans->{preview_text_string} .= '] ,';
589 $rh_ans->{preview_latex_string} .= '\\\\';
590 $display_ans .= '] ,';
591
592 }
593 chop($rh_ans->{preview_text_string});
594 chop($display_ans);
595 $rh_ans->{preview_text_string} .= '] ,';
596 $rh_ans->{preview_latex_string} .= '\end{pmatrix}'.' , ';
597 $display_ans .= '] ,';
598 }
599 chop($rh_ans->{preview_text_string});
600 chop($rh_ans->{preview_latex_string});
601 chop($rh_ans->{preview_latex_string});
602 chop($rh_ans->{preview_latex_string});
603 chop($display_ans);
604
605 my @temp = ();
606 for( $i = 0 ; $i < scalar( @array ); $i++ ){
607 push @temp , display_matrix($array[$i], 'left'=>'.', 'right'=>'.');
608 push @temp , "," unless $i == scalar(@array) - 1;
609 }
610 $rh_ans->{student_ans} = mbox(\@temp);
611 $rh_ans->{ra_student_ans} = \@array;
612
613 $rh_ans;
614
615}
616
617
618sub are_orthogonal_vecs{
619 my ($vec_ref , %opts) = @_;
620 $vec_ref->{_filter_name} = 'are_orthogonal_vecs';
621 my @vecs = ();
622 if( ref($vec_ref) eq 'AnswerHash' )
623 {
624 @vecs = @{$vec_ref->{ra_student_ans}};
625 }else{
626 @vecs = @{$vec_ref};
627 }
628
629 my $num = scalar(@vecs);
630 my $length = $vecs[0]->[1];
631
632 for( my $i=0; $i < $num ; $i ++ ) {
633 for( my $j = $i+1; $j < $num ; $j++ ) {
634 if( $vecs[$i]->scalar_product($vecs[$j]) > $main::functZeroLevelTolDefault ) {
635 if( ref( $vec_ref ) eq 'AnswerHash' ){
636 $vec_ref->{score} = 0;
637 if( $vec_ref->{help} =~ /orthogonal|orthonormal|verbose/ )
638 {
639 $vec_ref->throw_error('EVAL','You have entered vectors which are not orthogonal. ');
640 }else{
641 $vec_ref->throw_error('EVAL');
642 }
643 return $vec_ref;
644 } else {
645 return 0;
646 }
647 }
648 }
649 }
650 if( ref( $vec_ref ) eq 'AnswerHash' ){
651 $vec_ref->{score} = 1;
652 $vec_ref;
653 } else {
654 1;
655 }
656}
657
658sub is_diagonal{
659 my $matrix = shift;
660 my %options = @_;
661 my $process_ans_hash = ( ref( $matrix ) eq 'AnswerHash' ) ? 1 : 0 ;
662 my ($rh_ans);
663 if ($process_ans_hash) {
664 $rh_ans = $matrix;
665 $matrix = $rh_ans->{ra_student_ans};
666 }
667
668 return 0 unless defined($matrix);
669
670 if( ref($matrix) eq 'ARRAY' ) {
671 my @matrix = @{$matrix};
672 @matrix = @{$matrix[0]} if ref($matrix[0][0]) eq 'ARRAY';
673 if( ref($matrix[0]) ne 'ARRAY' or scalar( @matrix ) != scalar( @{$matrix[0]} ) ){
674 warn "It is impossible for a non-square matrix to be diagonal, if you are a student, please tell your professor that there is a problem.";
675 }
676
677 for( my $i = 0; $i < scalar( @matrix ) ; $i++ ) {
678 for( my $j = 0; $j < scalar( @{$matrix[0]} ); $j++ ){
679 if( $matrix[$i][$j] != 0 and $i != $j )
680 {
681 if ($process_ans_hash){
682 $rh_ans->throw_error('EVAL');
683 return $rh_ans;
684 } else {
685 return 0;
686 }
687 }
688 }
689 }
690 if ($process_ans_hash){
691 return $rh_ans;
692 } else {
693 return 1;
694 }
695 } elsif ( ref($matrix) eq 'Matrix' ) {
696 if( $matrix->[1] != $matrix->[2] ) {
697 warn "It is impossible for a non-square matrix to be diagonal, if you are a student, please tell your professor that there is a problem.";
698 if ($process_ans_hash){
699 $rh_ans->throw_error('EVAL');
700 return $rh_ans;
701 } else {
702 return 0;
703 }
704 }
705 for( my $i = 0; $i < $matrix->[1] ; $i++ ) {
706 for( my $j = 0; $j < $matrix->[2] ; $j++ ) {
707 if( $matrix->[0][$i][$j] != 0 and $i != $j ){
708 if ($process_ans_hash){
709 $rh_ans->throw_error('EVAL');
710 return $rh_ans;
711 } else {
712 return 0;
713 }
714 }
715 }
716 }
717 if ($process_ans_hash) {
718 return $rh_ans;
719 } else {
720 return 1;
721 }
722 } else {
723 warn "There is a problem with the problem, please alert your professor.";
724 if ($process_ans_hash){
725 $rh_ans->throw_error('EVAL');
726 return $rh_ans;
727 } else {
728 return 0;
729 }
730 }
731
732}
733
734
735sub are_unit_vecs{
736 my ( $vec_ref,%opts ) = @_;
737 $vec_ref->{_filter_name} = 'are_unit_vecs';
738 my @vecs = ();
739 if( ref($vec_ref) eq 'AnswerHash' )
740 {
741 @vecs = @{$vec_ref->{ra_student_ans}};
742 }else{
743 @vecs = @{$vec_ref};
744 }
745
746 my $i = 0;
747 my $num = scalar(@vecs);
748 my $length = $vecs[0]->[1];
749
750 for( ; $i < $num ; $i ++ ) {
751 if( abs(sqrt($vecs[$i]->scalar_product($vecs[$i]))- 1) > $main::functZeroLevelTolDefault )
752 {
753 if( ref( $vec_ref ) eq 'AnswerHash' ){
754 $vec_ref->{score} = 0;
755 if( $vec_ref->{help} =~ /unit|orthonormal|verbose/ )
756 {
757 $vec_ref->throw_error('EVAL','You have entered vector(s) which are not of unit length.');
758 }else{
759 $vec_ref->throw_error('EVAL');
760 }
761 return $vec_ref;
762 }else{
763 return 0;
764 }
765
766 }
767 }
768
769 if( ref( $vec_ref ) eq 'AnswerHash' ){
770 $vec_ref->{score} = 1;
771 $vec_ref;
772 }else{
773 1;
774 }
775}
776
777sub display_correct_vecs{
778 my ( $ra_vecs,%opts ) = @_;
779 my @ra_vecs = @{$ra_vecs};
780 my @temp = ();
781
782 for( my $i = 0 ; $i < scalar(@ra_vecs) ; $i++ ) {
783 push @temp, display_matrix(Matrix->new_from_col_vecs([$ra_vecs[$i]]),'left'=>'.','right'=>'.');
784 push @temp, ",";
785 }
786
787 pop @temp;
788
789 mbox(\@temp);
790
791}
792
793sub vec_solution_cmp{
794 my $correctAnswer = shift;
795 my %opt = @_;
796
797 set_default_options( \%opt,
798 'zeroLevelTol' => $main::functZeroLevelTolDefault,
799 'debug' => 0,
800 'mode' => 'basis',
801 'help' => 'none',
802 );
803
804
805## This is where the correct answer should be checked someday.
806 my $matrix = Matrix->new_from_col_vecs($correctAnswer);
807
808
809#construct the answer evaluator
810 my $answer_evaluator = new AnswerEvaluator;
811
812 $answer_evaluator->{debug} = $opt{debug};
813 $answer_evaluator->ans_hash(
814 correct_ans => display_correct_vecs($correctAnswer),
815 old_correct_ans => $correctAnswer,
816 rm_correct_ans => $matrix,
817 zeroLevelTol => $opt{zeroLevelTol},
818 debug => $opt{debug},
819 mode => $opt{mode},
820 help => $opt{help},
821 );
822
823 $answer_evaluator->install_pre_filter(\&ans_array_filter);
824 $answer_evaluator->install_pre_filter(
825 sub{
826 my ($rh_ans,@options) = @_;
827 $rh_ans->{_filter_name} = "create student answer as an array of vectors";
828 my @student_array = @{$rh_ans->{ra_student_ans}};
829 my @array = ();
830 for( my $i = 0; $i < scalar(@student_array) ; $i ++ ) {
831 push( @array, Matrix->new_from_array_ref($student_array[$i]));
832 }
833 $rh_ans->{ra_student_ans} = \@array;
834 $rh_ans;
835 }
836 );
837 #ra_student_ans is now the students answer as an array of vectors
838 # anonymous subroutine to check dimension and length of the student vectors
839 # if either is wrong, the answer is wrong.
840 $answer_evaluator->install_pre_filter(
841 sub{
842 my $rh_ans = shift;
843 $rh_ans->{_filter_name} = "check_dimension_and_length";
844 my $length = $rh_ans->{rm_correct_ans}->[1];
845 my $dim = $rh_ans->{rm_correct_ans}->[2];
239 if( $dim != scalar(@{$rh_ans->{ra_student_ans}})) 846 if( $dim != scalar(@{$rh_ans->{ra_student_ans}}))
240 { 847 {
241 848
242 $rh_ans->{score} = 0; 849 $rh_ans->{score} = 0;
243 if( $rh_ans->{help} =~ /dim|verbose/ ) 850 if( $rh_ans->{help} =~ /dim|verbose/ )
245 $rh_ans->throw_error('EVAL','You have entered the wrong number of vectors.'); 852 $rh_ans->throw_error('EVAL','You have entered the wrong number of vectors.');
246 }else{ 853 }else{
247 $rh_ans->throw_error('EVAL'); 854 $rh_ans->throw_error('EVAL');
248 } 855 }
249 } 856 }
250 for( my $i = 0; $i < scalar( @{$rh_ans->{ra_student_ans} }) ; $i++ ) 857 for( my $i = 0; $i < scalar( @{$rh_ans->{ra_student_ans} }) ; $i++ ) {
251 {
252 if( $length != $rh_ans->{ra_student_ans}->[$i]->[1]) 858 if( $length != $rh_ans->{ra_student_ans}->[$i]->[1]) {
253 {
254 $rh_ans->{score} = 0; 859 $rh_ans->{score} = 0;
255 if( $rh_ans->{help} =~ /length|verbose/ ) 860 if( $rh_ans->{help} =~ /length|verbose/ ) {
256 {
257 $rh_ans->throw_error('EVAL','You have entered vector(s) of the wrong length.'); 861 $rh_ans->throw_error('EVAL','You have entered vector(s) of the wrong length.');
258 }else{ 862 }else{
259 $rh_ans->throw_error('EVAL'); 863 $rh_ans->throw_error('EVAL');
260 } 864 }
261 } 865 }
262 } 866 }
263 $rh_ans; 867 $rh_ans;
264 } 868 }
265 ); 869 );
266 # Install prefilter for various modes 870 # Install prefilter for various modes
267 if( $mat_params{mode} ne 'basis' ) 871 if( $opt{mode} ne 'basis' ) {
268 {
269 if( $mat_params{mode} =~ /orthogonal|orthonormal/ ) 872 if( $opt{mode} =~ /orthogonal|orthonormal/ ) {
270 {
271 $answer_evaluator->install_pre_filter(\&are_orthogonal_vecs); 873 $answer_evaluator->install_pre_filter(\&are_orthogonal_vecs);
272 } 874 }
273 875
274 if( $mat_params{mode} =~ /unit|orthonormal/ ) 876 if( $opt{mode} =~ /unit|orthonormal/ ) {
275 {
276 $answer_evaluator->install_pre_filter(\&are_unit_vecs); 877 $answer_evaluator->install_pre_filter(\&are_unit_vecs);
277 878
278 } 879 }
279 }
280 $answer_evaluator->install_evaluator(\&compare_basis, %mat_params);
281 $answer_evaluator->install_post_filter(
282 sub {my $rh_ans = shift;
283 if ($rh_ans->catch_error('SYNTAX') ) {
284 $rh_ans->{ans_message} = $rh_ans->{error_message};
285 $rh_ans->clear_error('SYNTAX');
286 }
287 if ($rh_ans->catch_error('EVAL') ) {
288 $rh_ans->{ans_message} = $rh_ans->{error_message};
289 $rh_ans->clear_error('EVAL');
290 }
291 $rh_ans;
292 }
293 );
294 $answer_evaluator;
295}
296
297=head4 compare_basis
298
299 compare_basis( $ans_hash, %options);
300
301 {ra_student_ans}, # a reference to the array of students answer vectors
302 {rm_correct_ans}, # a reference to the correct answer matrix
303 %options
304 )
305
306=cut
307
308sub compare_basis {
309 my ($rh_ans, %options) = @_;
310 my @ch_coord;
311 my @vecs = @{$rh_ans->{ra_student_ans}};
312
313 # A lot of the follosing code was taken from Matrix::proj_coeff
314 # calling this method recursively would be a waste of time since
315 # the prof's matrix never changes and solve_LR is an expensive
316 # operation. This way it is only done once.
317 my $matrix = $rh_ans->{rm_correct_ans};
318 my ($dim,$x_vector, $base_matrix);
319 my $errors = undef;
320 my $lin_space_tr= ~ $matrix;
321 $matrix = $lin_space_tr * $matrix;
322 my $matrix_lr = $matrix->decompose_LR();
323
324 #finds the coefficient vectors for each of the students vectors
325 for( my $i = 0; $i < scalar(@{$rh_ans->{ra_student_ans}}) ; $i++ )
326 {
327
328 $vecs[$i] = $lin_space_tr*$vecs[$i];
329 ($dim,$x_vector, $base_matrix) = $matrix_lr->solve_LR($vecs[$i]);
330 push( @ch_coord, $x_vector );
331 $errors = "A unique adapted answer could not be determined. Possibly the parameters have coefficient zero.<br> dim = $dim base_matrix is $base_matrix\n" if $dim; # only print if the dim is not zero.
332 }
333
334 if( defined($errors))
335 {
336 $rh_ans->throw_error('EVAL', $errors) ;
337 }else{
338 my $ch_coord_mat = Matrix->new_from_col_vecs(\@ch_coord);#creates change of coordinate matrix
339 #existence of this matrix implies that
340 #the all of the students answers are a
341 #linear combo of the prof's
342 $ch_coord_mat = $ch_coord_mat->decompose_LR();
343
344 if( abs($ch_coord_mat->det_LR()) > $options{zeroLevelTol} )# if the det of the change of coordinate matrix is
345 # non-zero, this implies the existence of an inverse
346 # which implies all of the prof's vectors are a linear
347 # combo of the students vectors, showing containment
348 # both ways.
349 {
350 # I think sometimes if the students space has the same dimension as the profs space it
351 # will get projected into the profs space even if it isn't a basis for that space.
352 # this just checks that the prof's matrix times the change of coordinate matrix is actually
353 #the students matrix
354 if( abs(Matrix->new_from_col_vecs(\@{$rh_ans->{ra_student_ans}}) - ($rh_ans->{rm_correct_ans})*(Matrix->new_from_col_vecs(\@ch_coord))) < $options{zeroLevelTol} )
355 {
356 $rh_ans->{score} = 1;
357 }else{
358 $rh_ans->{score} = 0;
359 }
360 }
361 else{
362 $rh_ans->{score}=0;
363 }
364 }
365 $rh_ans;
366
367}
368
369
370=head 2 vec_list_string
371
372This is a check_syntax type method (in fact I borrowed some of that method's code) for vector input.
373The student needs to enter vectors like: [1,0,0],[1,2,3],[0,9/sqrt(10),1/sqrt(10)]
374Each entry can contain functions and operations and the usual math constants (pi and e).
375The vectors, however can not be added or multiplied or scalar multiplied by the student.
376Most errors are handled well. Any error in an entry is caught by the PG_answer_eval like it is in num_cmp or fun_cmp.
377Right now the method basically ignores every thing outside the vectors. Also, an unmatched open parenthesis is caught,
378but a unmatched close parenthesis ends the vector, and since everything outside is ignored, no error is sent (other than the
379later when the length of the vectors is checked.
380In the end, the method returns an array of Matrix objects.
381
382
383=cut
384
385sub vec_list_string{
386 my $rh_ans = shift;
387 my %options = @_;
388 my $i;
389 my $entry = "";
390 my $char;
391 my @paren_stack;
392 my $length = length($rh_ans->{student_ans});
393 my @temp;
394 my $j = 0;
395 my @answers;
396 my $paren;
397 my $display_ans;
398
399 for( $i = 0; $i < $length ; $i++ )
400 {
401 $char = substr($rh_ans->{student_ans},$i,1);
402
403 if( $char =~ /\(|\[|\{/ ){
404 push( @paren_stack, $char )
405 }
406
407 if( !( $char =~ /\(|\[|\{/ && scalar(@paren_stack) == 1 ) )
408 {
409 if( $char !~ /,|\)|\]|\}/ ){
410 $entry .= $char;
411 }else{
412 if( $char =~ /,/ || ( $char =~ /\)|\]|\}/ && scalar(@paren_stack) == 1 ) )
413 {
414 if( length($entry) == 0 ){
415 if( $char !~ /,/ ){
416 $rh_ans->throw_error('EVAL','There is a syntax error in your answer');
417 }else{
418 $rh_ans->{preview_text_string} .= ",";
419 $rh_ans->{preview_latex_string} .= ",";
420 $display_ans .= ",";
421 }
422 }else{
423
424 # This parser code was origianally taken from PGanswermacros::check_syntax
425 # but parts of it needed to be slighty modified for this context
426 my $parser = new AlgParserWithImplicitExpand;
427 my $ret = $parser -> parse($entry); #for use with loops
428
429 if ( ref($ret) ) { ## parsed successfully
430 $parser -> tostring();
431 $parser -> normalize();
432 $entry = $parser -> tostring();
433 $rh_ans->{preview_text_string} .= $entry.",";
434 $rh_ans->{preview_latex_string} .= $parser -> tolatex().",";
435
436 } else { ## error in parsing
437
438 $rh_ans->{'student_ans'} = 'syntax error:'.$display_ans. $parser->{htmlerror},
439 $rh_ans->{'ans_message'} = $display_ans.$parser -> {error_msg},
440 $rh_ans->{'preview_text_string'} = '',
441 $rh_ans->{'preview_latex_string'} = '',
442 $rh_ans->throw_error('SYNTAX', 'syntax error in answer:'.$display_ans.$parser->{htmlerror} . "$main::BR" .$parser -> {error_msg}.".$main::BR");
443 }
444
445 my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($entry);
446
447 if ($PG_eval_errors) {
448 $rh_ans->throw_error('EVAL','There is a syntax error in your answer.') ;
449 $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors);
450 last;
451 } else {
452 $entry = prfmt($inVal,$options{format});
453 $display_ans .= $entry.",";
454 push(@temp , $entry);
455 }
456
457 if( $char =~ /\)|\]|\}/ && scalar(@paren_stack) == 1)
458 {
459 pop @paren_stack;
460 chop($rh_ans->{preview_text_string});
461 chop($rh_ans->{preview_latex_string});
462 chop($display_ans);
463 $rh_ans->{preview_text_string} .= "]";
464 $rh_ans->{preview_latex_string} .= "]";
465 $display_ans .= "]";
466 if( scalar(@temp) > 0 )
467 {
468 push( @answers,Matrix->new_from_col_vecs([\@temp]));
469 while(scalar(@temp) > 0 ){
470 pop @temp;
471 }
472 }else{
473 $rh_ans->throw_error('EVAL','There is a syntax error in your answer.');
474 }
475 }
476 }
477 $entry = "";
478 }else{
479 $paren = pop @paren_stack;
480 if( scalar(@paren_stack) > 0 ){
481 #this uses ASCII to check if the parens match up
482 # in ASCII ord ( = 40 , ord ) = 41 , ord [ = 91 ,
483 # ord ] = 93 , ord { = 123 , ord } = 125
484 if( (ord($char) - ord($paren) <= 2) ){
485 $entry = $entry . $char;
486 }else{
487 $rh_ans->throw_error('EVAL','There is a syntax error in your answer');
488 }
489 }
490 }
491 }
492 }else{
493 $rh_ans->{preview_text_string} .= "[";
494 $rh_ans->{preview_latex_string} .= "[";
495 $display_ans .= "[";
496 }
497 }
498 $rh_ans->{ra_student_ans} = \@answers;
499 $rh_ans->{student_ans} = $display_ans unless $rh_ans->{error_flag};
500 $rh_ans;
501}
502
503=head5
504 This filter was created to get, format, and evaluate each entry of the ans_array and ans_array_extension
505 answer entry methods. Running this filter is necessary to get all the entries out of the answer
506 hash. Each entry is evaluated and the resulting number is put in the display for student answer
507 as a string. For evaluation purposes an array of arrays of arrays is created called ra_student_ans
508 and placed in the hash. The entries are [array_number][row_number][column_number]. The latex strings
509 for each entry are taken from the parser and put, as a matrix, into the previewer. The preview text
510 string is also created, but this display method becomes confusing when large matrices are used.
511=cut
512
513
514sub ans_array_filter{
515 my $rh_ans = shift;
516 my %options = @_;
517# assign_option_aliases( \%opt,
518# );
519 set_default_options(\%options,
520 '_filter_name' => 'ans_array_filter',
521 );
522# $rh_ans->{ans_label} =~ /ArRaY(\d+)\[\d+,\d+,\d+\]/; # CHANGE made to accomodate HTML 4.01 standards for name attribute
523 $rh_ans->{ans_label} =~ /ArRaY(\d+)\_\_\d+:\d+:\d+\_\_/;
524 my $ans_num = $1;
525 my @keys = grep /ArRaY$ans_num/, keys(%{$main::inputs_ref});
526 my $key;
527 my @array = ();
528 my ($i,$j,$k) = (0,0,0);
529
530 #the keys aren't in order, so their info has to be put into the array before doing anything with it
531 foreach $key (@keys){
532# $key =~ /ArRaY\d+\[(\d+),(\d+),(\d+)\]/;
533# ($i,$j,$k) = ($1,$2,$3);
534# $array[$i][$j][$k] = ${$main::inputs_ref}{'ArRaY'.$ans_num.'['.$i.','.$j.','.$k.']'};
535 $key =~ /ArRaY\d+\_\_(\d+):(\d+):(\d+)\_\_/;
536 ($i,$j,$k) = ($1,$2,$3);
537 $array[$i][$j][$k] = ${$main::inputs_ref}{'ArRaY'.$ans_num.'__'.$i.':'.$j.':'.$k.'__'};
538
539 }
540 $rh_ans->{debug_student_answer }= \@array;
541 my $display_ans = "";
542
543 for( $i=0; $i < scalar(@array) ; $i ++ )
544 {
545 $display_ans .= " [";
546 $rh_ans->{preview_text_string} .= ' [';
547 $rh_ans->{preview_latex_string} .= '\begin{pmatrix} ';
548 for( $j = 0; $j < scalar( @{$array[$i]} ) ; $j++ )
549 {
550 $display_ans .= " [";
551 $rh_ans->{preview_text_string} .= ' [';
552 for( $k = 0; $k < scalar( @{$array[$i][$j]} ) ; $k ++ ){
553 my $entry = $array[$i][$j][$k];
554 $entry = math_constants($entry);
555 # This parser code was origianally taken from PGanswermacros::check_syntax
556 # but parts of it needed to be slighty modified for this context
557 my $parser = new AlgParserWithImplicitExpand;
558 my $ret = $parser -> parse($entry); #for use with loops
559
560 if ( ref($ret) ) { ## parsed successfully
561 $parser -> tostring();
562 $parser -> normalize();
563 $entry = $parser -> tostring();
564 $rh_ans->{preview_text_string} .= $entry.",";
565 $rh_ans->{preview_latex_string} .= $parser -> tolatex() . '& ';
566
567 } else { ## error in parsing
568 $rh_ans->{'student_ans'} = 'syntax error:'.$display_ans. $parser->{htmlerror},
569 $rh_ans->{'ans_message'} = $display_ans.$parser -> {error_msg},
570 $rh_ans->{'preview_text_string'} = '',
571 $rh_ans->throw_error('SYNTAX', 'syntax error in answer:'.$display_ans.$parser->{htmlerror} . "$main::BR" .$parser -> {error_msg}.".$main::BR");
572 }
573
574 my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($entry);
575 if ($PG_eval_errors) {
576 $rh_ans->throw_error('EVAL','There is a syntax error in your answer.') ;
577 $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors);
578 last;
579 } else {
580 $entry = prfmt($inVal,$options{format});
581 $display_ans .= $entry.",";
582 $array[$i][$j][$k] = $entry;
583 }
584 }
585 chop($rh_ans->{preview_text_string});
586 chop($display_ans);
587 $rh_ans->{preview_text_string} .= '] ,';
588 $rh_ans->{preview_latex_string} .= '\\\\';
589 $display_ans .= '] ,';
590
591 }
592 chop($rh_ans->{preview_text_string});
593 chop($display_ans);
594 $rh_ans->{preview_text_string} .= '] ,';
595 $rh_ans->{preview_latex_string} .= '\end{pmatrix}'.' , ';
596 $display_ans .= '] ,';
597 }
598 chop($rh_ans->{preview_text_string});
599 chop($rh_ans->{preview_latex_string});
600 chop($rh_ans->{preview_latex_string});
601 chop($rh_ans->{preview_latex_string});
602 chop($display_ans);
603
604 my @temp = ();
605 for( $i = 0 ; $i < scalar( @array ); $i++ ){
606 push @temp , display_matrix($array[$i], 'left'=>'.', 'right'=>'.');
607 push @temp , "," unless $i == scalar(@array) - 1;
608 }
609 $rh_ans->{student_ans} = mbox(\@temp);
610 $rh_ans->{ra_student_ans} = \@array;
611
612 $rh_ans;
613
614}
615
616
617sub are_orthogonal_vecs{
618 my ($vec_ref , %opts) = @_;
619 $vec_ref->{_filter_name} = 'are_orthogonal_vecs';
620 my @vecs = ();
621 if( ref($vec_ref) eq 'AnswerHash' )
622 {
623 @vecs = @{$vec_ref->{ra_student_ans}};
624 }else{
625 @vecs = @{$vec_ref};
626 }
627 my ($i,$j) = (0,0);
628
629 my $num = scalar(@vecs);
630 my $length = $vecs[0]->[1];
631
632 for( ; $i < $num ; $i ++ )
633 {
634 for( $j = $i+1; $j < $num ; $j++ )
635 {
636 if( $vecs[$i]->scalar_product($vecs[$j]) > $main::functZeroLevelTolDefault )
637 {
638 if( ref( $vec_ref ) eq 'AnswerHash' ){
639 $vec_ref->{score} = 0;
640 if( $vec_ref->{help} =~ /orthogonal|orthonormal|verbose/ )
641 {
642 $vec_ref->throw_error('EVAL','You have entered vectors which are not orthogonal. ');
643 }else{
644 $vec_ref->throw_error('EVAL');
645 }
646 return $vec_ref;
647 }else{
648 return 0;
649 }
650 }
651 }
652 }
653 if( ref( $vec_ref ) eq 'AnswerHash' ){
654 $vec_ref->{score} = 1;
655 $vec_ref;
656 }else{
657 1;
658 }
659}
660
661sub is_diagonal{
662 my $matrix = shift;
663 my %options = @_;
664 my $process_ans_hash = ( ref( $matrix ) eq 'AnswerHash' ) ? 1 : 0 ;
665 my ($rh_ans);
666 if ($process_ans_hash) {
667 $rh_ans = $matrix;
668 $matrix = $rh_ans->{ra_student_ans};
669 }
670
671 return 0 unless defined($matrix);
672
673 if( ref($matrix) eq 'ARRAY' ){
674 my @matrix = @{$matrix};
675 @matrix = @{$matrix[0]} if ref($matrix[0][0]) eq 'ARRAY';
676 if( ref($matrix[0]) ne 'ARRAY' or scalar( @matrix ) != scalar( @{$matrix[0]} ) ){
677 warn "It is impossible for a non-square matrix to be diagonal, if you are a student, please tell your professor that there is a problem.";
678 }
679
680 for( my $i = 0; $i < scalar( @matrix ) ; $i++ ){
681 for( my $j = 0; $j < scalar( @{$matrix[0]} ); $j++ ){
682 if( $matrix[$i][$j] != 0 and $i != $j )
683 {
684 if ($process_ans_hash){
685 $rh_ans->throw_error('EVAL');
686 return $rh_ans;
687 } else {
688 return 0;
689 }
690 }
691 }
692 }
693 if ($process_ans_hash){
694 return $rh_ans;
695 } else {
696 return 1;
697 }
698 }elsif( ref($matrix) eq 'Matrix' ){
699 if( $matrix->[1] != $matrix->[2] ){
700 warn "It is impossible for a non-square matrix to be diagonal, if you are a student, please tell your professor that there is a problem.";
701 if ($process_ans_hash){
702 $rh_ans->throw_error('EVAL');
703 return $rh_ans;
704 } else {
705 return 0;
706 }
707 }
708 for( my $i = 0; $i < $matrix->[1] ; $i++ ){
709 for( my $j = 0; $j < $matrix->[2] ; $j++ ){
710 if( $matrix->[0][$i][$j] != 0 and $i != $j ){
711 if ($process_ans_hash){
712 $rh_ans->throw_error('EVAL');
713 return $rh_ans;
714 } else {
715 return 0;
716 }
717 }
718 }
719 }
720 if ($process_ans_hash){
721 return $rh_ans;
722 } else {
723 return 1;
724 }
725 }else{
726 warn "There is a problem with the problem, please alert your professor.";
727 if ($process_ans_hash){
728 $rh_ans->throw_error('EVAL');
729 return $rh_ans;
730 } else {
731 return 0;
732 }
733 }
734
735}
736
737
738sub are_unit_vecs{
739 my ( $vec_ref,%opts ) = @_;
740 $vec_ref->{_filter_name} = 'are_unit_vecs';
741 my @vecs = ();
742 if( ref($vec_ref) eq 'AnswerHash' )
743 {
744 @vecs = @{$vec_ref->{ra_student_ans}};
745 }else{
746 @vecs = @{$vec_ref};
747 }
748
749 my $i = 0;
750 my $num = scalar(@vecs);
751 my $length = $vecs[0]->[1];
752
753 for( ; $i < $num ; $i ++ )
754 {
755 if( abs(sqrt($vecs[$i]->scalar_product($vecs[$i]))- 1) > $main::functZeroLevelTolDefault )
756 {
757 if( ref( $vec_ref ) eq 'AnswerHash' ){
758 $vec_ref->{score} = 0;
759 if( $vec_ref->{help} =~ /unit|orthonormal|verbose/ )
760 {
761 $vec_ref->throw_error('EVAL','You have entered vector(s) which are not of unit length.');
762 }else{
763 $vec_ref->throw_error('EVAL');
764 }
765 return $vec_ref;
766 }else{
767 return 0;
768 }
769
770 }
771 }
772
773 if( ref( $vec_ref ) eq 'AnswerHash' ){
774 $vec_ref->{score} = 1;
775 $vec_ref;
776 }else{
777 1;
778 }
779}
780
781sub display_correct_vecs{
782 my ( $ra_vecs,%opts ) = @_;
783 my @ra_vecs = @{$ra_vecs};
784 my @temp = ();
785
786 for( my $i = 0 ; $i < scalar(@ra_vecs) ; $i++ ){
787 push @temp, display_matrix(Matrix->new_from_col_vecs([$ra_vecs[$i]]),'left'=>'.','right'=>'.');
788 push @temp, ",";
789 }
790
791 pop @temp;
792
793 mbox(\@temp);
794
795}
796
797sub vec_solution_cmp{
798 my $correctAnswer = shift;
799 my %opt = @_;
800
801 set_default_options( \%opt,
802 'zeroLevelTol' => $main::functZeroLevelTolDefault,
803 'debug' => 0,
804 'mode' => 'basis',
805 'help' => 'none',
806 );
807
808 $opt{debug} = 0 unless defined($opt{debug});
809
810## This is where the correct answer should be checked someday.
811 my $matrix = Matrix->new_from_col_vecs($correctAnswer);
812
813
814#construct the answer evaluator
815 my $answer_evaluator = new AnswerEvaluator;
816
817 $answer_evaluator->{debug} = $opt{debug};
818 $answer_evaluator->ans_hash( correct_ans => display_correct_vecs($correctAnswer),
819 old_correct_ans => $correctAnswer,
820 rm_correct_ans => $matrix,
821 zeroLevelTol => $opt{zeroLevelTol},
822 debug => $opt{debug},
823 mode => $opt{mode},
824 help => $opt{help},
825 );
826
827 $answer_evaluator->install_pre_filter(\&ans_array_filter);
828 $answer_evaluator->install_pre_filter(sub{
829 my ($rh_ans,@options) = @_;
830 my @student_array = @{$rh_ans->{ra_student_ans}};
831 my @array = ();
832 for( my $i = 0; $i < scalar(@student_array) ; $i ++ )
833 {
834 push( @array, Matrix->new_from_array_ref($student_array[$i]));
835 }
836 $rh_ans->{ra_student_ans} = \@array;
837 $rh_ans;
838 });#ra_student_ans is now the students answer as an array of vectors
839 # anonymous subroutine to check dimension and length of the student vectors
840 # if either is wrong, the answer is wrong.
841 $answer_evaluator->install_pre_filter(sub{
842 my $rh_ans = shift;
843 my $length = $rh_ans->{rm_correct_ans}->[1];
844 my $dim = $rh_ans->{rm_correct_ans}->[2];
845 if( $dim != scalar(@{$rh_ans->{ra_student_ans}}))
846 {
847
848 $rh_ans->{score} = 0;
849 if( $rh_ans->{help} =~ /dim|verbose/ )
850 {
851 $rh_ans->throw_error('EVAL','You have entered the wrong number of vectors.');
852 }else{
853 $rh_ans->throw_error('EVAL');
854 }
855 }
856 for( my $i = 0; $i < scalar( @{$rh_ans->{ra_student_ans} }) ; $i++ )
857 {
858 if( $length != $rh_ans->{ra_student_ans}->[$i]->[1])
859 {
860 $rh_ans->{score} = 0;
861 if( $rh_ans->{help} =~ /length|verbose/ )
862 {
863 $rh_ans->throw_error('EVAL','You have entered vector(s) of the wrong length.');
864 }else{
865 $rh_ans->throw_error('EVAL');
866 }
867 }
868 }
869 $rh_ans;
870 });
871 # Install prefilter for various modes
872 if( $opt{mode} ne 'basis' )
873 {
874 if( $opt{mode} =~ /orthogonal|orthonormal/ )
875 {
876 $answer_evaluator->install_pre_filter(\&are_orthogonal_vecs);
877 }
878
879 if( $opt{mode} =~ /unit|orthonormal/ )
880 {
881 $answer_evaluator->install_pre_filter(\&are_unit_vecs);
882
883 }
884 }
885 880 }
881
886 $answer_evaluator->install_evaluator(\&compare_vec_solution, %opt); 882 $answer_evaluator->install_evaluator(\&compare_vec_solution, %opt);
887 883
888 $answer_evaluator->install_post_filter( 884 $answer_evaluator->install_post_filter(
889 sub {my $rh_ans = shift; 885 sub {my $rh_ans = shift;
890 if ($rh_ans->catch_error('SYNTAX') ) { 886 if ($rh_ans->catch_error('SYNTAX') ) {
891 $rh_ans->{ans_message} = $rh_ans->{error_message}; 887 $rh_ans->{ans_message} = $rh_ans->{error_message};
892 $rh_ans->clear_error('SYNTAX'); 888 $rh_ans->clear_error('SYNTAX');
893 } 889 }
894 if ($rh_ans->catch_error('EVAL') ) { 890 if ($rh_ans->catch_error('EVAL') ) {
895 $rh_ans->{ans_message} = $rh_ans->{error_message}; 891 $rh_ans->{ans_message} = $rh_ans->{error_message};
896 $rh_ans->clear_error('EVAL'); 892 $rh_ans->clear_error('EVAL');
897 } 893 }
898 $rh_ans; 894 $rh_ans;
899 } 895 }
900 ); 896 );
901 $answer_evaluator; 897 $answer_evaluator;
902 898
903} 899}
904 900
905 901
906sub compare_vec_solution { 902sub compare_vec_solution {
907 my ( $rh_ans, %options ) = @_ ; 903 my ( $rh_ans, %options ) = @_ ;
904 $rh_ans->{_filter_name} = "compare_vec_solution";
908 my @space = @{$rh_ans->{ra_student_ans}}; 905 my @space = @{$rh_ans->{ra_student_ans}};
909 my $solution = shift @space; 906 my $solution = shift @space;
910 907
911 # A lot of the follosing code was taken from Matrix::proj_coeff 908 # A lot of the following code was taken from Matrix::proj_coeff
912 # calling this method recursively would be a waste of time since 909 # calling this method recursively would be a waste of time since
913 # the prof's matrix never changes and solve_LR is an expensive 910 # the prof's matrix never changes and solve_LR is an expensive
914 # operation. This way it is only done once. 911 # operation. This way it is only done once.
915 my $matrix = $rh_ans->{rm_correct_ans}; 912 my $matrix = $rh_ans->{rm_correct_ans};
916 my ($dim,$x_vector, $base_matrix); 913 my ($dim,$x_vector, $base_matrix);
917 my $errors = undef; 914 my $errors = undef;
918 my $lin_space_tr= ~ $matrix; 915 my $lin_space_tr= ~ $matrix;
919 $matrix = $lin_space_tr * $matrix; 916 $matrix = $lin_space_tr * $matrix;
920 my $matrix_lr = $matrix->decompose_LR(); 917 my $matrix_lr = $matrix->decompose_LR();
921 918
922 #this section determines whether or not the first vector, a solution to 919 #this section determines whether or not the first vector, a solution to
923 #the system, is a linear combination of the prof's vectors in which there 920 #the system, is a linear combination of the prof's vectors in which there
924 #is a nonzero coefficient on the first term, the prof's solution to the system 921 #is a nonzero coefficient on the first term, the prof's solution to the system
925 $solution = $lin_space_tr*$solution; 922 $solution = $lin_space_tr*$solution;
926 ($dim,$x_vector, $base_matrix) = $matrix_lr->solve_LR($solution); 923 ($dim,$x_vector, $base_matrix) = $matrix_lr->solve_LR($solution);
924 #$rh_ans->{debug_compare_vec_solution} = $x_vector->element(1,1);
927 if( $dim ){ 925 if( $dim ){
928 $rh_ans->throw_error('EVAL', "A unique adapted answer could not be determined. Possibly the parameters have coefficient zero.<br> dim = $dim base_matrix is $base_matrix\n" ); # only print if the dim is not zero. 926 $rh_ans->throw_error('EVAL', "A unique adapted answer could not be determined. Possibly the parameters have coefficient zero.<br> dim = $dim base_matrix is $base_matrix\n" ); # only print if the dim is not zero.
929 $rh_ans->{score} = 0; 927 $rh_ans->{score} = 0;
930 $rh_ans; 928 $rh_ans;
931 }elsif( abs($x_vector->[0][0][0]) <= $options{zeroLevelTol} ) 929 } elsif( abs($x_vector->element(1,1) -1) >= $options{zeroLevelTol} ) {
932 { 930 # changes by MEG 6/24/05
931 # the student answer needs to be a linear combination of the instructors vectors
932 # and the coefficient of the first vector needs to be 1 (it is NOT enough that it be non-zero).
933 # if this is not the case, then the answer is wrong.
934 # replaced $x_vector->[0][0][0] by $x_vector->element(1,1) since this doesn't depend on the internal structure of the matrix object.
935
933 $rh_ans->{score} = 0; 936 $rh_ans->{score} = 0;
934 $rh_ans; 937 $rh_ans;
935 }else{ 938 } else {
936 $rh_ans->{score} = 1; 939 $rh_ans->{score} = 1;
937 my @correct_space = @{$rh_ans->{old_correct_ans}}; 940 my @correct_space = @{$rh_ans->{old_correct_ans}};
938 shift @correct_space; 941 shift @correct_space;
939 $rh_ans->{rm_correct_ans} = Matrix->new_from_col_vecs(\@correct_space); 942 $rh_ans->{rm_correct_ans} = Matrix->new_from_col_vecs(\@correct_space);
940 $rh_ans->{ra_student_ans} = \@space; 943 $rh_ans->{ra_student_ans} = \@space;
941 return compare_basis( $rh_ans, %options ); 944 return compare_basis( $rh_ans, %options );
942 } 945 }
943} 946}
944 947
9451; 9481;

Legend:
Removed from v.1896  
changed lines
  Added in v.3314

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9