Parent Directory
|
Revision Log
The file PGcomplexmacros.pl is partially in sync with the version in courseScripts. I need advice on the latest versions of some of the subroutines from Liam. --Mike
1 #!/usr/local/bin/webwork-perl 2 # This file is PGcomplexmacros.pl 3 # This includes the subroutines for the ANS macros, that 4 # is, macros allowing a more flexible answer checking 5 #################################################################### 6 # Copyright @ 1995-2002 The WeBWorK Team 7 # All Rights Reserved 8 #################################################################### 9 #$Id$ 10 11 12 =head1 NAME 13 14 Macros for complex numbers for the PG language 15 16 =head1 SYNPOSIS 17 18 19 20 =head1 DESCRIPTION 21 22 =cut 23 24 25 BEGIN{ 26 be_strict(); 27 28 } 29 30 31 32 sub _PGcomplexmacros_init { 33 } 34 # export functions from Complex1. 35 36 foreach my $f (@Complex1::EXPORT) { 37 # #PG_restricted_eval("\*$f = \*Complex1::$f"); # this is too clever -- 38 # the original subroutines are destroyed 39 next if $f eq 'sqrt'; #exporting the square root caused conflicts with the standard version 40 # You can still use Complex1::sqrt to take square root of complex numbers 41 next if $f eq 'log'; #exporting loq caused conflicts with the standard version 42 # You can still use Complex1::log to take square root of complex numbers 43 44 my $string = qq{ 45 sub main::$f { 46 &Complex1::$f; 47 } 48 }; 49 PG_restricted_eval($string); 50 } 51 52 53 # You need to add 54 # sub i(); # to your problem or else to dangerousMacros.pl 55 # in order to use expressions such as 1 +3*i; 56 # Without this prototype you would have to write 1+3*i(); 57 # The prototype has to be defined at compile time, but dangerousMacros.pl is complied first. 58 #Complex1::display_format('cartesian'); 59 60 # number format used frequently in strict prefilters 61 my $number = '([+-]?)(?=\d|\.\d)\d*(\.\d*)?(E([+-]?\d+))?'; 62 63 64 sub polar{ 65 my $z = shift; 66 my %options = @_; 67 my $r = rho($z); 68 my $theta = $z->theta; 69 my $r_format = ':%0.3f'; 70 my $theta_format = ':%0.3f'; 71 $r_format=":" . $options{r_format} if defined($options{r_format}); 72 $theta_format = ":" . $options{theta_format} if defined($options{theta_format}); 73 "{$r$r_format} e^{i {$theta$theta_format}}"; 74 75 } 76 77 =head4 cplx_cmp 78 79 This subroutine compares complex numbers. 80 Available prefilters include: 81 each of these are called by cplx_cmp( answer, mode => '(prefilter name)' ) 82 'std' The standard comparison method for complex numbers. This option it the default 83 and works with any combination of cartesian numbers, polar numbers, and 84 functions. The default display method is cartesian, for all methods, but if 85 the student answer is polar, even in part, then their answer will be displayed 86 that way. 87 'strict_polar' This is still under developement. The idea is to check to make sure that there 88 only a single term in front of the e and after it... but the method does not 89 check to make sure that the i is in the exponent, nor does it handle cases 90 where the polar has e** coefficients. 91 'strict_num_cartesian' This prefilter allows only complex numbers of the form "a+bi" where a and b 92 are strictly numbers. 93 'strict_num_polar' This prefilter allows only complex numbers of the form "ae^(bi)" where a and b 94 are strictly numbers. 95 'strict' This is a combination of strict_num_cartesian and strict_num_polar, so it 96 allows complex numbers of either the form "a+bi" or "ae^(bi)" where a and b 97 are strictly numbers. 98 99 100 =cut 101 102 sub cplx_cmp { 103 my $correctAnswer = shift; 104 my %cplx_params = @_; 105 my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug ); 106 assign_option_aliases( \%cplx_params, 107 'reltol' => 'relTol', 108 ); 109 set_default_options(\%cplx_params, 110 'tolType' => (defined($cplx_params{tol}) ) ? 'absolute' : 'relative', 111 # default mode should be relative, to obtain this tol must not be defined 112 'tolerance' => $main::numAbsTolDefault, 113 'relTol' => $main::numRelPercentTolDefault, 114 'zeroLevel' => $main::numZeroLevelDefault, 115 'zeroLevelTol' => $main::numZeroLevelTolDefault, 116 'format' => $main::numFormatDefault, 117 'debug' => 0, 118 'mode' => 'std', 119 120 ); 121 $correctAnswer = cplx($correctAnswer,0) unless ref($correctAnswer) =~/Complex/; 122 my $format = $cplx_params{'format'}; 123 my $mode = $cplx_params{'mode'}; 124 125 if( $cplx_params{tolType} eq 'relative' ) { 126 $cplx_params{'tolerance'} = .01*$cplx_params{'tolerance'}; 127 } 128 129 my $formattedCorrectAnswer; 130 my $correct_num_answer; 131 my $corrAnswerIsString = 0; 132 133 134 if (defined($cplx_params{strings}) && $cplx_params{strings}) { 135 my $legalString = ''; 136 my @legalStrings = @{$cplx_params{strings}}; 137 $correct_num_answer = $correctAnswer; 138 $formattedCorrectAnswer = $correctAnswer; 139 foreach $legalString (@legalStrings) { 140 if ( uc($correctAnswer) eq uc($legalString) ) { 141 $corrAnswerIsString = 1; 142 143 last; 144 } 145 } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric 146 } else { 147 $correct_num_answer = $correctAnswer; 148 $formattedCorrectAnswer = prfmt( $correctAnswer, $cplx_params{'format'} ); 149 } 150 $correct_num_answer = math_constants($correct_num_answer); 151 my $PGanswerMessage = ''; 152 153 my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report); 154 155 if (defined($correct_num_answer) && $correct_num_answer =~ /\S/ && $corrAnswerIsString == 0 ) { 156 ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correct_num_answer); 157 } else { # case of a string answer 158 $PG_eval_errors = ' '; 159 $correctVal = $correctAnswer; 160 } 161 ## This throws an error all the time, and I don't know what it's for 162 #if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal)) && $corrAnswerIsString == 0)) { 163 ##error message from eval or above 164 #warn "Error in 'correct' answer: $PG_eval_errors<br> 165 # The answer $correctAnswer evaluates to $correctVal, 166 # which cannot be interpreted as a number. "; 167 168 #} 169 ######################################################################## 170 $correctVal = $correct_num_answer;#it took me two and a half hours to figure out that correctVal wasn't 171 #getting the number properly 172 #construct the answer evaluator 173 my $answer_evaluator = new AnswerEvaluator; 174 175 176 $answer_evaluator->{debug} = $cplx_params{debug}; 177 $answer_evaluator->ans_hash( 178 correct_ans => $correctVal, 179 type => "${mode}_number", 180 tolerance => $cplx_params{tolerance}, 181 tolType => 'absolute', # $cplx_params{tolType}, 182 original_correct_ans => $formattedCorrectAnswer, 183 answerIsString => $corrAnswerIsString, 184 answer_form => 'cartesian', 185 ); 186 my ($in, $formattedSubmittedAnswer); 187 $answer_evaluator->install_pre_filter(sub {my $rh_ans = shift; 188 $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; $rh_ans;} 189 ); 190 if (defined($cplx_params{strings}) && $cplx_params{strings}) { 191 $answer_evaluator->install_pre_filter(\&check_strings, %cplx_params); 192 } 193 194 $answer_evaluator->install_pre_filter(\&check_syntax); 195 196 $answer_evaluator->install_pre_filter(\&math_constants); 197 $answer_evaluator->install_pre_filter(\&cplx_constants); 198 $answer_evaluator->install_pre_filter(\&check_for_polar); 199 if ($mode eq 'std') { 200 # do nothing 201 } elsif ($mode eq 'strict_polar') { 202 $answer_evaluator->install_pre_filter(\&is_a_polar); 203 } elsif ($mode eq 'strict_num_cartesian') { 204 $answer_evaluator->install_pre_filter(\&is_a_numeric_cartesian); 205 } elsif ($mode eq 'strict_num_polar') { 206 $answer_evaluator->install_pre_filter(\&is_a_numeric_polar); 207 } elsif ($mode eq 'strict') { 208 $answer_evaluator->install_pre_filter(\&is_a_numeric_complex); 209 } elsif ($mode eq 'arith') { 210 $answer_evaluator->install_pre_filter(\&is_an_arithmetic_expression); 211 } elsif ($mode eq 'frac') { 212 $answer_evaluator->install_pre_filter(\&is_a_fraction); 213 214 } else { 215 $PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.'; 216 $formattedSubmittedAnswer = $in; 217 } 218 if ($corrAnswerIsString == 0 ){ # avoiding running compare_numbers when correct answer is a string. 219 $answer_evaluator->install_evaluator(\&compare_cplx, %cplx_params); 220 } 221 222 223 ############################################################################### 224 # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's 225 # can be displayed in the answer message. This may still cause a few anomolies when strings are used 226 # 227 ############################################################################### 228 229 $answer_evaluator->install_post_filter(\&fix_answers_for_display); 230 $answer_evaluator->install_post_filter(\&fix_for_polar_display); 231 232 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; 233 return $rh_ans unless $rh_ans->catch_error('EVAL'); 234 $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message}; 235 $rh_ans->clear_error('EVAL'); } ); 236 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } ); 237 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('POLAR'); } ); 238 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('CARTESIAN'); } ); 239 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('COMPLEX'); } ); 240 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } ); 241 $answer_evaluator; 242 } 243 244 245 246 247 248 sub cplx_cmp4{ 249 my $number_of_answers = shift; 250 my $count = 0; my @answers; 251 while( $count < $number_of_answers ) 252 { 253 $answers[$count] = shift; 254 $count++; 255 } 256 my %cplx_params = @_; 257 my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug ); 258 my @correctVal; 259 my $formattedCorrectAnswer; 260 my @correct_num_answer; 261 my ($PG_eval_errors,$PG_full_error_report); 262 assign_option_aliases( \%cplx_params, 263 'reltol' => 'relTol', 264 ); 265 set_default_options(\%cplx_params, 266 'tolType' => (defined($cplx_params{tol}) ) ? 'absolute' : 'relative', 267 # default mode should be relative, to obtain this tol must not be defined 268 'tolerance' => $main::numAbsTolDefault, 269 'relTol' => $main::numRelPercentTolDefault, 270 'zeroLevel' => $main::numZeroLevelDefault, 271 'zeroLevelTol' => $main::numZeroLevelTolDefault, 272 'format' => $main::numFormatDefault, 273 'debug' => 0, 274 'mode' => 'std', 275 276 ); 277 foreach( @answers ) 278 { 279 $_ = cplx( $_, 0 ) unless ref($_) =~/Complex/; 280 } 281 my $format = $cplx_params{'format'}; 282 my $mode = $cplx_params{'mode'}; 283 284 if( $cplx_params{tolType} eq 'relative' ) { 285 $cplx_params{'tolerance'} = .01*$cplx_params{'tolerance'}; 286 } 287 #my $correctAnswer = $answers[0]; 288 289 my $corrAnswerIsString = 0; 290 291 for( my $k = 0; $k < $number_of_answers; $k++ ){ 292 if (defined($cplx_params{strings}) && $cplx_params{strings}) { 293 my $legalString = ''; 294 my @legalStrings = @{$cplx_params{strings}}; 295 $correct_num_answer[$k] = $answers[$k]; 296 $formattedCorrectAnswer .= $answers[$k] . ","; 297 foreach $legalString (@legalStrings) { 298 if ( uc($answers[$k]) eq uc($legalString) ) { 299 $corrAnswerIsString = 1; 300 301 last; 302 } 303 } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric 304 } else { 305 $correct_num_answer[$k] = $answers[$k]; 306 $formattedCorrectAnswer .= prfmt( $answers[$k], $cplx_params{'format'} ). ", "; 307 } 308 $correct_num_answer[$k] = math_constants($correct_num_answer[$k]); 309 my $PGanswerMessage = ''; 310 311 312 if (defined($correct_num_answer[$k]) && $correct_num_answer[$k] =~ /\S/ && $corrAnswerIsString == 0 ) { 313 ($correctVal[$k], $PG_eval_errors,$PG_full_error_report) = 314 PG_answer_eval($correct_num_answer[$k]); 315 } else { # case of a string answer 316 $PG_eval_errors = ' '; 317 $correctVal[$k] = $answers[$k]; 318 } 319 320 if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal[$k])) && $corrAnswerIsString == 0)) { 321 ##error message from eval or above 322 warn "Error in 'correct' answer: $PG_eval_errors<br> 323 The answer $answers[$k] evaluates to $correctVal[$k], 324 which cannot be interpreted as a number. "; 325 326 } 327 ######################################################################## 328 $correctVal[$k] = $correct_num_answer[$k];#it took me two and a half hours to figure out that correctVal wasn't 329 } 330 #getting the number properly 331 #construct the answer evaluator 332 333 my $answer_evaluator = new AnswerEvaluator; 334 335 336 $answer_evaluator->{debug} = $cplx_params{debug}; 337 $answer_evaluator->ans_hash( 338 correct_ans => [@correctVal], 339 type => "${mode}_number", 340 tolerance => $cplx_params{tolerance}, 341 tolType => 'absolute', # $cplx_params{tolType}, 342 original_correct_ans => $formattedCorrectAnswer, 343 answerIsString => $corrAnswerIsString, 344 answer_form => 'cartesian', 345 ); 346 my ($in, $formattedSubmittedAnswer); 347 $answer_evaluator->install_pre_filter(sub {my $rh_ans = shift; 348 $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; $rh_ans;} 349 ); 350 if (defined($cplx_params{strings}) && $cplx_params{strings}) { 351 $answer_evaluator->install_pre_filter(\&check_strings, %cplx_params); 352 } 353 #$answer_evaluator->install_pre_filter(\&check_syntax); 354 355 $answer_evaluator->install_pre_filter(\&math_constants); 356 $answer_evaluator->install_pre_filter(\&cplx_constants); 357 $answer_evaluator->install_pre_filter(\&check_for_polar); 358 if ($mode eq 'std') { 359 # do nothing 360 } elsif ($mode eq 'strict_polar') { 361 $answer_evaluator->install_pre_filter(\&is_a_polar); 362 } elsif ($mode eq 'strict_num_cartesian') { 363 $answer_evaluator->install_pre_filter(\&is_a_numeric_cartesian); 364 } elsif ($mode eq 'strict_num_polar') { 365 $answer_evaluator->install_pre_filter(\&is_a_numeric_polar); 366 } elsif ($mode eq 'strict') { 367 $answer_evaluator->install_pre_filter(\&is_a_numeric_complex); 368 } elsif ($mode eq 'arith') { 369 $answer_evaluator->install_pre_filter(\&is_an_arithmetic_expression); 370 } elsif ($mode eq 'frac') { 371 $answer_evaluator->install_pre_filter(\&is_a_fraction); 372 373 } else { 374 #$PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.'; 375 $formattedSubmittedAnswer = $in; 376 } 377 if ($corrAnswerIsString == 0 ){ # avoiding running compare_numbers when correct answer is a string. 378 $answer_evaluator->install_evaluator(\&compare_cplx4, %cplx_params); 379 } 380 381 382 ############################################################################### 383 # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's 384 # can be displayed in the answer message. This may still cause a few anomolies when strings are used 385 # 386 ############################################################################### 387 #$answer_evaluator->install_post_filter( sub{my $rh_ans = shift; $rh_ans->{student_ans} = $rh_ans->{original_student_ans};$rh_ans;}); 388 $answer_evaluator->install_post_filter(\&fix_answers_for_display); 389 $answer_evaluator->install_post_filter(\&fix_for_polar_display); 390 391 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; 392 return $rh_ans unless $rh_ans->catch_error('EVAL'); 393 $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message}; 394 $rh_ans->clear_error('EVAL'); } ); 395 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } ); 396 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('POLAR'); } ); 397 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('CARTESIAN'); } ); 398 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('COMPLEX'); } ); 399 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } ); 400 $answer_evaluator; 401 } 402 403 404 # compares two complex numbers by comparing their real and imaginary parts 405 sub compare_cplx4 { 406 my ($rh_ans, %options) = @_; 407 my @student_answers = split/,/,$rh_ans->{student_ans}; 408 my @correct_answers = @{$rh_ans->{correct_ans}}; 409 my $one_correct = 1/@correct_answers; 410 foreach( @student_answers ) 411 { 412 $rh_ans->{student_ans} = $_; 413 $rh_ans = &check_syntax( $rh_ans ); 414 my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans}); 415 416 if ($PG_eval_errors) { 417 $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); 418 $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); 419 # return $rh_ans; 420 } else { 421 $rh_ans->{student_ans} = prfmt($inVal,$options{format}); 422 } 423 424 $inVal = cplx($inVal,0) unless ref($inVal) =~/Complex/; 425 my $permitted_error_Re; 426 my $permitted_error_Im; 427 foreach( @correct_answers ){ 428 if ($rh_ans->{tolType} eq 'absolute') { 429 $permitted_error_Re = $rh_ans->{tolerance}; 430 $permitted_error_Im = $rh_ans->{tolerance}; 431 } 432 elsif ( abs($_) <= $options{zeroLevel}) { 433 $permitted_error_Re = $options{zeroLevelTol}; ## want $tol to be non zero 434 $permitted_error_Im = $options{zeroLevelTol}; ## want $tol to be non zero 435 } 436 else { 437 $permitted_error_Re = abs($rh_ans->{tolerance}*$_->Complex::Re); 438 $permitted_error_Im = abs($rh_ans->{tolerance}*$_->Complex::Im); 439 440 } 441 442 if ( abs( $_->Complex::Re - $inVal->Complex::Re) <=$permitted_error_Re && 443 abs($_->Complex::Im - $inVal->Complex::Im )<= $permitted_error_Im ){ 444 $rh_ans->{score} += $one_correct ; 445 } 446 447 if( $rh_ans->{score} == 1 ){ return $rh_ans; } 448 } 449 450 } 451 $rh_ans; 452 453 } 454 455 456 457 sub mult_cmp{ 458 my $number_of_answers = shift; 459 my @answers; 460 for( my $count = 0; $count < $number_of_answers; $count++ ) 461 { 462 $answers[$count] = shift; 463 } 464 my %mult_params = @_; 465 my @keys = qw ( tolerance tolType format mode zeroLevel zeroLevelTol debug ); 466 my @correctVal; 467 my $formattedCorrectAnswer; 468 my @correct_num_answer; 469 my ($PG_eval_errors,$PG_full_error_report); 470 assign_option_aliases( \%mult_params, 471 'reltol' => 'relTol', 472 ); 473 set_default_options(\%mult_params, 474 'tolType' => (defined($mult_params{tol}) ) ? 'absolute' : 'relative', 475 # default mode should be relative, to obtain this tol must not be defined 476 'tolerance' => $main::numAbsTolDefault, 477 'relTol' => $main::numRelPercentTolDefault, 478 'zeroLevel' => $main::numZeroLevelDefault, 479 'zeroLevelTol' => $main::numZeroLevelTolDefault, 480 'format' => $main::numFormatDefault, 481 'debug' => 0, 482 'mode' => 'std', 483 'compare' => 'num', 484 ); 485 my $format = $mult_params{'format'}; 486 my $mode = $mult_params{'mode'}; 487 488 if( $mult_params{tolType} eq 'relative' ) { 489 $mult_params{'tolerance'} = .01*$mult_params{'tolerance'}; 490 } 491 492 if( $mult_params{ 'compare' } eq 'cplx' ){ 493 foreach( @answers ) 494 { 495 $_ = cplx( $_, 0 ) unless ref($_) =~/Complex/; 496 } 497 } 498 499 my $corrAnswerIsString = 0; 500 501 for( my $k = 0; $k < $number_of_answers; $k++ ){ 502 if (defined($mult_params{strings}) && $mult_params{strings}) { 503 my $legalString = ''; 504 my @legalStrings = @{$mult_params{strings}}; 505 $correct_num_answer[$k] = $answers[$k]; 506 $formattedCorrectAnswer .= $answers[$k] . ","; 507 foreach $legalString (@legalStrings) { 508 if ( uc($answers[$k]) eq uc($legalString) ) { 509 $corrAnswerIsString = 1; 510 511 last; 512 } 513 } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric 514 } else { 515 $correct_num_answer[$k] = $answers[$k]; 516 $formattedCorrectAnswer .= prfmt( $answers[$k], $mult_params{'format'} ) . ", "; 517 } 518 $correct_num_answer[$k] = math_constants($correct_num_answer[$k]); 519 my $PGanswerMessage = ''; 520 521 522 if (defined($correct_num_answer[$k]) && $correct_num_answer[$k] =~ /\S/ && $corrAnswerIsString == 0 ) { 523 ($correctVal[$k], $PG_eval_errors,$PG_full_error_report) = 524 PG_answer_eval($correct_num_answer[$k]); 525 } else { # case of a string answer 526 $PG_eval_errors = ' '; 527 $correctVal[$k] = $answers[$k]; 528 } 529 530 #if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal[$k])) && $corrAnswerIsString == 0)) { 531 ##error message from eval or above 532 #warn "Error in 'correct' answer: $PG_eval_errors<br> 533 #The answer $answers[$k] evaluates to $correctVal[$k], 534 #which cannot be interpreted as a number. "; 535 536 #} 537 ######################################################################## 538 $correctVal[$k] = $correct_num_answer[$k]; 539 } 540 $formattedCorrectAnswer =~ s/, \Z//; 541 542 #construct the answer evaluator 543 544 my $answer_evaluator = new AnswerEvaluator; 545 546 547 $answer_evaluator->{debug} = $mult_params{debug}; 548 $answer_evaluator->ans_hash( 549 correct_ans => [@correctVal], 550 type => "${mode}_number", 551 tolerance => $mult_params{tolerance}, 552 tolType => 'absolute', # $mult_params{tolType}, 553 original_correct_ans => $formattedCorrectAnswer, 554 answerIsString => $corrAnswerIsString, 555 answer_form => 'cartesian', 556 ); 557 my ($in, $formattedSubmittedAnswer); 558 $answer_evaluator->install_pre_filter(sub {my $rh_ans = shift; 559 $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; $rh_ans;} 560 ); 561 if (defined($mult_params{strings}) && $mult_params{strings}) { 562 $answer_evaluator->install_pre_filter(\&check_strings, %mult_params); 563 } 564 565 $answer_evaluator -> install_pre_filter( \&mult_prefilters, %mult_params ); 566 $answer_evaluator->install_pre_filter( sub{my $rh_ans = shift; $rh_ans->{original_student_ans} = $rh_ans->{student_ans};$rh_ans;}); 567 568 if ($corrAnswerIsString == 0 ){ # avoiding running compare_numbers when correct answer is a string. 569 $answer_evaluator->install_evaluator(\&compare_mult, %mult_params); 570 } 571 572 573 ############################################################################### 574 # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's 575 # can be displayed in the answer message. This may still cause a few anomolies when strings are used 576 # 577 ############################################################################### 578 $answer_evaluator->install_post_filter( sub{my $rh_ans = shift; $rh_ans->{student_ans} = $rh_ans->{original_student_ans};$rh_ans;}); 579 $answer_evaluator->install_post_filter(\&fix_answers_for_display); 580 $answer_evaluator->install_post_filter(\&fix_for_polar_display); 581 582 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; 583 return $rh_ans unless $rh_ans->catch_error('EVAL'); 584 $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message}; 585 $rh_ans->clear_error('EVAL'); } ); 586 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } ); 587 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('POLAR'); } ); 588 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('CARTESIAN'); } ); 589 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('COMPLEX'); } ); 590 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } ); 591 $answer_evaluator; 592 } 593 594 sub mult_prefilters{ 595 my ($rh_ans, %options) = @_; 596 my @student_answers = split/,/,$rh_ans->{student_ans}; 597 foreach( @student_answers ){ 598 $rh_ans->{student_ans} = $_; 599 $rh_ans = &check_syntax( $rh_ans ); 600 $rh_ans = &math_constants( $rh_ans ); 601 if( $options{compare} eq 'cplx' ){ 602 $rh_ans = &cplx_constants( $rh_ans ); 603 #$rh_ans = &check_for_polar( $rh_ans ); 604 } 605 if ( $options{mode} eq 'std') { 606 # do nothing 607 } elsif ($options{mode} eq 'strict_polar') { 608 $rh_ans = &is_a_polar( $rh_ans ); 609 } elsif ($options{mode} eq 'strict_num_cartesian') { 610 $rh_ans = &is_a_numeric_cartesian( $rh_ans ); 611 } elsif ($options{mode} eq 'strict_num_polar') { 612 $rh_ans = &is_a_numeric_polar( $rh_ans ); 613 } elsif ($options{mode} eq 'strict') { 614 $rh_ans = &is_a_numeric_complex( $rh_ans ); 615 } elsif ($options{mode} eq 'arith') { 616 $rh_ans = &is_an_arithmetic_expression( $rh_ans ); 617 } elsif ($options{mode} eq 'frac') { 618 $rh_ans = &is_a_fraction( $rh_ans ); 619 620 } else { 621 #$PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.'; 622 #$formattedSubmittedAnswer = $in; 623 } 624 $_ = $rh_ans->{student_ans}; 625 } 626 my $ans_string; 627 foreach( @student_answers ) 628 { 629 $ans_string .= ", $_"; 630 } 631 $ans_string =~ s/\A,//; 632 $rh_ans->{student_ans} = $ans_string; 633 $rh_ans; 634 } 635 636 # compares two complex numbers by comparing their real and imaginary parts 637 sub compare_mult { 638 my ($rh_ans, %options) = @_; 639 my @student_answers = split/,/,$rh_ans->{student_ans}; 640 my @correct_answers = @{$rh_ans->{correct_ans}}; 641 my $one_correct = 1/@correct_answers; 642 my $temp_score = 0; 643 foreach( @correct_answers ){ 644 $rh_ans->{correct_ans} = $_; 645 foreach( @student_answers ){ 646 $rh_ans->{student_ans} = $_; 647 if( $options{compare} eq 'cplx' ){ 648 $rh_ans = &compare_cplx( $rh_ans, %options); 649 }else{ 650 $rh_ans = &compare_numbers( $rh_ans, %options); 651 } 652 if( $rh_ans->{score} == 1 ) 653 { 654 $temp_score += $one_correct; 655 $rh_ans->{score} = 0; 656 last; 657 } 658 } 659 } 660 $rh_ans->{score} = $temp_score; 661 $rh_ans; 662 663 } 664 665 666 667 #this basically just checks for "e^" which unfortunately will show something like (e^4)*i as a polar, this should be changed 668 sub check_for_polar{ 669 670 my($in,%options) = @_; 671 my $rh_ans; 672 my $process_ans_hash = ( ref( $in ) eq 'AnswerHash' ) ? 1 : 0 ; 673 if ($process_ans_hash) { 674 $rh_ans = $in; 675 $in = $rh_ans->{student_ans}; 676 } 677 # The code fragment above allows this filter to be used when the input is simply a string 678 # as well as when the input is an AnswerHash, and options. 679 if( $in =~ /2.71828182845905\*\*/ ){ 680 $rh_ans->{answer_form} = 'polar'; 681 } 682 $rh_ans; 683 } 684 685 686 # compares two complex numbers by comparing their real and imaginary parts 687 sub compare_cplx { 688 my ($rh_ans, %options) = @_; 689 my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans}); 690 691 if ($PG_eval_errors) { 692 $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); 693 $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); 694 # return $rh_ans; 695 } else { 696 $rh_ans->{student_ans} = prfmt($inVal,$options{format}); 697 } 698 699 $inVal = cplx($inVal,0) unless ref($inVal) =~/Complex/; 700 my $permitted_error_Re; 701 my $permitted_error_Im; 702 if ($rh_ans->{tolType} eq 'absolute') { 703 $permitted_error_Re = $rh_ans->{tolerance}; 704 $permitted_error_Im = $rh_ans->{tolerance}; 705 } 706 elsif ( abs($rh_ans->{correct_ans}) <= $options{zeroLevel}) { 707 $permitted_error_Re = $options{zeroLevelTol}; ## want $tol to be non zero 708 $permitted_error_Im = $options{zeroLevelTol}; ## want $tol to be non zero 709 } 710 else { 711 $permitted_error_Re = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans}->Complex::Re); 712 $permitted_error_Im = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans}->Complex::Im); 713 714 } 715 716 $rh_ans->{score} = 1 if ( abs( $rh_ans->{correct_ans}->Complex::Re - $inVal->Complex::Re) <= 717 $permitted_error_Re && abs($rh_ans->{correct_ans}->Complex::Im - $inVal->Complex::Im )<= $permitted_error_Im ); 718 719 $rh_ans; 720 } 721 722 723 sub cplx_constants { 724 my($in,%options) = @_; 725 my $rh_ans; 726 my $process_ans_hash = ( ref( $in ) eq 'AnswerHash' ) ? 1 : 0 ; 727 if ($process_ans_hash) { 728 $rh_ans = $in; 729 $in = $rh_ans->{student_ans}; 730 } 731 # The code fragment above allows this filter to be used when the input is simply a string 732 # as well as when the input is an AnswerHash, and options. 733 $in =~ s/\bi\b/(i)/g; #try to keep -i being recognized as a file reference 734 # and recognized as a function whose output is an imaginary number 735 736 if ($process_ans_hash) { 737 $rh_ans->{student_ans}=$in; 738 return $rh_ans; 739 } else { 740 return $in; 741 } 742 } 743 744 ## allows only for numbers of the form a+bi and ae^(bi), where a and b are strict numbers 745 sub is_a_numeric_complex { 746 my ($num,%options) = @_; 747 my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; 748 my ($rh_ans); 749 if ($process_ans_hash) { 750 $rh_ans = $num; 751 $num = $rh_ans->{student_ans}; 752 } 753 754 my $is_a_number = 0; 755 return $is_a_number unless defined($num); 756 $num =~ s/^\s*//; ## remove initial spaces 757 $num =~ s/\s*$//; ## remove trailing spaces 758 759 if ($num =~ 760 761 /^($number[+,-]?($number\*\(i\)|\(i\)|\(i\)\*$number)|($number\*\(i\)|-?\(i\)|-?\(i\)\*$number)([+,-]$number)?|($number\*)?2.71828182845905\*\*\(($number\*\(i\)|\(i\)\*$number|i|-\(i\))\)|$number)$/){ 762 $is_a_number = 1; 763 } 764 765 if ($process_ans_hash) { 766 if ($is_a_number == 1 ) { 767 $rh_ans->{student_ans}=$num; 768 return $rh_ans; 769 } else { 770 $rh_ans->{student_ans} = "Incorrect number format: You must enter a numeric complex, e.g. a+bi 771 or a*e^(bi)"; 772 $rh_ans->throw_error('COMPLEX', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3'); 773 return $rh_ans; 774 } 775 } else { 776 return $is_a_number; 777 } 778 } 779 780 ## allows only for the form a + bi, where a and b are strict numbers 781 sub is_a_numeric_cartesian { 782 my ($num,%options) = @_; 783 my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; 784 my ($rh_ans); 785 if ($process_ans_hash) { 786 $rh_ans = $num; 787 $num = $rh_ans->{student_ans}; 788 } 789 790 my $is_a_number = 0; 791 return $is_a_number unless defined($num); 792 $num =~ s/^\s*//; ## remove initial spaces 793 $num =~ s/\s*$//; ## remove trailing spaces 794 795 if ($num =~ 796 797 /^($number[+,-]?($number\*\(i\)|\(i\)|\(i\)\*$number)|($number\*\(i\)|-?\(i\)|-?\(i\)\*$number)([+,-]$number)?|$number)$/){ 798 $is_a_number = 1; 799 } 800 801 if ($process_ans_hash) { 802 if ($is_a_number == 1 ) { 803 $rh_ans->{student_ans}=$num; 804 return $rh_ans; 805 } else { 806 $rh_ans->{student_ans} = "Incorrect number format: You must enter a numeric cartesian, e.g. a+bi"; 807 $rh_ans->throw_error('CARTESIAN', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3'); 808 return $rh_ans; 809 } 810 } else { 811 return $is_a_number; 812 } 813 } 814 815 ## allows only for the form ae^(bi), where a and b are strict numbers 816 sub is_a_numeric_polar { 817 my ($num,%options) = @_; 818 my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; 819 my ($rh_ans); 820 if ($process_ans_hash) { 821 $rh_ans = $num; 822 $num = $rh_ans->{student_ans}; 823 } 824 825 my $is_a_number = 0; 826 return $is_a_number unless defined($num); 827 $num =~ s/^\s*//; ## remove initial spaces 828 $num =~ s/\s*$//; ## remove trailing spaces 829 if ($num =~ 830 /^($number|($number\*)?2.71828182845905\*\*\(($number\*\(i\)|\(i\)\*$number|i|-\(i\))\))$/){ 831 $is_a_number = 1; 832 } 833 834 if ($process_ans_hash) { 835 if ($is_a_number == 1 ) { 836 $rh_ans->{student_ans}=$num; 837 return $rh_ans; 838 } else { 839 $rh_ans->{student_ans} = "Incorrect number format: You must enter a numeric polar, e.g. a*e^(bi)"; 840 $rh_ans->throw_error('POLAR', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3'); 841 return $rh_ans; 842 } 843 } else { 844 return $is_a_number; 845 } 846 } 847 #this subroutine mearly captures what is before and after the "e**" it does not verify that the "i" is there, or in the 848 #exponent this must eventually be addresed 849 sub is_a_polar { 850 my ($num,%options) = @_; 851 my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; 852 my ($rh_ans); 853 if ($process_ans_hash) { 854 $rh_ans = $num; 855 $num = $rh_ans->{student_ans}; 856 } 857 858 my $is_a_number = 0; 859 return $is_a_number unless defined($num); 860 $num =~ s/^\s*//; ## remove initial spaces 861 $num =~ s/\s*$//; ## remove trailing spaces 862 $num =~ /^(.*)\*2.71828182845905\*\*(.*)/; 863 #warn "rho: ", $1; 864 #warn "theta: ", $2; 865 if( defined( $1 ) ){ 866 if( &single_term( $1 ) && &single_term( $2 ) ) 867 { 868 $is_a_number = 1; 869 } 870 } 871 if ($process_ans_hash) { 872 if ($is_a_number == 1 ) { 873 $rh_ans->{student_ans}=$num; 874 return $rh_ans; 875 } else { 876 $rh_ans->{student_ans} = "Incorrect number format: You must enter a polar, e.g. a*e^(bi)"; 877 $rh_ans->throw_error('POLAR', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3'); 878 return $rh_ans; 879 } 880 } else { 881 return $is_a_number; 882 } 883 } 884 885 =head4 single_term() 886 This subroutine takes in a string, which is a mathematical expresion, and determines whether or not 887 it is a single term. This is accoplished using a stack. Open parenthesis pluses and minuses are all 888 added onto the stack, and when a closed parenthesis is reached, the stack is popped untill the open 889 parenthesis is found. If the original was a single term, the stack should be empty after 890 evaluation. If there is anything left ( + or - ) then false is returned. 891 Of course, the unary operator "-" must be handled... if it is a unary operator, and not a regular - 892 the only place it could occur unambiguously without being surrounded by parenthesis, is the very 893 first position. So that case is checked before the loop begins. 894 =cut 895 896 sub single_term{ 897 my $term = shift; 898 my @stack; 899 $term = reverse $term; 900 if( length $term >= 1 ) 901 { 902 my $temp = chop $term; 903 if( $temp ne "-" ){ $term .= $temp; } 904 } 905 while( length $term >= 1 ){ 906 my $character = chop $term; 907 if( $character eq "+" || $character eq "-" || $character eq "(" ){ 908 push @stack, $character; 909 }elsif( $character eq ")" ){ 910 while( pop @stack ne "(" ){} 911 } 912 913 } 914 if( scalar @stack == 0 ){ return 1;}else{ return 0;} 915 } 916 917 # changes default to display as a polar 918 sub fix_for_polar_display{ 919 my ($rh_ans, %options) = @_; 920 if( ref( $rh_ans->{student_ans} ) =~ /Complex/ && $rh_ans->{answer_form} eq 'polar' ){ 921 $rh_ans->{student_ans}->display_format( 'polar'); 922 ## these lines of code have the polar displayed as re^(theta) instead of [rho,theta] 923 $rh_ans->{student_ans} =~ s/,/*e^\(/; 924 $rh_ans->{student_ans} =~ s/\[//; 925 $rh_ans->{student_ans} =~ s/\]/i\)/; 926 } 927 $rh_ans; 928 } 929 930 sub cplx_cmp2 { 931 my $correctAnswer = shift; 932 my %cplx_params = @_; 933 my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug ); 934 assign_option_aliases( \%cplx_params, 935 'reltol' => 'relTol', 936 ); 937 set_default_options(\%cplx_params, 938 'tolType' => (defined($cplx_params{tol}) ) ? 'absolute' : 'relative', 939 # default mode should be relative, to obtain this tol must not be defined 940 'tolerance' => $main::numAbsTolDefault, 941 'relTol' => $main::numRelPercentTolDefault, 942 'zeroLevel' => $main::numZeroLevelDefault, 943 'zeroLevelTol' => $main::numZeroLevelTolDefault, 944 'format' => $main::numFormatDefault, 945 'debug' => 0, 946 'mode' => 'std', 947 948 ); 949 $correctAnswer = cplx($correctAnswer,0) unless ref($correctAnswer) =~/Complex/; 950 my $format = $cplx_params{'format'}; 951 my $mode = $cplx_params{'mode'}; 952 953 if( $cplx_params{tolType} eq 'relative' ) { 954 $cplx_params{'tolerance'} = .01*$cplx_params{'tolerance'}; 955 } 956 957 my $formattedCorrectAnswer; 958 my $correct_num_answer; 959 my $corrAnswerIsString = 0; 960 961 962 if (defined($cplx_params{strings}) && $cplx_params{strings}) { 963 my $legalString = ''; 964 my @legalStrings = @{$cplx_params{strings}}; 965 $correct_num_answer = $correctAnswer; 966 $formattedCorrectAnswer = $correctAnswer; 967 foreach $legalString (@legalStrings) { 968 if ( uc($correctAnswer) eq uc($legalString) ) { 969 $corrAnswerIsString = 1; 970 971 last; 972 } 973 } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric 974 } else { 975 $correct_num_answer = $correctAnswer; 976 $formattedCorrectAnswer = prfmt( $correctAnswer, $cplx_params{'format'} ); 977 } 978 $correct_num_answer = math_constants($correct_num_answer); 979 my $PGanswerMessage = ''; 980 981 my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report); 982 983 if (defined($correct_num_answer) && $correct_num_answer =~ /\S/ && $corrAnswerIsString == 0 ) { 984 ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correct_num_answer); 985 } else { # case of a string answer 986 $PG_eval_errors = ' '; 987 $correctVal = $correctAnswer; 988 } 989 990 if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal)) && $corrAnswerIsString == 0)) { 991 ##error message from eval or above 992 warn "Error in 'correct' answer: $PG_eval_errors<br> 993 The answer $correctAnswer evaluates to $correctVal, 994 which cannot be interpreted as a number. "; 995 996 } 997 ######################################################################## 998 $correctVal = $correct_num_answer;#it took me two and a half hours to figure out that correctVal wasn't 999 #getting the number properly 1000 #construct the answer evaluator 1001 my $answer_evaluator = new AnswerEvaluator; 1002 1003 1004 $answer_evaluator->{debug} = $cplx_params{debug}; 1005 $answer_evaluator->ans_hash( 1006 correct_ans => $correctVal, 1007 type => "${mode}_number", 1008 tolerance => $cplx_params{tolerance}, 1009 tolType => 'absolute', # $cplx_params{tolType}, 1010 original_correct_ans => $formattedCorrectAnswer, 1011 answerIsString => $corrAnswerIsString, 1012 answer_form => 'cartesian', 1013 ); 1014 my ($in, $formattedSubmittedAnswer); 1015 $answer_evaluator->install_pre_filter(sub {my $rh_ans = shift; 1016 $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; $rh_ans;} 1017 ); 1018 if (defined($cplx_params{strings}) && $cplx_params{strings}) { 1019 $answer_evaluator->install_pre_filter(\&check_strings, %cplx_params); 1020 } 1021 #$answer_evaluator->install_pre_filter(\&check_syntax); 1022 1023 $answer_evaluator->install_pre_filter(\&math_constants); 1024 $answer_evaluator->install_pre_filter(\&cplx_constants); 1025 $answer_evaluator->install_pre_filter(\&check_for_polar); 1026 if ($mode eq 'std') { 1027 # do nothing 1028 } elsif ($mode eq 'strict_polar') { 1029 $answer_evaluator->install_pre_filter(\&is_a_polar); 1030 } elsif ($mode eq 'strict_num_cartesian') { 1031 $answer_evaluator->install_pre_filter(\&is_a_numeric_cartesian); 1032 } elsif ($mode eq 'strict_num_polar') { 1033 $answer_evaluator->install_pre_filter(\&is_a_numeric_polar); 1034 } elsif ($mode eq 'strict') { 1035 $answer_evaluator->install_pre_filter(\&is_a_numeric_complex); 1036 } elsif ($mode eq 'arith') { 1037 $answer_evaluator->install_pre_filter(\&is_an_arithmetic_expression); 1038 } elsif ($mode eq 'frac') { 1039 $answer_evaluator->install_pre_filter(\&is_a_fraction); 1040 1041 } else { 1042 $PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.'; 1043 $formattedSubmittedAnswer = $in; 1044 } 1045 if ($corrAnswerIsString == 0 ){ # avoiding running compare_numbers when correct answer is a string. 1046 $answer_evaluator->install_evaluator(\&compare_cplx2, %cplx_params); 1047 } 1048 1049 1050 ############################################################################### 1051 # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's 1052 # can be displayed in the answer message. This may still cause a few anomolies when strings are used 1053 # 1054 ############################################################################### 1055 1056 $answer_evaluator->install_post_filter(\&fix_answers_for_display); 1057 $answer_evaluator->install_post_filter(\&fix_for_polar_display); 1058 1059 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; 1060 return $rh_ans unless $rh_ans->catch_error('EVAL'); 1061 $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message}; 1062 $rh_ans->clear_error('EVAL'); } ); 1063 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } ); 1064 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('POLAR'); } ); 1065 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('CARTESIAN'); } ); 1066 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('COMPLEX'); } ); 1067 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } ); 1068 $answer_evaluator; 1069 } 1070 1071 1072 # compares two complex numbers by comparing their real and imaginary parts 1073 sub compare_cplx2 { 1074 my ($rh_ans, %options) = @_; 1075 my @answers = split/,/,$rh_ans->{student_ans}; 1076 foreach( @answers ) 1077 { 1078 $rh_ans->{student_ans} = $_; 1079 $rh_ans = &check_syntax( $rh_ans ); 1080 my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans}); 1081 1082 if ($PG_eval_errors) { 1083 $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); 1084 $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); 1085 # return $rh_ans; 1086 } else { 1087 $rh_ans->{student_ans} = prfmt($inVal,$options{format}); 1088 } 1089 1090 $inVal = cplx($inVal,0) unless ref($inVal) =~/Complex/; 1091 my $permitted_error_Re; 1092 my $permitted_error_Im; 1093 if ($rh_ans->{tolType} eq 'absolute') { 1094 $permitted_error_Re = $rh_ans->{tolerance}; 1095 $permitted_error_Im = $rh_ans->{tolerance}; 1096 } 1097 elsif ( abs($rh_ans->{correct_ans}) <= $options{zeroLevel}) { 1098 $permitted_error_Re = $options{zeroLevelTol}; ## want $tol to be non zero 1099 $permitted_error_Im = $options{zeroLevelTol}; ## want $tol to be non zero 1100 } 1101 else { 1102 $permitted_error_Re = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans}->Complex::Re); 1103 $permitted_error_Im = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans}->Complex::Im); 1104 1105 } 1106 1107 $rh_ans->{score} = 1 if ( abs( $rh_ans->{correct_ans}->Complex::Re - $inVal->Complex::Re) <= 1108 $permitted_error_Re && abs($rh_ans->{correct_ans}->Complex::Im - $inVal->Complex::Im )<= $permitted_error_Im ); 1109 if( $rh_ans->{score} == 1 ){ return $rh_ans; } 1110 1111 1112 } 1113 $rh_ans; 1114 1115 } 1116 1117 1118 sub cplx_cmp_mult { 1119 my $correctAnswer = shift; 1120 my %cplx_params = @_; 1121 my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug ); 1122 assign_option_aliases( \%cplx_params, 1123 'reltol' => 'relTol', 1124 ); 1125 set_default_options(\%cplx_params, 1126 'tolType' => (defined($cplx_params{tol}) ) ? 'absolute' : 'relative', 1127 # default mode should be relative, to obtain this tol must not be defined 1128 'tolerance' => $main::numAbsTolDefault, 1129 'relTol' => $main::numRelPercentTolDefault, 1130 'zeroLevel' => $main::numZeroLevelDefault, 1131 'zeroLevelTol' => $main::numZeroLevelTolDefault, 1132 'format' => $main::numFormatDefault, 1133 'debug' => 0, 1134 'mode' => 'std', 1135 1136 ); 1137 $correctAnswer = cplx($correctAnswer,0) unless ref($correctAnswer) =~/Complex/; 1138 my $format = $cplx_params{'format'}; 1139 my $mode = $cplx_params{'mode'}; 1140 1141 if( $cplx_params{tolType} eq 'relative' ) { 1142 $cplx_params{'tolerance'} = .01*$cplx_params{'tolerance'}; 1143 } 1144 1145 my $formattedCorrectAnswer; 1146 my $correct_num_answer; 1147 my $corrAnswerIsString = 0; 1148 1149 1150 if (defined($cplx_params{strings}) && $cplx_params{strings}) { 1151 my $legalString = ''; 1152 my @legalStrings = @{$cplx_params{strings}}; 1153 $correct_num_answer = $correctAnswer; 1154 $formattedCorrectAnswer = $correctAnswer; 1155 foreach $legalString (@legalStrings) { 1156 if ( uc($correctAnswer) eq uc($legalString) ) { 1157 $corrAnswerIsString = 1; 1158 1159 last; 1160 } 1161 } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric 1162 } else { 1163 $correct_num_answer = $correctAnswer; 1164 $formattedCorrectAnswer = prfmt( $correctAnswer, $cplx_params{'format'} ); 1165 } 1166 $correct_num_answer = math_constants($correct_num_answer); 1167 my $PGanswerMessage = ''; 1168 1169 my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report); 1170 1171 if (defined($correct_num_answer) && $correct_num_answer =~ /\S/ && $corrAnswerIsString == 0 ) { 1172 ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correct_num_answer); 1173 } else { # case of a string answer 1174 $PG_eval_errors = ' '; 1175 $correctVal = $correctAnswer; 1176 } 1177 1178 if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal)) && $corrAnswerIsString == 0)) { 1179 ##error message from eval or above 1180 warn "Error in 'correct' answer: $PG_eval_errors<br> 1181 The answer $correctAnswer evaluates to $correctVal, 1182 which cannot be interpreted as a number. "; 1183 1184 } 1185 ######################################################################## 1186 $correctVal = $correct_num_answer;#it took me two and a half hours to figure out that correctVal wasn't 1187 #getting the number properly 1188 #construct the answer evaluator 1189 my $counter = 0; 1190 my $answer_evaluator = new AnswerEvaluator; 1191 1192 my $number; 1193 $answer_evaluator->install_pre_filter( sub{ my $rh_ans = shift; my @temp = 1194 split/,/,$rh_ans->{student_ans}; $number = @temp; warn "this number ", $number; $rh_ans;}); 1195 warn "number ", $number; 1196 while( $counter < 4 ) 1197 { 1198 $answer_evaluator = &answer_mult( $correctVal, $mode, $formattedCorrectAnswer, 1199 $corrAnswerIsString, $counter, %cplx_params ); 1200 warn "answer_evaluator ", $answer_evaluator; 1201 $answer_evaluator->install_evaluator( sub { my $rh_ans = shift; warn "score ", $rh_ans->{score}; 1202 $rh_ans;}); 1203 $counter += 1; 1204 } 1205 1206 $answer_evaluator; 1207 1208 } 1209 1210 sub answer_mult{ 1211 my $correctVal = shift; 1212 my $mode = shift; 1213 my $formattedCorrectAnswer = shift; 1214 my $corrAnswerIsString = shift; 1215 my $counter = shift; 1216 warn "counter ", $counter; 1217 1218 my %cplx_params = @_; 1219 my $answer_evaluator = new AnswerEvaluator; 1220 1221 1222 $answer_evaluator->{debug} = $cplx_params{debug}; 1223 $answer_evaluator->ans_hash( 1224 correct_ans => $correctVal, 1225 type => "${mode}_number", 1226 tolerance => $cplx_params{tolerance}, 1227 tolType => 'absolute', # $cplx_params{tolType}, 1228 original_correct_ans => $formattedCorrectAnswer, 1229 answerIsString => $corrAnswerIsString, 1230 answer_form => 'cartesian', 1231 ); 1232 $answer_evaluator->install_pre_filter(sub { 1233 my $rh_ans = shift; 1234 $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; 1235 my @answers = split/,/,$rh_ans->{student_ans}; 1236 $rh_ans -> {student_ans} = $answers[$counter]; 1237 $rh_ans; 1238 } 1239 ); 1240 if (defined($cplx_params{strings}) && $cplx_params{strings}) { 1241 $answer_evaluator->install_pre_filter(\&check_strings, %cplx_params); 1242 } 1243 $answer_evaluator->install_pre_filter(\&check_syntax); 1244 $answer_evaluator->install_pre_filter(\&math_constants); 1245 $answer_evaluator->install_pre_filter(\&cplx_constants); 1246 $answer_evaluator->install_pre_filter(\&check_for_polar); 1247 if ($mode eq 'std') { 1248 # do nothing 1249 } elsif ($mode eq 'strict_polar') { 1250 $answer_evaluator->install_pre_filter(\&is_a_polar); 1251 } elsif ($mode eq 'strict_num_cartesian') { 1252 $answer_evaluator->install_pre_filter(\&is_a_numeric_cartesian); 1253 } elsif ($mode eq 'strict_num_polar') { 1254 $answer_evaluator->install_pre_filter(\&is_a_numeric_polar); 1255 } elsif ($mode eq 'strict') { 1256 $answer_evaluator->install_pre_filter(\&is_a_numeric_complex); 1257 } elsif ($mode eq 'arith') { 1258 $answer_evaluator->install_pre_filter(\&is_an_arithmetic_expression); 1259 } elsif ($mode eq 'frac') { 1260 $answer_evaluator->install_pre_filter(\&is_a_fraction); 1261 1262 } else { 1263 #$PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.'; 1264 } 1265 if ($corrAnswerIsString == 0 ){ # avoiding running compare_numbers when correct answer is a string. 1266 $answer_evaluator->install_evaluator(\&compare_cplx, %cplx_params); 1267 } 1268 1269 1270 ############################################################################### 1271 # We'll leave these next lines out for now, so that the evaluated versions of the student's and professor's 1272 # can be displayed in the answer message. This may still cause a few anomolies when strings are used 1273 # 1274 ############################################################################### 1275 1276 $answer_evaluator->install_post_filter(\&fix_answers_for_display); 1277 $answer_evaluator->install_post_filter(\&fix_for_polar_display); 1278 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; 1279 return $rh_ans unless $rh_ans->catch_error('EVAL'); 1280 $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message}; 1281 $rh_ans->clear_error('EVAL'); } ); 1282 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } ); 1283 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('POLAR'); } ); 1284 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('CARTESIAN'); } ); 1285 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('COMPLEX'); } ); 1286 $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; warn "ans hash", $rh_ans->clear_error('STRING'); } ); 1287 $answer_evaluator; 1288 } 1289 1290 1291 1292 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |