| 1 | #!/usr/local/bin/perl -w |
1 | #!/usr/local/bin/webwork-perl |
| 2 | |
2 | |
| 3 | # This file is PGanswermacros.pl |
3 | # This file is PGanswermacros.pl |
| 4 | # This includes the subroutines for the ANS macros, that |
4 | # This includes the subroutines for the ANS macros, that |
| 5 | # is, macros allowing a more flexible answer checking |
5 | # is, macros allowing a more flexible answer checking |
| 6 | #################################################################### |
6 | #################################################################### |
| 7 | # Copyright @ 1995-2000 University of Rochester |
7 | # Copyright @ 1995-2000 University of Rochester |
| 8 | # All Rights Reserved |
8 | # All Rights Reserved |
| 9 | #################################################################### |
9 | #################################################################### |
|
|
10 | #$Id$ |
| 10 | |
11 | |
| 11 | =head1 NAME |
12 | =head1 NAME |
| 12 | |
13 | |
| 13 | PGanswermacros.pl -- located in the courseScripts directory |
14 | PGanswermacros.pl -- located in the courseScripts directory |
| 14 | |
15 | |
| … | |
… | |
| 106 | =cut |
107 | =cut |
| 107 | |
108 | |
| 108 | BEGIN { |
109 | BEGIN { |
| 109 | be_strict(); # an alias for use strict. This means that all global variable must contain main:: as a prefix. |
110 | be_strict(); # an alias for use strict. This means that all global variable must contain main:: as a prefix. |
| 110 | } |
111 | } |
| 111 | my ($BR , # convenient localizations. |
112 | |
| 112 | $PAR , |
113 | |
| 113 | $numRelPercentTolDefault , |
114 | |
| 114 | $numZeroLevelDefault , |
115 | sub _PGanswermacros_export { |
| 115 | $numZeroLevelTolDefault , |
116 | my @EXPORT = ( |
| 116 | $numAbsTolDefault , |
117 | '&std_num_cmp', '&std_num_cmp_list', '&std_num_cmp_abs', |
| 117 | $numFormatDefault , |
118 | '&std_num_cmp_abs_list', '&frac_num_cmp', '&frac_num_cmp_list', |
| 118 | $functRelPercentTolDefault , |
119 | '&frac_num_cmp_abs', '&frac_num_cmp_abs_list', '&arith_num_cmp', |
| 119 | $functZeroLevelDefault , |
120 | '&arith_num_cmp_list', '&arith_num_cmp_abs', '&arith_num_cmp_abs_list', |
| 120 | $functZeroLevelTolDefault , |
121 | '&strict_num_cmp', '&strict_num_cmp_list', '&strict_num_cmp_abs', |
| 121 | $functAbsTolDefault , |
122 | '&strict_num_cmp_abs_list', '&numerical_compare_with_units', |
| 122 | $functNumOfPoints , |
123 | '&std_num_str_cmp', '&num_cmp', '&num_rel_cmp', '&NUM_CMP', |
| 123 | $functVarDefault , |
124 | '&NUM_CMP_LIST', '&adaptive_function_cmp', '&function_cmp', |
| 124 | $functLLimitDefault , |
125 | '&function_cmp_up_to_constant', '&function_cmp_abs', |
| 125 | $functULimitDefault , |
126 | '&function_cmp_up_to_constant_abs', '&multivar_function_cmp', |
| 126 | $functMaxConstantOfIntegration |
127 | '&fun_cmp', '&FUNCTION_CMP', '&is_array', '&check_syntax', |
|
|
128 | '&std_num_filter', '&std_num_array_filter', '&function_from_string2', |
|
|
129 | '&is_zero_array', '&best_approx_parameters', |
|
|
130 | '&calculate_difference_vector', '&str_filters', '&remove_whitespace', |
|
|
131 | '&compress_whitespace', '&trim_whitespace', '&ignore_case', |
|
|
132 | '&ignore_order', '&std_str_cmp', '&std_str_cmp_list', '&std_cs_str_cmp', |
|
|
133 | '&std_cs_str_cmp_list', '&strict_str_cmp', '&strict_str_cmp_list', |
|
|
134 | '&unordered_str_cmp', '&unordered_str_cmp_list', |
|
|
135 | '&unordered_cs_str_cmp', '&unordered_cs_str_cmp_list', |
|
|
136 | '&ordered_str_cmp', '&ordered_str_cmp_list', '&ordered_cs_str_cmp', |
|
|
137 | '&ordered_cs_str_cmp_list', '&str_cmp', '&STR_CMP', '&checkbox_cmp', |
|
|
138 | '&radio_cmp', '&store_ans_at', '&DUMMY_ANSWER', '&escapeHTML', |
|
|
139 | '&anstext', '&ansradio', '&mail_answers_to', '&mail_answers_to2', |
|
|
140 | '&install_problem_grader', '&std_problem_grader', |
|
|
141 | '&std_problem_grader2', '&avg_problem_grader', '&get_var_array', |
|
|
142 | '&get_limits_array', '&check_option_list', '&function_invalid_params', |
|
|
143 | '&is_a_number', '&is_a_fraction', '&is_an_arithmetic_expression', |
|
|
144 | '&math_constants', '&clean_up_error_msg', '&prfmt', '&pretty_print', |
|
|
145 | '&set_default_options', '&assign_option_aliases', |
| 127 | ); |
146 | ); |
|
|
147 | @EXPORT; |
|
|
148 | } |
| 128 | |
149 | |
|
|
150 | my ($BR, $PAR,$numRelPercentTolDefault,$numZeroLevelDefault,$numZeroLevelTolDefault, |
|
|
151 | $numAbsTolDefault,$numFormatDefault,$functRelPercentTolDefault,$functZeroLevelDefault, |
|
|
152 | $functZeroLevelTolDefault,$functAbsTolDefault,$functNumOfPoints,$functVarDefault, |
|
|
153 | $functLLimitDefault, $functULimitDefault, $functMaxConstantOfIntegration, |
|
|
154 | ); |
|
|
155 | |
| 129 | sub _PGanswermacros_init { |
156 | sub _PGanswermacros_init { |
| 130 | |
157 | |
| 131 | $BR = $main::BR; # convenient localizations. |
158 | $BR = $main::BR; # convenient localizations. |
| 132 | $PAR = $main::PAR; |
159 | $PAR = $main::PAR; |
| 133 | |
160 | |
| 134 | # import defaults |
161 | # import defaults |
| 135 | # these are now imported from the %envir variable |
162 | # these are now imported from the %envir variable |
| 136 | $numRelPercentTolDefault = $main::numRelPercentTolDefault; |
163 | $numRelPercentTolDefault = PG_restricted_eval(q{$main::numRelPercentTolDefault}); |
| 137 | $numZeroLevelDefault = $main::numZeroLevelDefault; |
164 | $numZeroLevelDefault = PG_restricted_eval(q{$main::numZeroLevelDefault}); |
| 138 | $numZeroLevelTolDefault = $main::numZeroLevelTolDefault; |
165 | $numZeroLevelTolDefault = PG_restricted_eval(q{$main::numZeroLevelTolDefault}); |
| 139 | $numAbsTolDefault = $main::numAbsTolDefault; |
166 | $numAbsTolDefault = PG_restricted_eval(q{$main::numAbsTolDefault}); |
| 140 | $numFormatDefault = $main::numFormatDefault; |
167 | $numFormatDefault = PG_restricted_eval(q{$main::numFormatDefault}); |
| 141 | |
168 | |
| 142 | $functRelPercentTolDefault = $main::functRelPercentTolDefault; |
169 | $functRelPercentTolDefault = PG_restricted_eval(q{$main::functRelPercentTolDefault}); |
| 143 | $functZeroLevelDefault = $main::functZeroLevelDefault; |
170 | $functZeroLevelDefault = PG_restricted_eval(q{$main::functZeroLevelDefault}); |
| 144 | $functZeroLevelTolDefault = $main::functZeroLevelTolDefault; |
171 | $functZeroLevelTolDefault = PG_restricted_eval(q{$main::functZeroLevelTolDefault}); |
| 145 | $functAbsTolDefault = $main::functAbsTolDefault; |
172 | $functAbsTolDefault = PG_restricted_eval(q{$main::functAbsTolDefault}); |
| 146 | $functNumOfPoints = $main::functNumOfPoints; |
173 | $functNumOfPoints = PG_restricted_eval(q{$main::functNumOfPoints}); |
| 147 | $functVarDefault = $main::functVarDefault; |
174 | $functVarDefault = PG_restricted_eval(q{$main::functVarDefault}); |
| 148 | $functLLimitDefault = $main::functLLimitDefault; |
175 | $functLLimitDefault = PG_restricted_eval(q{$main::functLLimitDefault}); |
| 149 | $functULimitDefault = $main::functULimitDefault; |
176 | $functULimitDefault = PG_restricted_eval(q{$main::functULimitDefault}); |
| 150 | $functMaxConstantOfIntegration = $main::functMaxConstantOfIntegration; |
177 | $functMaxConstantOfIntegration = PG_restricted_eval(q{$main::functMaxConstantOfIntegration}); |
| 151 | |
178 | |
|
|
179 | |
|
|
180 | |
| 152 | } |
181 | } |
| 153 | _PGanswermacros_init(); |
|
|
| 154 | |
182 | |
| 155 | ########################################################################## |
183 | ########################################################################## |
| 156 | ########################################################################## |
184 | ########################################################################## |
| 157 | ## Number answer evaluators |
185 | ## Number answer evaluators |
| 158 | |
186 | |
| … | |
… | |
| 349 | The student answer can contain elementary functions, e.g. sin(.3+pi/2) |
377 | The student answer can contain elementary functions, e.g. sin(.3+pi/2) |
| 350 | |
378 | |
| 351 | =cut |
379 | =cut |
| 352 | |
380 | |
| 353 | sub std_num_cmp { # compare numbers allowing use of elementary functions |
381 | sub std_num_cmp { # compare numbers allowing use of elementary functions |
| 354 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
382 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 355 | |
383 | |
| 356 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
384 | my %options = ( 'tolerance' => $relPercentTol, |
| 357 | 'tolerance' => $relPercentTol, |
|
|
| 358 | 'tolType' => 'relative', |
|
|
| 359 | 'format' => $format, |
385 | 'format' => $format, |
|
|
386 | 'zeroLevel' => $zeroLevel, |
|
|
387 | 'zeroLevelTol' => $zeroLevelTol |
|
|
388 | ); |
|
|
389 | |
|
|
390 | set_default_options( \%options, |
|
|
391 | 'tolType' => 'relative', |
|
|
392 | 'tolerance' => $numRelPercentTolDefault, |
| 360 | 'mode' => 'std', |
393 | 'mode' => 'std', |
| 361 | 'zeroLevel' => $zeroLevel, |
394 | 'format' => $numFormatDefault, |
| 362 | 'zeroLevelTol' => $zeroLevelTol |
395 | 'relTol' => $numRelPercentTolDefault, |
|
|
396 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
397 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
398 | 'debug' => 0, |
| 363 | ); |
399 | ); |
|
|
400 | |
|
|
401 | num_cmp([$correctAnswer], %options); |
| 364 | } |
402 | } |
| 365 | |
403 | |
| 366 | ## Similar to std_num_cmp but accepts a list of numbers in the form |
404 | ## Similar to std_num_cmp but accepts a list of numbers in the form |
| 367 | ## std_num_cmp_list(relpercentTol,format,ans1,ans2,ans3,...) |
405 | ## std_num_cmp_list(relpercentTol,format,ans1,ans2,ans3,...) |
| 368 | ## format is of the form "%10.3g" or "", i.e., a format suitable for sprintf(). Use "" for default |
406 | ## format is of the form "%10.3g" or "", i.e., a format suitable for sprintf(). Use "" for default |
| 369 | ## You must enter a format and tolerance |
407 | ## You must enter a format and tolerance |
| 370 | sub std_num_cmp_list { |
408 | sub std_num_cmp_list { |
| 371 | my ( $relPercentTol, $format, @answerList) = @_; |
409 | my ( $relPercentTol, $format, @answerList) = @_; |
| 372 | |
410 | |
| 373 | NUM_CMP_LIST( 'tolerance' => $relPercentTol, |
411 | my %options = ( 'tolerance' => $relPercentTol, |
| 374 | 'tolType' => 'relative', |
412 | 'format' => $format, |
| 375 | 'format' => $format, |
|
|
| 376 | 'mode' => 'std', |
|
|
| 377 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
| 378 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
| 379 | 'answerList' => \@answerList |
|
|
| 380 | ); |
413 | ); |
| 381 | } |
|
|
| 382 | |
414 | |
|
|
415 | set_default_options( \%options, |
|
|
416 | 'tolType' => 'relative', |
|
|
417 | 'tolerance' => $numRelPercentTolDefault, |
|
|
418 | 'mode' => 'std', |
|
|
419 | 'format' => $numFormatDefault, |
|
|
420 | 'relTol' => $numRelPercentTolDefault, |
|
|
421 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
422 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
423 | 'debug' => 0, |
|
|
424 | ); |
|
|
425 | |
|
|
426 | num_cmp(\@answerList, %options); |
|
|
427 | |
|
|
428 | } |
|
|
429 | |
| 383 | sub std_num_cmp_abs { # compare numbers allowing use of elementary functions with absolute tolerance |
430 | sub std_num_cmp_abs { # compare numbers allowing use of elementary functions with absolute tolerance |
| 384 | my ( $correctAnswer, $absTol, $format) = @_; |
431 | my ( $correctAnswer, $absTol, $format) = @_; |
|
|
432 | my %options = ( 'tolerance' => $absTol, |
|
|
433 | 'format' => $format); |
|
|
434 | |
|
|
435 | set_default_options (\%options, |
|
|
436 | 'tolType' => 'absolute', |
|
|
437 | 'tolerance' => $absTol, |
|
|
438 | 'mode' => 'std', |
|
|
439 | 'format' => $numFormatDefault, |
|
|
440 | 'zeroLevel' => 0, |
|
|
441 | 'zeroLevelTol' => 0, |
|
|
442 | 'debug' => 0, |
|
|
443 | ); |
| 385 | |
444 | |
| 386 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
445 | num_cmp([$correctAnswer], %options); |
| 387 | 'tolerance' => $absTol, |
|
|
| 388 | 'tolType' => 'absolute', |
|
|
| 389 | 'format' => $format, |
|
|
| 390 | 'mode' => 'std', |
|
|
| 391 | 'zeroLevel' => 0, |
|
|
| 392 | 'zeroLevelTol' => 0 |
|
|
| 393 | ); |
|
|
| 394 | } |
446 | } |
| 395 | |
447 | |
| 396 | ## See std_num_cmp_list for usage |
448 | ## See std_num_cmp_list for usage |
|
|
449 | |
| 397 | sub std_num_cmp_abs_list { |
450 | sub std_num_cmp_abs_list { |
| 398 | my ( $absTol, $format, @answerList ) = @_; |
451 | my ( $absTol, $format, @answerList ) = @_; |
| 399 | |
452 | |
| 400 | NUM_CMP_LIST( 'tolerance' => $absTol, |
453 | my %options = ( 'tolerance' => $absTol, |
| 401 | 'tolType' => 'absolute', |
454 | 'format' => $format, |
| 402 | 'format' => $format, |
|
|
| 403 | 'mode' => 'std', |
|
|
| 404 | 'zeroLevel' => 0, |
|
|
| 405 | 'zeroLevelTol' => 0, |
|
|
| 406 | 'answerList' => \@answerList |
|
|
| 407 | ); |
455 | ); |
| 408 | } |
|
|
| 409 | |
456 | |
|
|
457 | set_default_options( \%options, |
|
|
458 | 'tolType' => 'absolute', |
|
|
459 | 'tolerance' => $absTol, |
|
|
460 | 'mode' => 'std', |
|
|
461 | 'format' => $numFormatDefault, |
|
|
462 | 'zeroLevel' => 0, |
|
|
463 | 'zeroLevelTol' => 0, |
|
|
464 | 'debug' => 0, |
|
|
465 | ); |
|
|
466 | |
|
|
467 | num_cmp(\@answerList, %options); |
|
|
468 | |
|
|
469 | } |
| 410 | |
470 | |
| 411 | sub frac_num_cmp { # only allow fractions and numbers as submitted answer |
471 | sub frac_num_cmp { # only allow fractions and numbers as submitted answer |
|
|
472 | |
| 412 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
473 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
|
|
474 | |
|
|
475 | my %options = ( 'tolerance' => $relPercentTol, |
|
|
476 | 'format' => $format, |
|
|
477 | 'zeroLevel' => $zeroLevel, |
|
|
478 | 'zeroLevelTol' => $zeroLevelTol |
|
|
479 | ); |
| 413 | |
480 | |
| 414 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
481 | set_default_options( \%options, |
|
|
482 | 'tolType' => 'relative', |
| 415 | 'tolerance' => $relPercentTol, |
483 | 'tolerance' => $relPercentTol, |
| 416 | 'tolType' => 'relative', |
484 | 'mode' => 'frac', |
| 417 | 'format' => $format, |
485 | 'format' => $numFormatDefault, |
| 418 | 'mode' => 'frac', |
486 | 'zeroLevel' => $numZeroLevelDefault, |
| 419 | 'zeroLevel' => $zeroLevel, |
487 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 420 | 'zeroLevelTol' => $zeroLevelTol |
488 | 'relTol' => $numRelPercentTolDefault, |
|
|
489 | 'debug' => 0, |
| 421 | ); |
490 | ); |
|
|
491 | |
|
|
492 | num_cmp([$correctAnswer], %options); |
| 422 | } |
493 | } |
| 423 | |
494 | |
| 424 | ## See std_num_cmp_list for usage |
495 | ## See std_num_cmp_list for usage |
| 425 | sub frac_num_cmp_list { |
496 | sub frac_num_cmp_list { |
| 426 | my ( $relPercentTol, $format, @answerList ) = @_; |
497 | my ( $relPercentTol, $format, @answerList ) = @_; |
| 427 | |
498 | |
| 428 | NUM_CMP_LIST( 'tolerance' => $relPercentTol, |
499 | my %options = ( 'tolerance' => $relPercentTol, |
| 429 | 'tolType' => 'relative', |
500 | 'format' => $format |
| 430 | 'format' => $format, |
501 | ); |
| 431 | 'mode' => 'frac', |
502 | |
|
|
503 | set_default_options( \%options, |
|
|
504 | 'tolType' => 'relative', |
|
|
505 | 'tolerance' => $relPercentTol, |
|
|
506 | 'mode' => 'frac', |
|
|
507 | 'format' => $numFormatDefault, |
| 432 | 'zeroLevel' => $numZeroLevelDefault, |
508 | 'zeroLevel' => $numZeroLevelDefault, |
| 433 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
509 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 434 | 'answerList' => \@answerList |
510 | 'relTol' => $numRelPercentTolDefault, |
| 435 | ); |
511 | 'debug' => 0, |
|
|
512 | ); |
|
|
513 | |
|
|
514 | num_cmp(\@answerList, %options); |
|
|
515 | |
| 436 | } |
516 | } |
| 437 | |
517 | |
|
|
518 | |
| 438 | sub frac_num_cmp_abs { # only allow fraction expressions as submitted answer with absolute tolerance |
519 | sub frac_num_cmp_abs { # only allow fraction expressions as submitted answer with absolute tolerance |
| 439 | my ( $correctAnswer, $absTol, $format ) = @_; |
520 | my ( $correctAnswer, $absTol, $format ) = @_; |
| 440 | |
521 | |
| 441 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
522 | my %options = ( 'tolerance' => $absTol, |
|
|
523 | 'format' => $format |
|
|
524 | ); |
|
|
525 | |
|
|
526 | set_default_options (\%options, |
|
|
527 | 'tolType' => 'absolute', |
| 442 | 'tolerance' => $absTol, |
528 | 'tolerance' => $absTol, |
| 443 | 'tolType' => 'absolute', |
529 | 'mode' => 'frac', |
| 444 | 'format' => $format, |
530 | 'format' => $numFormatDefault, |
| 445 | 'mode' => 'frac', |
531 | 'zeroLevel' => 0, |
| 446 | 'zeroLevel' => 0, |
|
|
| 447 | 'zeroLevelTol' => 0 |
532 | 'zeroLevelTol' => 0, |
| 448 | ); |
533 | 'debug' => 0, |
| 449 | } |
534 | ); |
|
|
535 | num_cmp([$correctAnswer], %options); |
|
|
536 | |
| 450 | |
537 | |
|
|
538 | } |
|
|
539 | |
| 451 | ## See std_num_cmp_list for usage |
540 | ## See std_num_cmp_list for usage |
| 452 | sub frac_num_cmp_abs_list { |
541 | sub frac_num_cmp_abs_list { |
| 453 | my ( $absTol, $format, @answerList ) = @_; |
542 | my ( $absTol, $format, @answerList ) = @_; |
| 454 | |
543 | |
| 455 | NUM_CMP_LIST( 'tolerance' => $absTol, |
544 | my %options = ( 'tolerance' => $absTol, |
| 456 | 'tolType' => 'absolute', |
545 | 'format' => $format |
| 457 | 'format' => $format, |
546 | ); |
| 458 | 'mode' => 'frac', |
547 | |
| 459 | 'zeroLevel' => 0, |
548 | set_default_options (\%options, |
|
|
549 | 'tolType' => 'absolute', |
|
|
550 | 'tolerance' => $absTol, |
|
|
551 | 'mode' => 'frac', |
|
|
552 | 'format' => $numFormatDefault, |
|
|
553 | 'zeroLevel' => 0, |
| 460 | 'zeroLevelTol' => 0, |
554 | 'zeroLevelTol' => 0, |
| 461 | 'answerList' => \@answerList |
555 | 'debug' => 0, |
| 462 | ); |
556 | ); |
|
|
557 | |
|
|
558 | num_cmp(\@answerList, %options); |
| 463 | } |
559 | } |
| 464 | |
560 | |
| 465 | |
561 | |
| 466 | sub arith_num_cmp { # only allow arithmetic expressions as submitted answer |
562 | sub arith_num_cmp { # only allow arithmetic expressions as submitted answer |
|
|
563 | |
| 467 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
564 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 468 | |
565 | |
| 469 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
566 | my %options = ( 'tolerance' => $relPercentTol, |
| 470 | 'tolerance' => $relPercentTol, |
567 | 'format' => $format, |
| 471 | 'tolType' => 'relative', |
568 | 'zeroLevel' => $zeroLevel, |
| 472 | 'format' => $format, |
|
|
| 473 | 'mode' => 'arith', |
|
|
| 474 | 'zeroLevel' => $zeroLevel, |
|
|
| 475 | 'zeroLevelTol' => $zeroLevelTol |
569 | 'zeroLevelTol' => $zeroLevelTol |
| 476 | ); |
570 | ); |
|
|
571 | |
|
|
572 | set_default_options( \%options, |
|
|
573 | 'tolType' => 'relative', |
|
|
574 | 'tolerance' => $relPercentTol, |
|
|
575 | 'mode' => 'arith', |
|
|
576 | 'format' => $numFormatDefault, |
|
|
577 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
578 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
579 | 'relTol' => $numRelPercentTolDefault, |
|
|
580 | 'debug' => 0, |
|
|
581 | ); |
|
|
582 | |
|
|
583 | num_cmp([$correctAnswer], %options); |
| 477 | } |
584 | } |
| 478 | |
585 | |
| 479 | ## See std_num_cmp_list for usage |
586 | ## See std_num_cmp_list for usage |
| 480 | sub arith_num_cmp_list { |
587 | sub arith_num_cmp_list { |
| 481 | my ( $relPercentTol, $format, @answerList ) = @_; |
588 | my ( $relPercentTol, $format, @answerList ) = @_; |
| 482 | |
589 | |
| 483 | NUM_CMP_LIST( 'tolerance' => $relPercentTol, |
590 | my %options = ( 'tolerance' => $relPercentTol, |
| 484 | 'tolType' => 'relative', |
591 | 'format' => $format, |
| 485 | 'format' => $format, |
592 | ); |
| 486 | 'mode' => 'arith', |
|
|
| 487 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
| 488 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
| 489 | 'answerList' => \@answerList |
|
|
| 490 | ); |
|
|
| 491 | } |
|
|
| 492 | |
593 | |
|
|
594 | set_default_options( \%options, |
|
|
595 | 'tolType' => 'relative', |
|
|
596 | 'tolerance' => $relPercentTol, |
|
|
597 | 'mode' => 'arith', |
|
|
598 | 'format' => $numFormatDefault, |
|
|
599 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
600 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
601 | 'relTol' => $numRelPercentTolDefault, |
|
|
602 | 'debug' => 0, |
|
|
603 | ); |
|
|
604 | num_cmp(\@answerList, %options); |
|
|
605 | } |
|
|
606 | |
| 493 | sub arith_num_cmp_abs { # only allow arithmetic expressions as submitted answer with absolute tolerance |
607 | sub arith_num_cmp_abs { # only allow arithmetic expressions as submitted answer with absolute tolerance |
| 494 | my ( $correctAnswer, $absTol, $format ) = @_; |
608 | my ( $correctAnswer, $absTol, $format ) = @_; |
|
|
609 | |
|
|
610 | my %options = ( 'tolerance' => $absTol, |
|
|
611 | 'format' => $format |
|
|
612 | ); |
|
|
613 | |
|
|
614 | set_default_options (\%options, |
|
|
615 | 'tolType' => 'absolute', |
|
|
616 | 'tolerance' => $absTol, |
|
|
617 | 'mode' => 'arith', |
|
|
618 | 'format' => $numFormatDefault, |
|
|
619 | 'zeroLevel' => 0, |
|
|
620 | 'zeroLevelTol' => 0, |
|
|
621 | 'debug' => 0, |
|
|
622 | ); |
|
|
623 | num_cmp([$correctAnswer], %options); |
| 495 | |
624 | |
| 496 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
625 | |
| 497 | 'tolerance' => $absTol, |
|
|
| 498 | 'tolType' => 'absolute', |
|
|
| 499 | 'format' => $format, |
|
|
| 500 | 'mode' => 'arith', |
|
|
| 501 | 'zeroLevel' => 0, |
|
|
| 502 | 'zeroLevelTol' => 0 |
|
|
| 503 | ); |
|
|
| 504 | } |
626 | } |
| 505 | |
627 | |
| 506 | ## See std_num_cmp_list for usage |
628 | ## See std_num_cmp_list for usage |
| 507 | sub arith_num_cmp_abs_list { |
629 | sub arith_num_cmp_abs_list { |
| 508 | my ( $absTol, $format, @answerList ) = @_; |
630 | my ( $absTol, $format, @answerList ) = @_; |
| 509 | |
631 | |
| 510 | NUM_CMP_LIST( 'tolerance' => $absTol, |
632 | my %options = ( 'tolerance' => $absTol, |
| 511 | 'tolType' => 'absolute', |
633 | 'format' => $format |
| 512 | 'format' => $format, |
634 | ); |
| 513 | 'mode' => 'arith', |
635 | |
| 514 | 'zeroLevel' => 0, |
636 | set_default_options (\%options, |
| 515 | 'zeroLevelTol' => 0, |
637 | 'tolType' => 'absolute', |
| 516 | 'answerList' => \@answerList |
638 | 'tolerance' => $absTol, |
| 517 | ); |
639 | 'mode' => 'arith', |
|
|
640 | 'format' => $numFormatDefault, |
|
|
641 | 'zeroLevel' => 0, |
|
|
642 | 'zeroLevelTol' => 0, |
|
|
643 | 'debug' => 0, |
|
|
644 | ); |
|
|
645 | num_cmp(\@answerList, %options); |
|
|
646 | |
| 518 | } |
647 | } |
| 519 | |
648 | |
| 520 | sub strict_num_cmp { # only allow numbers as submitted answer |
649 | sub strict_num_cmp { # only allow numbers as submitted answer |
|
|
650 | |
| 521 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
651 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
|
|
652 | |
|
|
653 | my %options = ( 'tolerance' => $relPercentTol, |
|
|
654 | 'format' => $format, |
|
|
655 | 'zeroLevel' => $zeroLevel, |
|
|
656 | 'zeroLevelTol' => $zeroLevelTol |
|
|
657 | ); |
|
|
658 | |
|
|
659 | set_default_options( \%options, |
|
|
660 | 'tolType' => 'relative', |
|
|
661 | 'tolerance' => $relPercentTol, |
|
|
662 | 'mode' => 'strict', |
|
|
663 | 'format' => $numFormatDefault, |
|
|
664 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
665 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
666 | 'relTol' => $numRelPercentTolDefault, |
|
|
667 | 'debug' => 0, |
|
|
668 | ); |
|
|
669 | |
|
|
670 | num_cmp([$correctAnswer], %options); |
| 522 | |
671 | |
| 523 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
|
|
| 524 | 'tolerance' => $relPercentTol, |
|
|
| 525 | 'tolType' => 'relative', |
|
|
| 526 | 'format' => $format, |
|
|
| 527 | 'mode' => 'strict', |
|
|
| 528 | 'zeroLevel' => $zeroLevel, |
|
|
| 529 | 'zeroLevelTol' => $zeroLevelTol |
|
|
| 530 | ); |
|
|
| 531 | } |
672 | } |
| 532 | |
673 | |
| 533 | ## See std_num_cmp_list for usage |
674 | ## See std_num_cmp_list for usage |
| 534 | sub strict_num_cmp_list { # compare numbers |
675 | sub strict_num_cmp_list { # compare numbers |
| 535 | my ( $relPercentTol, $format, @answerList ) = @_; |
676 | my ( $relPercentTol, $format, @answerList ) = @_; |
|
|
677 | |
|
|
678 | my %options = ( 'tolerance' => $relPercentTol, |
|
|
679 | 'format' => $format, |
|
|
680 | ); |
|
|
681 | |
|
|
682 | set_default_options( \%options, |
|
|
683 | 'tolType' => 'relative', |
|
|
684 | 'tolerance' => $relPercentTol, |
|
|
685 | 'mode' => 'strict', |
|
|
686 | 'format' => $numFormatDefault, |
|
|
687 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
688 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
689 | 'relTol' => $numRelPercentTolDefault, |
|
|
690 | 'debug' => 0, |
|
|
691 | ); |
| 536 | |
692 | |
| 537 | NUM_CMP_LIST( 'tolerance' => $relPercentTol, |
693 | num_cmp(\@answerList, %options); |
| 538 | 'tolType' => 'relative', |
694 | } |
| 539 | 'format' => $format, |
695 | |
| 540 | 'mode' => 'strict', |
|
|
| 541 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
| 542 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
| 543 | 'answerList' => \@answerList |
|
|
| 544 | ); |
|
|
| 545 | } |
|
|
| 546 | |
696 | |
| 547 | sub strict_num_cmp_abs { # only allow numbers as submitted answer with absolute tolerance |
697 | sub strict_num_cmp_abs { # only allow numbers as submitted answer with absolute tolerance |
|
|
698 | |
| 548 | my ( $correctAnswer, $absTol, $format ) = @_; |
699 | my ( $correctAnswer, $absTol, $format ) = @_; |
| 549 | |
700 | |
| 550 | NUM_CMP( 'correctAnswer' => $correctAnswer, |
701 | my %options = ( 'tolerance' => $absTol, |
| 551 | 'tolerance' => $absTol, |
702 | 'format' => $format |
| 552 | 'tolType' => 'absolute', |
703 | ); |
| 553 | 'format' => $format, |
704 | |
| 554 | 'mode' => 'strict', |
705 | set_default_options (\%options, |
| 555 | 'zeroLevel' => 0, |
706 | 'tolType' => 'absolute', |
| 556 | 'zeroLevelTol' => 0 |
707 | 'tolerance' => $absTol, |
| 557 | ); |
708 | 'mode' => 'strict', |
|
|
709 | 'format' => $numFormatDefault, |
|
|
710 | 'zeroLevel' => 0, |
|
|
711 | 'zeroLevelTol' => 0, |
|
|
712 | 'debug' => 0, |
|
|
713 | ); |
|
|
714 | |
|
|
715 | num_cmp([$correctAnswer], %options); |
|
|
716 | |
| 558 | } |
717 | } |
| 559 | |
718 | |
| 560 | ## See std_num_cmp_list for usage |
719 | ## See std_num_cmp_list for usage |
| 561 | sub strict_num_cmp_abs_list { # compare numbers |
720 | sub strict_num_cmp_abs_list { # compare numbers |
| 562 | my ( $absTol, $format, @answerList ) = @_; |
721 | my ( $absTol, $format, @answerList ) = @_; |
| 563 | |
722 | |
| 564 | NUM_CMP_LIST( 'tolerance' => $absTol, |
723 | |
| 565 | 'tolType' => 'absolute', |
724 | my %options = ( 'tolerance' => $absTol, |
| 566 | 'format' => $format, |
725 | 'format' => $format |
| 567 | 'mode' => 'strict', |
726 | ); |
| 568 | 'zeroLevel' => 0, |
727 | |
| 569 | 'zeroLevelTol' => 0, |
728 | set_default_options (\%options, |
| 570 | 'answerList' => \@answerList |
729 | 'tolType' => 'absolute', |
| 571 | ); |
730 | 'tolerance' => $absTol, |
|
|
731 | 'mode' => 'strict', |
|
|
732 | 'format' => $numFormatDefault, |
|
|
733 | 'zeroLevel' => 0, |
|
|
734 | 'zeroLevelTol' => 0, |
|
|
735 | 'debug' => 0, |
|
|
736 | ); |
|
|
737 | |
|
|
738 | num_cmp(\@answerList, %options); |
|
|
739 | |
|
|
740 | |
|
|
741 | |
| 572 | } |
742 | } |
| 573 | |
743 | |
| 574 | |
744 | |
| 575 | ## Compares a number with units |
745 | ## Compares a number with units |
| 576 | ## Deprecated; use num_cmp() |
746 | ## Deprecated; use num_cmp() |
| … | |
… | |
| 581 | ## format -- the format to use when displaying the answer |
751 | ## format -- the format to use when displaying the answer |
| 582 | ## tol -- an absolute tolerance, or |
752 | ## tol -- an absolute tolerance, or |
| 583 | ## relTol -- a relative tolerance |
753 | ## relTol -- a relative tolerance |
| 584 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
754 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
| 585 | ## zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero |
755 | ## zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero |
|
|
756 | |
|
|
757 | |
|
|
758 | sub check_strings { |
|
|
759 | my ($rh_ans, %options) = @_; |
|
|
760 | |
|
|
761 | # if the student's answer is a number, simply return the answer hash (unchanged). |
|
|
762 | |
|
|
763 | if ( $rh_ans->{student_ans} =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/) { |
|
|
764 | if ( $rh_ans->{answerIsString} == 1) { |
|
|
765 | $rh_ans->throw_error('STRING','Incorrect Answer'); # student's answer is a number |
|
|
766 | } |
|
|
767 | return $rh_ans; |
|
|
768 | } |
|
|
769 | # the student's answer is recognized as a string |
|
|
770 | my $ans = $rh_ans->{student_ans}; |
|
|
771 | |
|
|
772 | # OVERVIEW of remindar of function: |
|
|
773 | # if answer is correct, return correct. (adjust score to 1) |
|
|
774 | # if answer is incorect: |
|
|
775 | # 1) determine if the answer is sensible. if it is, return incorrect. |
|
|
776 | # 2) if the answer is not sensible (and incorrect), then return an error message indicating so. |
|
|
777 | # no matter what: throw a 'STRING' error to skip numerical evaluations. (error flag skips remainder of pre_filters and evaluators) |
|
|
778 | # last: 'STRING' post_filter will clear the error (avoiding pink screen.) |
|
|
779 | |
|
|
780 | my $sensibleAnswer = 0; |
|
|
781 | $ans = str_filters( $ans, 'compress_whitespace' ); # remove trailing, leading, and double spaces. |
|
|
782 | my ($ans_eval) = str_cmp($rh_ans->{correct_ans}); |
|
|
783 | my $temp_ans_hash = &$ans_eval($ans); |
|
|
784 | $rh_ans->{test} = $temp_ans_hash; |
|
|
785 | if ($temp_ans_hash->{score} ==1 ) { # students answer matches the correct answer. |
|
|
786 | $rh_ans->{score} = 1; |
|
|
787 | $sensibleAnswer = 1; |
|
|
788 | } else { # students answer does not match the correct answer. |
|
|
789 | ## find out if string makes sense |
|
|
790 | my $legalString = ''; |
|
|
791 | my @legalStrings = @{$options{strings}}; |
|
|
792 | foreach $legalString (@legalStrings) { |
|
|
793 | if ( uc($ans) eq uc($legalString) ) { |
|
|
794 | $sensibleAnswer = 1; |
|
|
795 | last; |
|
|
796 | } |
|
|
797 | } |
|
|
798 | $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible |
|
|
799 | $rh_ans->throw_error('EVAL', "$BR Your answer is not a recognized answer") unless ($sensibleAnswer); |
|
|
800 | # $temp_ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' ) unless ($sensibleAnswer); |
|
|
801 | # $temp_ans_hash -> setKeys( 'student_ans' => uc($ans) ); |
|
|
802 | } |
|
|
803 | $rh_ans->{student_ans} = $ans; |
|
|
804 | if ($sensibleAnswer) { |
|
|
805 | $rh_ans->throw_error('STRING', "The student's answer $rh_ans->{student_ans} is interpreted as a string."); |
|
|
806 | } |
|
|
807 | # warn ("\$rh_ans->{answerIsString} = $rh_ans->{answerIsString}"); |
|
|
808 | |
|
|
809 | $rh_ans; |
|
|
810 | |
|
|
811 | } |
|
|
812 | |
|
|
813 | |
|
|
814 | |
|
|
815 | sub check_units { |
|
|
816 | my ($rh_ans, %options) = @_; |
|
|
817 | |
|
|
818 | my %correct_units = %{$rh_ans-> {rh_correct_units}}; |
|
|
819 | |
|
|
820 | my $ans = $rh_ans->{student_ans}; |
|
|
821 | # $ans = '' unless defined ($ans); |
|
|
822 | $ans = str_filters ($ans, 'trim_whitespace'); |
|
|
823 | my $original_student_ans = $ans; |
|
|
824 | |
|
|
825 | $rh_ans->{original_student_ans} = $original_student_ans; |
|
|
826 | |
|
|
827 | # it surprises me that the match below works since the first .* is greedy. |
|
|
828 | my ($num_answer, $units) = $ans =~ /^(.*)\s+([^\s]*)$/; |
|
|
829 | |
|
|
830 | unless ( defined($num_answer) && $units ) { |
|
|
831 | # there is an error reading the input |
|
|
832 | if ( $ans =~ /\S/ ) { # the answer is not blank |
|
|
833 | $rh_ans -> setKeys( 'ans_message' => "The answer \"$ans\" could not be interpreted " . |
|
|
834 | "as a number or an arithmetic expression followed by a unit specification. " . |
|
|
835 | "Your answer must contain units." ); |
|
|
836 | $rh_ans->throw_error('UNITS', "The answer \"$ans\" could not be interpreted " . |
|
|
837 | "as a number or an arithmetic expression followed by a unit specification. " . |
|
|
838 | "Your answer must contain units." ); |
|
|
839 | } |
|
|
840 | |
|
|
841 | return $rh_ans; |
|
|
842 | } |
|
|
843 | |
|
|
844 | # we have been able to parse the answer into a numerical part and a unit part |
|
|
845 | |
|
|
846 | # $num_answer = $1; #$1 and $2 from the regular expression above |
|
|
847 | # $units = $2; |
|
|
848 | |
|
|
849 | my %units = Units::evaluate_units($units); |
|
|
850 | if ( defined( $units{'ERROR'} ) ) { |
|
|
851 | # handle error condition |
|
|
852 | $units{'ERROR'} = clean_up_error_msg($units{'ERROR'}); |
|
|
853 | $rh_ans -> setKeys( 'ans_message' => "$units{'ERROR'}" ); |
|
|
854 | return $rh_ans; |
|
|
855 | } |
|
|
856 | |
|
|
857 | my $units_match = 1; |
|
|
858 | my $fund_unit; |
|
|
859 | foreach $fund_unit (keys %correct_units) { |
|
|
860 | next if $fund_unit eq 'factor'; |
|
|
861 | $units_match = 0 unless $correct_units{$fund_unit} == $units{$fund_unit}; |
|
|
862 | } |
|
|
863 | |
|
|
864 | if ( $units_match ) { |
|
|
865 | # units are ok. Evaluate the numerical part of the answer |
|
|
866 | $rh_ans->{'tolerance'} = $rh_ans->{'tolerance'}* $correct_units{'factor'}/$units{'factor'} if |
|
|
867 | $rh_ans->{'tolType'} eq 'absolute'; # the tolerance is in the units specified by the instructor. |
|
|
868 | $rh_ans->{correct_ans} = prfmt($rh_ans->{correct_ans}*$correct_units{'factor'}/$units{'factor'}); |
|
|
869 | $rh_ans->{student_ans} = $num_answer; |
|
|
870 | |
|
|
871 | } else { |
|
|
872 | $rh_ans -> setKeys( ans_message => 'There is an error in the units for this answer.' ); |
|
|
873 | $rh_ans -> throw_error ( 'UNITS', 'There is an error in the units for this answer.' ); |
|
|
874 | } |
|
|
875 | |
|
|
876 | return $rh_ans; |
|
|
877 | } |
|
|
878 | |
|
|
879 | |
|
|
880 | # This mode is depricated. send input through num_cmp -- it can handle units. |
| 586 | sub numerical_compare_with_units { |
881 | sub numerical_compare_with_units { |
| 587 | my $correct_answer = shift; # the answer is a string which includes both the numerical answer and the units. |
882 | my $correct_answer = shift; # the answer is a string which includes both the numerical answer and the units. |
| 588 | my %options = @_; # all of the other inputs are (key value) pairs |
883 | my %options = @_; # all of the other inputs are (key value) pairs |
| 589 | |
|
|
| 590 | # handle the defaults |
|
|
| 591 | $options{'mode'} = 'std' unless defined( $options{'mode'} ); |
|
|
| 592 | $options{'format'} = $numFormatDefault unless defined( $options{'format'} ); |
|
|
| 593 | $options{'zeroLevel'} = $numZeroLevelDefault unless defined( $options{'zeroLevel'} ); |
|
|
| 594 | $options{'zeroLevelTol'} = $numZeroLevelTolDefault unless defined( $options{'zeroLevelTol'} ); |
|
|
| 595 | |
|
|
| 596 | # both spellings are maintained for backward compatibility |
|
|
| 597 | # relTol is preferred |
|
|
| 598 | if( defined $options{'reltol'} ) { |
|
|
| 599 | $options{'relTol'} = $options{'reltol'}; |
|
|
| 600 | delete $options{'reltol'}; |
|
|
| 601 | } |
|
|
| 602 | |
|
|
| 603 | my ($tol, $tolerance_mode); |
|
|
| 604 | if ( defined $options{'tol'} ) { |
|
|
| 605 | $tol = $options{'tol'}; |
|
|
| 606 | $tolerance_mode = 'absolute'; |
|
|
| 607 | } |
|
|
| 608 | elsif( defined $options{'relTol'} ) { |
|
|
| 609 | $tol = $options{'relTol'}; |
|
|
| 610 | $tolerance_mode = 'relative'; |
|
|
| 611 | } |
|
|
| 612 | else { #the default is a relative tolerance |
|
|
| 613 | $tol = $numRelPercentTolDefault; |
|
|
| 614 | $tolerance_mode = 'relative'; |
|
|
| 615 | } |
|
|
| 616 | |
884 | |
| 617 | # Prepare the correct answer |
885 | # Prepare the correct answer |
| 618 | $correct_answer = str_filters( $correct_answer, 'trim_whitespace' ); |
886 | $correct_answer = str_filters( $correct_answer, 'trim_whitespace' ); |
| 619 | |
887 | |
| 620 | # it surprises me that the match below works since the first .* is greedy. |
888 | # it surprises me that the match below works since the first .* is greedy. |
| 621 | my ($correct_num_answer, $correct_units) = $correct_answer =~ /^(.*)\s+([^\s]*)$/; |
889 | my ($correct_num_answer, $correct_units) = $correct_answer =~ /^(.*)\s+([^\s]*)$/; |
| 622 | |
890 | |
| 623 | my %correct_units = Units::evaluate_units($correct_units); |
891 | $options{units} = $correct_units; |
| 624 | if ( defined( $correct_units{'ERROR'} ) ) { |
892 | |
| 625 | die "ERROR: The answer \"$correct_answer\" in the problem definition cannot be parsed:\n" . |
893 | |
| 626 | "$correct_units{'ERROR'}\n"; |
894 | num_cmp($correct_num_answer, %options); |
| 627 | } |
|
|
| 628 | |
|
|
| 629 | my $ans_evaluator = sub { |
|
|
| 630 | |
|
|
| 631 | my $ans = shift; |
|
|
| 632 | $ans = '' unless defined($ans); |
|
|
| 633 | my $original_student_ans = $ans; |
|
|
| 634 | |
|
|
| 635 | $ans = str_filters( $ans, 'trim_whitespace' ); |
|
|
| 636 | |
|
|
| 637 | my $ans_hash = new AnswerHash( |
|
|
| 638 | 'score' => 0, |
|
|
| 639 | 'correct_ans' => spf($correct_num_answer,$options{'format'}) . " $correct_units", |
|
|
| 640 | 'student_ans' => $ans, |
|
|
| 641 | 'ans_message' => '', |
|
|
| 642 | 'type' => 'num_cmp_with_units', |
|
|
| 643 | 'preview_text_string' => '', |
|
|
| 644 | 'original_student_ans' => $original_student_ans |
|
|
| 645 | ); |
|
|
| 646 | |
|
|
| 647 | # it surprises me that the match below works since the first .* is greedy. |
|
|
| 648 | my ($num_answer, $units) = $ans =~ /^(.*)\s+([^\s]*)$/; |
|
|
| 649 | |
|
|
| 650 | unless ( defined($num_answer) && $units ) { |
|
|
| 651 | # there is an error reading the input |
|
|
| 652 | if ( $ans =~ /\S/ ) { # the answer is not blank |
|
|
| 653 | $ans_hash -> setKeys( 'ans_message' => "The answer \"$ans\" could not be interpreted " . |
|
|
| 654 | "as a number or an arithmetic expression followed by a unit specification. " . |
|
|
| 655 | "Your answer must contain units." ); |
|
|
| 656 | } |
|
|
| 657 | |
|
|
| 658 | return $ans_hash; |
|
|
| 659 | } |
|
|
| 660 | |
|
|
| 661 | # we have been able to parse the answer into a numerical part and a unit part |
|
|
| 662 | |
|
|
| 663 | $num_answer = $1; #$1 and $2 from the regular expression above |
|
|
| 664 | $units = $2; |
|
|
| 665 | |
|
|
| 666 | my %units = Units::evaluate_units($units); |
|
|
| 667 | if ( defined( $units{'ERROR'} ) ) { |
|
|
| 668 | # handle error condition |
|
|
| 669 | $units{'ERROR'} = clean_up_error_msg($units{'ERROR'}); |
|
|
| 670 | |
|
|
| 671 | $ans_hash -> setKeys( 'ans_message' => "$units{'ERROR'}" ); |
|
|
| 672 | |
|
|
| 673 | return $ans_hash; |
|
|
| 674 | } |
|
|
| 675 | |
|
|
| 676 | my $units_match = 1; |
|
|
| 677 | my $fund_unit; |
|
|
| 678 | foreach $fund_unit (keys %correct_units) { |
|
|
| 679 | next if $fund_unit eq 'factor'; |
|
|
| 680 | $units_match = 0 unless $correct_units{$fund_unit} == $units{$fund_unit}; |
|
|
| 681 | } |
|
|
| 682 | |
|
|
| 683 | if ( $units_match ) { |
|
|
| 684 | |
|
|
| 685 | # units are ok. Evaluate the numerical part of the answer |
|
|
| 686 | $tol = $tol * $correct_units{'factor'}/$units{'factor'} if |
|
|
| 687 | $tolerance_mode eq 'absolute'; # the tolerance is in the units specified by the instructor. |
|
|
| 688 | |
|
|
| 689 | my $numerical_answer_evaluator = NUM_CMP( 'correctAnswer' => $correct_num_answer*$correct_units{'factor'}/$units{'factor'}, |
|
|
| 690 | 'tolerance' => $tol, |
|
|
| 691 | 'tolType' => $tolerance_mode, |
|
|
| 692 | 'format' => $options{'format'}, |
|
|
| 693 | 'mode' => $options{'mode'}, |
|
|
| 694 | 'zeroLevel' => $options{'zeroLevel'}, |
|
|
| 695 | 'zeroLevelTol' => $options{'zeroLevelTol'} ); |
|
|
| 696 | |
|
|
| 697 | # because num_answer may contain an arithmetic expression rather than |
|
|
| 698 | # a number we can't multiply it by the $units{'factor'} |
|
|
| 699 | # instead we divide the correct answer by this amount; |
|
|
| 700 | # this is also why the numerical_answer_evaluator is not defined outside this subroutine. |
|
|
| 701 | |
|
|
| 702 | $ans_hash = &$numerical_answer_evaluator($num_answer); |
|
|
| 703 | |
|
|
| 704 | # now we need to doctor the correct answer in order to add units |
|
|
| 705 | # to it and correct for the division we did before |
|
|
| 706 | $ans_hash -> {correct_ans} = |
|
|
| 707 | prfmt( ( $ans_hash->{'correct_ans'} )*$units{'factor'}/$correct_units{'factor'}, |
|
|
| 708 | $options{'format'} ) . " $correct_units"; |
|
|
| 709 | # we also need to doctor the submitted answer to get it back in its original format. |
|
|
| 710 | |
|
|
| 711 | # we don't add the units on if there is an error message from numerical_answer_evaluator |
|
|
| 712 | if ( ( $ans_hash -> {ans_message} ) =~ /^\s*$/ ) { |
|
|
| 713 | $ans_hash -> {student_ans} = $ans_hash -> {student_ans} . " $units"; |
|
|
| 714 | $ans_hash -> setKeys( original_student_ans => $ans ); |
|
|
| 715 | } |
|
|
| 716 | else { |
|
|
| 717 | # error message from numerical_answer_evaluator doesn't have units tacked on |
|
|
| 718 | $ans_hash -> setKeys( original_student_ans => $ans ); |
|
|
| 719 | } |
|
|
| 720 | } |
|
|
| 721 | else { |
|
|
| 722 | $ans_hash -> setKeys( ans_message => 'There is an error in the units for this answer.' ); |
|
|
| 723 | } |
|
|
| 724 | |
|
|
| 725 | return $ans_hash; |
|
|
| 726 | }; |
|
|
| 727 | |
|
|
| 728 | $ans_evaluator; |
|
|
| 729 | } |
895 | } |
|
|
896 | |
| 730 | |
897 | |
| 731 | =head3 std_num_str_cmp() |
898 | =head3 std_num_str_cmp() |
| 732 | |
899 | |
| 733 | NOTE: This function is maintained for compatibility. num_cmp() with the |
900 | NOTE: This function is maintained for compatibility. num_cmp() with the |
| 734 | 'strings' parameter is slightly preferred. |
901 | 'strings' parameter is slightly preferred. |
| 735 | |
902 | |
| 736 | std_num_str_cmp() is used when the correct answer could be either a number or a |
903 | std_num_str_cmp() is used when the correct answer could be either a number or a |
| 737 | string. For example, if you wanted the student to evaluate a function at number |
904 | string. For example, if you wanted the student to evaluate a function at number |
| … | |
… | |
| 758 | Example: |
925 | Example: |
| 759 | ANS( std_num_str_cmp( $ans, ["Inf", "Minf", "NaN"] ) ); |
926 | ANS( std_num_str_cmp( $ans, ["Inf", "Minf", "NaN"] ) ); |
| 760 | |
927 | |
| 761 | =cut |
928 | =cut |
| 762 | |
929 | |
| 763 | sub std_num_str_cmp { |
930 | sub std_num_str_cmp { |
| 764 | my ( $correctAnswer, $ra_legalStrings, $relpercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
931 | my ( $correctAnswer, $ra_legalStrings, $relpercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
|
|
932 | # warn ('This method is depreciated. Use num_cmp instead.'); |
|
|
933 | return num_cmp ($correctAnswer, strings=>$ra_legalStrings, relTol=>$relpercentTol, format=>$format, |
|
|
934 | zeroLevel=>$zeroLevel, zeroLevelTol=>$zeroLevelTol); |
|
|
935 | } |
| 765 | |
936 | |
|
|
937 | #sub old_std_num_str_cmp { |
|
|
938 | # my ( $correctAnswer, $ra_legalStrings, $relpercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
|
|
939 | # |
| 766 | $ra_legalStrings = [''] unless defined $ra_legalStrings; |
940 | # $ra_legalStrings = [''] unless defined $ra_legalStrings; |
| 767 | my @legalStrings = @{$ra_legalStrings}; |
941 | # my @legalStrings = @{$ra_legalStrings}; |
| 768 | |
942 | # |
| 769 | my $ans_evaluator = sub { |
943 | # my $ans_evaluator = sub { |
| 770 | |
944 | # |
| 771 | my $ans = shift; |
945 | # my $ans = shift; |
| 772 | my $ans_hash; |
946 | # my $ans_hash; |
| 773 | my $corrAnswerIsString = 0; |
947 | # my $corrAnswerIsString = 0; |
| 774 | # my $studAnswerIsString = 0; ## uses new incorrect logic |
948 | ## my $studAnswerIsString = 0; ## uses new incorrect logic |
| 775 | my $studAnswerIsString = 1; |
949 | # my $studAnswerIsString = 1; |
| 776 | |
950 | # |
| 777 | my $legalString = ''; |
951 | # my $legalString = ''; |
| 778 | foreach $legalString (@legalStrings) { |
952 | # foreach $legalString (@legalStrings) { |
| 779 | if ( uc($correctAnswer) eq uc($legalString) ) { |
953 | # if ( uc($correctAnswer) eq uc($legalString) ) { |
| 780 | $corrAnswerIsString = 1; |
954 | # $corrAnswerIsString = 1; |
| 781 | last; |
955 | # last; |
| 782 | } |
956 | # } |
| 783 | } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric |
957 | # } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric |
| 784 | |
958 | # |
| 785 | # Neither of these is perfect; the first is more general, but |
959 | # # Neither of these is perfect; the first is more general, but |
| 786 | # has problems with certain special strings like "ee", while the |
960 | # # has problems with certain special strings like "ee", while the |
| 787 | # second doesn't support arithmetic expressions. |
961 | # # second doesn't support arithmetic expressions. |
| 788 | # |
962 | # # |
| 789 | # if( $ans !~ m/^\s*([\+\-\*\/\^\(\)\[\]\{\}\s\d\.Ee]*|e|pi)\s*$/ ) { |
963 | ## if( $ans !~ m/^\s*([\+\-\*\/\^\(\)\[\]\{\}\s\d\.Ee]*|e|pi)\s*$/ ) { |
| 790 | # $studAnswerIsString = 1; |
964 | ## $studAnswerIsString = 1; |
|
|
965 | ## } |
|
|
966 | # #if( $ans !~ m/^\s*([\d+\-*\/^()]|e|pi)\s*$/ ) { |
|
|
967 | # # $studAnswerIsString = 1; |
|
|
968 | # #} |
|
|
969 | # |
|
|
970 | # ## Both the above new versions are incorrect. We replace this by the original logic namely that |
|
|
971 | # ## an answer that contain any of the symbols |
|
|
972 | # ## a digit(0-9), +, -, *, /, ^, (, ), {, }, [, ] |
|
|
973 | # ## or an answer that consists of "pi" or "e" alone |
|
|
974 | # ## will be considered an arithmetic expression rather than a string answer. |
|
|
975 | # |
|
|
976 | # if ($ans =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/) {$studAnswerIsString = 0;} |
|
|
977 | # |
|
|
978 | # |
|
|
979 | # ## at this point $studAnswerIsString = 0 iff correct answer is numeric |
|
|
980 | # |
|
|
981 | # if( $studAnswerIsString ) { |
|
|
982 | # $ans = str_filters( $ans, 'compress_whitespace' ) |
| 791 | # } |
983 | # } |
| 792 | #if( $ans !~ m/^\s*([\d+\-*\/^()]|e|pi)\s*$/ ) { |
984 | # |
| 793 | # $studAnswerIsString = 1; |
985 | # |
| 794 | #} |
986 | # |
| 795 | |
987 | # |
| 796 | ## Both the above new versions are incorrect. We replace this by the original logic namely that |
|
|
| 797 | ## an answer that contain any of the symbols |
|
|
| 798 | ## a digit(0-9), +, -, *, /, ^, (, ), {, }, [, ] |
|
|
| 799 | ## or an answer that consists of "pi" or "e" alone |
|
|
| 800 | ## will be considered an arithmetic expression rather than a string answer. |
|
|
| 801 | |
|
|
| 802 | if ($ans =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/) {$studAnswerIsString = 0;} |
|
|
| 803 | |
|
|
| 804 | |
|
|
| 805 | ## at this point $studAnswerIsString = 0 iff correct answer is numeric |
|
|
| 806 | |
|
|
| 807 | if( $studAnswerIsString ) { |
|
|
| 808 | $ans = str_filters( $ans, 'compress_whitespace' ) |
|
|
| 809 | } |
|
|
| 810 | |
|
|
| 811 | if ( ($corrAnswerIsString == 1) and ($studAnswerIsString == 1) ) { |
988 | # if ( ($corrAnswerIsString == 1) and ($studAnswerIsString == 1) ) { |
| 812 | my $string_answer_evaluator = std_str_cmp( $correctAnswer ); |
989 | # my $string_answer_evaluator = std_str_cmp( $correctAnswer ); |
| 813 | $ans_hash = &$string_answer_evaluator( $ans ); |
990 | # $ans_hash = &$string_answer_evaluator( $ans ); |
| 814 | |
991 | # |
| 815 | if( ($ans_hash -> {score}) != 1 ) { ## find out if string makes sense |
992 | # if( ($ans_hash -> {score}) != 1 ) { ## find out if string makes sense |
| 816 | my $sensibleAnswer = 0; |
993 | # my $sensibleAnswer = 0; |
| 817 | foreach $legalString (@legalStrings) { |
994 | # foreach $legalString (@legalStrings) { |
| 818 | if ( uc($ans) eq uc($legalString) ) { |
995 | # if ( uc($ans) eq uc($legalString) ) { |
| 819 | $sensibleAnswer = 1; |
996 | # $sensibleAnswer = 1; |
| 820 | last; |
997 | # last; |
| 821 | } |
998 | # } |
| 822 | } |
999 | # } |
| 823 | $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible |
1000 | # $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible |
| 824 | |
1001 | # |
| 825 | $ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' ) |
1002 | # $ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' ) |
| 826 | unless ($sensibleAnswer); |
1003 | # unless ($sensibleAnswer); |
| 827 | $ans_hash -> setKeys( 'student_ans' => uc($ans) ); |
1004 | # $ans_hash -> setKeys( 'student_ans' => uc($ans) ); |
| 828 | } |
1005 | # } |
| 829 | } |
1006 | # } |
| 830 | elsif ( ($corrAnswerIsString == 0) and ($studAnswerIsString == 0) ) { |
1007 | # elsif ( ($corrAnswerIsString == 0) and ($studAnswerIsString == 0) ) { |
| 831 | my $numeric_answer_evaluator = std_num_cmp($correctAnswer,$relpercentTol,$format,$zeroLevel,$zeroLevelTol); |
1008 | # my $numeric_answer_evaluator = std_num_cmp($correctAnswer,$relpercentTol,$format,$zeroLevel,$zeroLevelTol); |
| 832 | $ans_hash = &$numeric_answer_evaluator($ans); |
1009 | # $ans_hash = &$numeric_answer_evaluator($ans); |
| 833 | } |
1010 | # } |
| 834 | elsif ( ($corrAnswerIsString == 1) and ($studAnswerIsString == 0) ) { |
1011 | # elsif ( ($corrAnswerIsString == 1) and ($studAnswerIsString == 0) ) { |
| 835 | my $numeric_answer_evaluator = std_num_cmp(1); |
1012 | # my $numeric_answer_evaluator = std_num_cmp(1); |
| 836 | $ans_hash = &$numeric_answer_evaluator($ans); |
1013 | # $ans_hash = &$numeric_answer_evaluator($ans); |
| 837 | $ans_hash -> setKeys( 'score' => 0, |
1014 | # $ans_hash -> setKeys( 'score' => 0, |
| 838 | 'correct_ans' => $correctAnswer |
1015 | # 'correct_ans' => $correctAnswer |
| 839 | ); |
1016 | # ); |
| 840 | } |
1017 | # } |
| 841 | elsif ( ($corrAnswerIsString == 0) and ($studAnswerIsString == 1) ) { |
1018 | # elsif ( ($corrAnswerIsString == 0) and ($studAnswerIsString == 1) ) { |
| 842 | my $string_answer_evaluator = std_str_cmp('bad'); |
1019 | # my $string_answer_evaluator = std_str_cmp('bad'); |
| 843 | $ans_hash = &$string_answer_evaluator($ans); |
1020 | # $ans_hash = &$string_answer_evaluator($ans); |
| 844 | |
1021 | # |
| 845 | $ans_hash -> setKeys( 'score' => 0, |
1022 | # $ans_hash -> setKeys( 'score' => 0, |
| 846 | 'correct_ans' => $correctAnswer |
1023 | # 'correct_ans' => $correctAnswer |
| 847 | ); |
1024 | # ); |
| 848 | |
1025 | # |
| 849 | ## find out if string makes sense |
1026 | # ## find out if string makes sense |
| 850 | my $sensibleAnswer = 0; |
1027 | # my $sensibleAnswer = 0; |
| 851 | foreach $legalString (@legalStrings) { |
1028 | # foreach $legalString (@legalStrings) { |
| 852 | if ( uc($ans) eq uc($legalString) ) { |
1029 | # if ( uc($ans) eq uc($legalString) ) { |
| 853 | $sensibleAnswer = 1; |
1030 | # $sensibleAnswer = 1; |
| 854 | last; |
1031 | # last; |
| 855 | } |
1032 | # } |
| 856 | } |
1033 | # } |
| 857 | $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible |
1034 | # $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible |
| 858 | |
1035 | # |
| 859 | $ans_hash -> setKeys( 'ans_message' => "Your answer is not a recognized answer" ) |
1036 | # $ans_hash -> setKeys( 'ans_message' => "Your answer is not a recognized answer" ) |
| 860 | unless $sensibleAnswer; |
1037 | # unless $sensibleAnswer; |
| 861 | } |
1038 | # } |
| 862 | |
1039 | # |
| 863 | return $ans_hash; |
1040 | # return $ans_hash; |
| 864 | }; |
1041 | # }; |
| 865 | |
1042 | # |
| 866 | return $ans_evaluator; |
1043 | # return $ans_evaluator; |
| 867 | } |
1044 | #} |
| 868 | |
1045 | |
| 869 | =head3 num_cmp() |
1046 | =head3 num_cmp() |
| 870 | |
1047 | |
| 871 | Compares a number or a list of numbers, using a named hash of options to set |
1048 | Compares a number or a list of numbers, using a named hash of options to set |
| 872 | parameters. This can make for more readable code than using the "mode"_num_cmp() |
1049 | parameters. This can make for more readable code than using the "mode"_num_cmp() |
| … | |
… | |
| 907 | =cut |
1084 | =cut |
| 908 | |
1085 | |
| 909 | sub num_cmp { |
1086 | sub num_cmp { |
| 910 | my $correctAnswer = shift @_; |
1087 | my $correctAnswer = shift @_; |
| 911 | my @opt = @_; |
1088 | my @opt = @_; |
|
|
1089 | my %out_options; |
| 912 | |
1090 | |
|
|
1091 | ######################################################################### |
|
|
1092 | # Retain this first check for backword compatibility. Allows input of the form |
|
|
1093 | # num_cmp($ans, 1, '%0.5f') but warns against it |
|
|
1094 | ######################################################################### |
|
|
1095 | |
| 913 | my %known_options = ( 'mode' => 'std', |
1096 | my %known_options = ( 'mode' => 'std', |
| 914 | 'format' => $numFormatDefault, |
1097 | 'format' => $numFormatDefault, |
| 915 | 'tol' => $numAbsTolDefault, |
1098 | 'tol' => $numAbsTolDefault, |
| 916 | 'relTol' => $numRelPercentTolDefault, |
1099 | 'relTol' => $numRelPercentTolDefault, |
| 917 | 'units' => undef, |
1100 | 'units' => undef, |
| 918 | 'strings' => undef, |
1101 | 'strings' => undef, |
| 919 | 'zeroLevel' => $numZeroLevelDefault, |
1102 | 'zeroLevel' => $numZeroLevelDefault, |
| 920 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
1103 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 921 | |
1104 | 'tolType' => 'relative', |
|
|
1105 | 'tolerance' => 1, |
| 922 | 'reltol' => undef, #alternate spelling |
1106 | 'reltol' => undef, #alternate spelling |
| 923 | 'unit' => undef #alternate spelling |
1107 | 'unit' => undef); #alternate spelling |
| 924 | ); |
1108 | |
| 925 | my %in_options; |
|
|
| 926 | my @output_list; |
1109 | my @output_list; |
| 927 | my %out_options; |
1110 | my( $relPercentTol, $format, $zeroLevel, $zeroLevelTol) = @opt; |
| 928 | |
1111 | |
| 929 | unless( ref($correctAnswer) eq 'ARRAY' || scalar( @opt ) == 0 || |
1112 | unless( ref($correctAnswer) eq 'ARRAY' || scalar( @opt ) == 0 || |
| 930 | ( defined($opt[0]) and exists $known_options{$opt[0]} ) ) { |
1113 | ( defined($opt[0]) and exists $known_options{$opt[0]} ) ) { |
| 931 | # unless the first parameter is a list of arrays |
1114 | # unless the first parameter is a list of arrays |
| 932 | # or the second parameter is a known option or |
1115 | # or the second parameter is a known option or |
| 933 | # no options were used, |
1116 | # no options were used, |
| 934 | # use the old num_cmp which does not use options, but has inputs |
1117 | # use the old num_cmp which does not use options, but has inputs |
| 935 | # $relPercentTol,$format,$zeroLevel,$zeroLevelTol |
1118 | # $relPercentTol,$format,$zeroLevel,$zeroLevelTol |
| 936 | warn "This method of using num_cmp() is deprecated. Please rewrite this" . |
1119 | warn "This method of using num_cmp() is deprecated. Please rewrite this" . |
| 937 | " problem using the options style of parameter passing (or" . |
1120 | " problem using the options style of parameter passing (or" . |
| 938 | " check that your first option is spelled correctly)."; |
1121 | " check that your first option is spelled correctly)."; |
| 939 | my( $relPercentTol, $format, $zeroLevel, $zeroLevelTol) = @opt; |
1122 | |
| 940 | |
1123 | |
| 941 | %out_options = ( 'relTol' => $relPercentTol, |
1124 | %out_options = ( 'relTol' => $relPercentTol, |
| 942 | 'format' => $format, |
1125 | 'format' => $format, |
| 943 | 'zeroLevel' => $zeroLevel, |
1126 | 'zeroLevel' => $zeroLevel, |
| 944 | 'zeroLevelTol' => $zeroLevelTol, |
1127 | 'zeroLevelTol' => $zeroLevelTol, |
| 945 | 'mode' => 'std' |
1128 | 'mode' => 'std' |
| 946 | ); |
1129 | ); |
| 947 | } |
1130 | } |
| 948 | else { |
1131 | # else { |
| 949 | # handle options |
1132 | # # handle options |
| 950 | |
1133 | # |
| 951 | check_option_list( @opt ); |
1134 | # |
|
|
1135 | # @opt = ( 'relTol' => $relPercentTol, |
|
|
1136 | # 'format' => $format, |
|
|
1137 | # 'zeroLevel' => $numZeroLevelDefault, |
|
|
1138 | # 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
1139 | # 'mode' => 'std' |
|
|
1140 | # ); |
|
|
1141 | # } |
|
|
1142 | ######################################################################### |
|
|
1143 | # Now handle the options assuming they are entered in the form |
|
|
1144 | # num_cmp($ans, relTol=>1, format=>'%0.5f') |
|
|
1145 | ######################################################################### |
| 952 | %in_options = @opt; |
1146 | %out_options = @opt; |
|
|
1147 | assign_option_aliases( \%out_options, |
|
|
1148 | 'reltol' => 'relTol', |
|
|
1149 | 'unit' => 'units', |
|
|
1150 | ); |
| 953 | |
1151 | |
| 954 | # both spellings maintained for compatibility |
1152 | |
| 955 | # relTol is preferred |
1153 | |
| 956 | if( defined( $in_options{'reltol'} ) ) { |
1154 | |
| 957 | $in_options{'relTol'} = $in_options{'reltol'}; |
1155 | set_default_options( \%out_options, |
| 958 | delete $in_options{'reltol'}; |
1156 | 'tolType' => (defined($out_options{tol}) ) ? 'absolute' : 'relative', |
|
|
1157 | 'tolerance' => (defined($out_options{tol}) ) ? $numAbsTolDefault : $numRelPercentTolDefault, |
|
|
1158 | 'mode' => 'std', |
|
|
1159 | 'format' => $numFormatDefault, |
|
|
1160 | 'tol' => $numAbsTolDefault, |
|
|
1161 | 'relTol' => $numRelPercentTolDefault, |
|
|
1162 | 'units' => undef, |
|
|
1163 | 'strings' => undef, |
|
|
1164 | 'zeroLevel' => $numZeroLevelDefault, |
|
|
1165 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
|
|
1166 | 'debug' => 0, |
| 959 | } |
1167 | |
|
|
1168 | ); |
| 960 | |
1169 | |
| 961 | # both spellings maintained for compatibility |
|
|
| 962 | # units is preferred |
|
|
| 963 | if( defined( $in_options{'unit'} ) ) { |
|
|
| 964 | $in_options{'units'} = $in_options{'unit'}; |
|
|
| 965 | delete $in_options{'unit'}; |
|
|
| 966 | } |
|
|
| 967 | |
1170 | |
|
|
1171 | |
|
|
1172 | |
|
|
1173 | |
|
|
1174 | |
| 968 | # can't use both units and strings |
1175 | # can't use both units and strings |
| 969 | if( defined( $in_options{'units'} ) && defined( $in_options{'strings'} ) ) { |
1176 | if( defined( $out_options{'units'} ) && defined( $out_options{'strings'} ) ) { |
| 970 | warn "Can't use both 'units' and 'strings' in the same problem " . |
1177 | warn "Can't use both 'units' and 'strings' in the same problem " . |
| 971 | "(check your parameters to num_cmp() )"; |
1178 | "(check your parameters to num_cmp() )"; |
|
|
1179 | |
| 972 | } |
1180 | } |
| 973 | |
1181 | |
| 974 | #%out_options = %known_options; |
|
|
| 975 | foreach my $opt_name (keys %in_options) { |
|
|
| 976 | |
1182 | |
| 977 | if( exists( $known_options{$opt_name} ) ) { |
|
|
| 978 | $out_options{$opt_name} = $in_options{$opt_name}; |
|
|
| 979 | } |
|
|
| 980 | else { |
|
|
| 981 | die "Option $opt_name is not defined for num_cmp. Answer is $correctAnswer; " . |
|
|
| 982 | "Default options are:<BR> ", pretty_print(\%known_options); |
|
|
| 983 | } |
|
|
| 984 | } |
|
|
| 985 | } |
|
|
| 986 | |
|
|
| 987 | # set tolerance flags -- note that the order of testing means that |
|
|
| 988 | # relative tolerance is the default |
|
|
| 989 | my ($tolType, $tol); |
1183 | # my ($tolType, $tol); |
| 990 | |
1184 | if ($out_options{tolType} eq 'absolute') { |
| 991 | if ( defined( $out_options{'tol'} ) ) { |
|
|
| 992 | $tolType = 'absolute'; |
1185 | # $tolType = 'absolute'; |
|
|
1186 | # $out_options{tolType} = 'absolute'; |
| 993 | $tol = $out_options{'tol'}; |
1187 | # $tol = $out_options{'tol'}; |
| 994 | } |
1188 | $out_options{'tolerance'}=$out_options{'tol'}; |
|
|
1189 | delete($out_options{'relTol'}) if exists( $out_options{'relTol'} ); |
| 995 | else { |
1190 | } else { |
| 996 | $tolType = 'relative'; |
1191 | # $tolType = 'relative'; |
|
|
1192 | # $out_options{tolType} = 'relative'; |
| 997 | $tol = $out_options{'relTol'}; |
1193 | # $tol = $out_options{'relTol'}; |
|
|
1194 | # $out_options{'tolType'} = $out_options{'relative'}; |
|
|
1195 | $out_options{'tolerance'}=$out_options{'relTol'}; |
|
|
1196 | # delete($out_options{'tol'}) if exists( $out_options{'tol'} ); |
| 998 | } |
1197 | } |
| 999 | |
1198 | |
| 1000 | # thread over lists |
1199 | # thread over lists |
| 1001 | my @ans_list = (); |
1200 | my @ans_list = (); |
| 1002 | |
1201 | |
| … | |
… | |
| 1004 | @ans_list = @{$correctAnswer}; |
1203 | @ans_list = @{$correctAnswer}; |
| 1005 | } |
1204 | } |
| 1006 | else { |
1205 | else { |
| 1007 | push( @ans_list, $correctAnswer ); |
1206 | push( @ans_list, $correctAnswer ); |
| 1008 | } |
1207 | } |
|
|
1208 | |
| 1009 | # produce answer evaluators |
1209 | # produce answer evaluators |
| 1010 | foreach my $ans (@ans_list) { |
1210 | foreach my $ans (@ans_list) { |
| 1011 | if( defined( $out_options{'units'} ) ) { |
1211 | if( defined( $out_options{'units'} ) ) { |
| 1012 | $ans = "$ans $out_options{'units'}"; |
1212 | $ans = "$ans $out_options{'units'}"; |
| 1013 | push( @output_list, numerical_compare_with_units($ans, %out_options) ); |
|
|
| 1014 | } |
1213 | |
|
|
1214 | push( @output_list, NUM_CMP( 'correctAnswer' => $ans, |
|
|
1215 | 'tolerance' => $out_options{tolerance}, |
|
|
1216 | 'tolType' => $out_options{tolType}, |
|
|
1217 | 'format' => $out_options{'format'}, |
|
|
1218 | 'mode' => $out_options{'mode'}, |
|
|
1219 | 'zeroLevel' => $out_options{'zeroLevel'}, |
|
|
1220 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
|
|
1221 | 'debug' => $out_options{'debug'}, |
|
|
1222 | 'units' => $out_options{'units'}, |
|
|
1223 | ) |
|
|
1224 | ); |
|
|
1225 | } |
| 1015 | elsif( defined( $out_options{'strings'} ) ) { |
1226 | elsif( defined( $out_options{'strings'} ) ) { |
| 1016 | if( defined $out_options{'tol'} ) { |
1227 | #if( defined $out_options{'tol'} ) { |
| 1017 | warn "You are using 'tol' (for absolute tolerance) with a num/str " . |
1228 | # warn "You are using 'tol' (for absolute tolerance) with a num/str " . |
| 1018 | "compare, which currently only uses relative tolerance. The default " . |
1229 | # "compare, which currently only uses relative tolerance. The default " . |
| 1019 | "tolerance will be used."; |
1230 | # "tolerance will be used."; |
|
|
1231 | #} |
|
|
1232 | |
|
|
1233 | push( @output_list, NUM_CMP( 'correctAnswer' => $ans, |
|
|
1234 | 'tolerance' => $out_options{tolerance}, |
|
|
1235 | 'tolType' => $out_options{tolType}, |
|
|
1236 | 'format' => $out_options{'format'}, |
|
|
1237 | 'mode' => $out_options{'mode'}, |
|
|
1238 | 'zeroLevel' => $out_options{'zeroLevel'}, |
|
|
1239 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
|
|
1240 | 'debug' => $out_options{'debug'}, |
|
|
1241 | 'strings' => $out_options{'strings'}, |
|
|
1242 | |
|
|
1243 | ) |
|
|
1244 | ); |
|
|
1245 | } |
|
|
1246 | else { |
| 1020 | } |
1247 | |
| 1021 | |
|
|
| 1022 | push( @output_list, std_num_str_cmp( $ans, $out_options{'strings'}, |
|
|
| 1023 | $out_options{'relTol'}, |
|
|
| 1024 | $out_options{'format'}, |
|
|
| 1025 | $out_options{'zeroLevel'}, |
|
|
| 1026 | $out_options{'zeroLevelTol'} |
|
|
| 1027 | ) |
|
|
| 1028 | ); |
|
|
| 1029 | } else { |
|
|
| 1030 | push(@output_list, |
1248 | push(@output_list, |
| 1031 | NUM_CMP( 'correctAnswer' => $ans, |
1249 | NUM_CMP( 'correctAnswer' => $ans, |
| 1032 | 'tolerance' => $tol, |
1250 | 'tolerance' => $out_options{tolerance}, |
| 1033 | 'tolType' => $tolType, |
1251 | 'tolType' => $out_options{tolType}, |
| 1034 | 'format' => $out_options{'format'}, |
1252 | 'format' => $out_options{'format'}, |
| 1035 | 'mode' => $out_options{'mode'}, |
1253 | 'mode' => $out_options{'mode'}, |
| 1036 | 'zeroLevel' => $out_options{'zeroLevel'}, |
1254 | 'zeroLevel' => $out_options{'zeroLevel'}, |
| 1037 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
1255 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
|
|
1256 | 'debug' => $out_options{'debug'}, |
|
|
1257 | |
| 1038 | ), |
1258 | ), |
| 1039 | ); |
1259 | ); |
|
|
1260 | } |
| 1040 | } |
1261 | } |
| 1041 | } |
1262 | |
| 1042 | |
|
|
| 1043 | return @output_list; |
1263 | return @output_list; |
| 1044 | } |
1264 | } |
| 1045 | |
1265 | |
| 1046 | #legacy code for compatability purposes |
1266 | #legacy code for compatability purposes |
| 1047 | sub num_rel_cmp { # compare numbers |
1267 | sub num_rel_cmp { # compare numbers |
| 1048 | std_num_cmp( @_ ); |
1268 | std_num_cmp( @_ ); |
| 1049 | } |
1269 | } |
| 1050 | |
1270 | |
| 1051 | ## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION |
1271 | ## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION |
| 1052 | ## |
1272 | ## |
| 1053 | ## IN: a hash containing the following items (error-checking to be added later?): |
1273 | ## IN: a hash containing the following items (error-checking to be added later?): |
| … | |
… | |
| 1057 | ## format -- the display format of the answer |
1277 | ## format -- the display format of the answer |
| 1058 | ## mode -- one of 'std', 'strict', 'arith', or 'frac'; |
1278 | ## mode -- one of 'std', 'strict', 'arith', or 'frac'; |
| 1059 | ## determines allowable formats for the input |
1279 | ## determines allowable formats for the input |
| 1060 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
1280 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
| 1061 | ## zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
1281 | ## zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
|
|
1282 | |
|
|
1283 | sub compare_numbers { |
|
|
1284 | my ($rh_ans, %options) = @_; |
|
|
1285 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans}); |
|
|
1286 | if ($PG_eval_errors) { |
|
|
1287 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); |
|
|
1288 | $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); |
|
|
1289 | |
|
|
1290 | |
|
|
1291 | } else { |
|
|
1292 | $rh_ans->{student_ans} = prfmt($inVal,$options{format}); |
|
|
1293 | } |
|
|
1294 | |
|
|
1295 | my $permitted_error; |
|
|
1296 | |
|
|
1297 | if ($rh_ans->{tolType} eq 'absolute') { |
|
|
1298 | $permitted_error = $rh_ans->{tolerance}; |
|
|
1299 | |
|
|
1300 | } |
|
|
1301 | elsif ( abs($rh_ans->{correct_ans}) <= $options{zeroLevel}) { |
|
|
1302 | $permitted_error = $options{zeroLevelTol}; ## want $tol to be non zero |
|
|
1303 | } |
|
|
1304 | else { |
|
|
1305 | $permitted_error = abs($rh_ans->{tolerance}*$rh_ans->{correct_ans}); |
|
|
1306 | } |
|
|
1307 | |
|
|
1308 | my $is_a_number = is_a_number($inVal); |
|
|
1309 | $rh_ans->{score} = 1 if ( ($is_a_number) and |
|
|
1310 | (abs( $inVal - $rh_ans->{correct_ans} ) <= $permitted_error) ); |
|
|
1311 | if (not $is_a_number) { |
|
|
1312 | $rh_ans->throw_error('EVAL','Your answer does not evaluate to a number'); |
|
|
1313 | } |
|
|
1314 | |
|
|
1315 | $rh_ans; |
|
|
1316 | } |
|
|
1317 | |
| 1062 | sub NUM_CMP { # low level numeric compare |
1318 | sub NUM_CMP { # low level numeric compare |
| 1063 | my %num_params = @_; |
1319 | my %num_params = @_; |
|
|
1320 | |
|
|
1321 | my @keys = qw ( correctAnswer tolerance tolType format mode zeroLevel zeroLevelTol debug ); |
|
|
1322 | foreach my $key (@keys) { |
|
|
1323 | warn "$key must be defined in options when calling NUM_CMP" unless defined ($num_params{$key}); |
|
|
1324 | } |
| 1064 | |
1325 | |
| 1065 | my $correctAnswer = $num_params{'correctAnswer'}; |
1326 | my $correctAnswer = $num_params{'correctAnswer'}; |
| 1066 | my $tol = $num_params{'tolerance'}; |
|
|
| 1067 | my $tolType = $num_params{'tolType'}; |
|
|
| 1068 | my $format = $num_params{'format'}; |
1327 | my $format = $num_params{'format'}; |
| 1069 | my $mode = $num_params{'mode'}; |
1328 | my $mode = $num_params{'mode'}; |
|
|
1329 | |
|
|
1330 | # my $tol = $num_params{'tolerance'}; |
|
|
1331 | # my $tolType = $num_params{'tolType'}; |
| 1070 | my $zeroLevel = $num_params{'zeroLevel'}; |
1332 | # my $zeroLevel = $num_params{'zeroLevel'}; |
| 1071 | my $zeroLevelTol = $num_params{'zeroLevelTol'}; |
1333 | # my $zeroLevelTol = $num_params{'zeroLevelTol'}; |
| 1072 | |
1334 | |
| 1073 | if( $tolType eq 'relative' ) { |
1335 | if( $num_params{tolType} eq 'relative' ) { |
| 1074 | $tol = $numRelPercentTolDefault unless defined $tol; |
1336 | $num_params{'tolerance'} = .01*$num_params{'tolerance'}; |
| 1075 | $tol *= .01; |
1337 | } |
|
|
1338 | |
|
|
1339 | #$format = $numFormatDefault unless defined $format; |
|
|
1340 | #$mode = 'std' unless defined $mode; |
|
|
1341 | #$zeroLevel = $numZeroLevelDefault unless defined $zeroLevel; |
|
|
1342 | #$zeroLevelTol = $numZeroLevelTolDefault unless defined $zeroLevelTol; |
|
|
1343 | |
|
|
1344 | my $formattedCorrectAnswer; |
|
|
1345 | my $correct_units; |
|
|
1346 | my $correct_num_answer; |
|
|
1347 | my %correct_units; |
|
|
1348 | my $corrAnswerIsString = 0; |
|
|
1349 | |
|
|
1350 | |
|
|
1351 | if (defined($num_params{units}) && $num_params{units}) { |
|
|
1352 | $correctAnswer = str_filters( $correctAnswer, 'trim_whitespace' ); |
|
|
1353 | # units are in form stuff space units where units contains no spaces. |
|
|
1354 | |
|
|
1355 | ($correct_num_answer, $correct_units) = $correctAnswer =~ /^(.*)\s+([^\s]*)$/; |
|
|
1356 | %correct_units = Units::evaluate_units($correct_units); |
|
|
1357 | if ( defined( $correct_units{'ERROR'} ) ) { |
|
|
1358 | warn ("ERROR: The answer \"$correctAnswer\" in the problem definition cannot be parsed:\n" . |
|
|
1359 | "$correct_units{'ERROR'}\n"); |
|
|
1360 | } |
|
|
1361 | # $formattedCorrectAnswer = spf($correct_num_answer,$num_params{'format'}) . " $correct_units"; |
|
|
1362 | $formattedCorrectAnswer = prfmt($correct_num_answer,$num_params{'format'}) . " $correct_units"; |
|
|
1363 | |
|
|
1364 | } elsif (defined($num_params{strings}) && $num_params{strings}) { |
|
|
1365 | |
|
|
1366 | my $legalString = ''; |
|
|
1367 | my @legalStrings = @{$num_params{strings}}; |
|
|
1368 | $correct_num_answer = $correctAnswer; |
|
|
1369 | $formattedCorrectAnswer = $correctAnswer; |
|
|
1370 | foreach $legalString (@legalStrings) { |
|
|
1371 | if ( uc($correctAnswer) eq uc($legalString) ) { |
|
|
1372 | $corrAnswerIsString = 1; |
|
|
1373 | last; |
|
|
1374 | } |
|
|
1375 | } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric |
|
|
1376 | |
| 1076 | |
1377 | |
| 1077 | } else { |
1378 | } else { |
| 1078 | $tol = $numAbsTolDefault unless defined $tol; |
1379 | $correct_num_answer = $correctAnswer; |
| 1079 | } |
|
|
| 1080 | $format = $numFormatDefault unless defined $format; |
|
|
| 1081 | $mode = 'std' unless defined $mode; |
|
|
| 1082 | $zeroLevel = $numZeroLevelDefault unless defined $zeroLevel; |
|
|
| 1083 | $zeroLevelTol = $numZeroLevelTolDefault unless defined $zeroLevelTol; |
|
|
| 1084 | |
|
|
| 1085 | my $formattedCorrectAnswer = prfmt( $correctAnswer, $format ); |
1380 | $formattedCorrectAnswer = prfmt( $correctAnswer, $num_params{'format'} ); |
|
|
1381 | } |
| 1086 | |
1382 | |
| 1087 | my $answer_evaluator = sub { |
1383 | $correct_num_answer = math_constants($correct_num_answer); |
| 1088 | my $in = shift @_; |
1384 | |
| 1089 | $in = '' unless defined $in; |
1385 | my $PGanswerMessage = ''; |
| 1090 | my $score = 0; |
1386 | |
| 1091 | my $original_student_answer = $in; |
1387 | my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report); |
| 1092 | my $parser = new AlgParserWithImplicitExpand; |
1388 | |
| 1093 | my $ret = $parser -> parse($in); |
1389 | if (defined($correct_num_answer) && $correct_num_answer =~ /\S/ && $corrAnswerIsString == 0 ) { |
| 1094 | my $preview_text_string = ''; |
1390 | ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correct_num_answer); |
| 1095 | my $preview_latex_string = ''; |
1391 | } |
|
|
1392 | else { |
|
|
1393 | $PG_eval_errors = ' '; |
|
|
1394 | } |
| 1096 | |
1395 | |
| 1097 | if ( ref($ret) ) { ## parsed successfully |
1396 | if ( ($PG_eval_errors && $corrAnswerIsString == 0) or ((not is_a_number($correctVal)) && $corrAnswerIsString == 0)) { |
| 1098 | $parser -> tostring(); |
1397 | ##error message from eval or above |
| 1099 | $parser -> normalize(); |
1398 | warn "Error in 'correct' answer: $PG_eval_errors<br> |
| 1100 | $in = $parser -> tostring(); |
1399 | The answer $correctAnswer evaluates to $correctVal, |
| 1101 | $preview_text_string = $in; |
1400 | which cannot be interpreted as a number. "; |
| 1102 | $preview_latex_string = $parser -> tolatex(); |
1401 | |
|
|
1402 | } |
|
|
1403 | ######################################################################### |
| 1103 | |
1404 | |
|
|
1405 | #construct the answer evaluator |
|
|
1406 | my $answer_evaluator = new AnswerEvaluator; |
|
|
1407 | $answer_evaluator->{debug} = $num_params{debug}; |
|
|
1408 | $answer_evaluator->ans_hash( correct_ans => $correct_num_answer, |
|
|
1409 | type => "${mode}_number", |
|
|
1410 | tolerance => $num_params{tolerance}, |
|
|
1411 | tolType => $num_params{tolType}, |
|
|
1412 | units => $correct_units, |
|
|
1413 | original_correct_ans => $formattedCorrectAnswer, |
|
|
1414 | rh_correct_units => \%correct_units, |
|
|
1415 | answerIsString => $corrAnswerIsString, |
|
|
1416 | ); |
|
|
1417 | my ($in, $formattedSubmittedAnswer); |
|
|
1418 | $answer_evaluator->install_pre_filter(sub {my $rh_ans = shift; |
|
|
1419 | $rh_ans->{original_student_ans} = $rh_ans->{student_ans}; $rh_ans;} |
|
|
1420 | ); |
|
|
1421 | if (defined($num_params{units}) && $num_params{units}) { |
|
|
1422 | $answer_evaluator->install_pre_filter(\&check_units); |
|
|
1423 | } |
|
|
1424 | if (defined($num_params{strings}) && $num_params{strings}) { |
|
|
1425 | $answer_evaluator->install_pre_filter(\&check_strings, %num_params); |
|
|
1426 | } |
|
|
1427 | |
|
|
1428 | |
|
|
1429 | $answer_evaluator->install_pre_filter(\&check_syntax); |
|
|
1430 | |
|
|
1431 | $answer_evaluator->install_pre_filter(\&math_constants); |
|
|
1432 | |
|
|
1433 | |
|
|
1434 | |
|
|
1435 | if ($mode eq 'std') { |
|
|
1436 | # do nothing |
|
|
1437 | } elsif ($mode eq 'strict') { |
|
|
1438 | $answer_evaluator->install_pre_filter(\&is_a_number); |
|
|
1439 | } elsif ($mode eq 'arith') { |
|
|
1440 | $answer_evaluator->install_pre_filter(\&is_an_arithmetic_expression); |
|
|
1441 | } elsif ($mode eq 'frac') { |
|
|
1442 | $answer_evaluator->install_pre_filter(\&is_a_fraction); |
|
|
1443 | |
|
|
1444 | } else { |
|
|
1445 | $PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.'; |
|
|
1446 | $formattedSubmittedAnswer = $in; |
| 1104 | } |
1447 | } |
| 1105 | else { ## error in parsing |
1448 | |
| 1106 | my $ans_hash = new AnswerHash( |
1449 | if ($corrAnswerIsString == 0 ){ # avoiding running compare_numbers when correct answer is a string. |
| 1107 | 'score' => $score, |
1450 | $answer_evaluator->install_evaluator(\&compare_numbers, %num_params); |
| 1108 | 'correct_ans' => $formattedCorrectAnswer, |
|
|
| 1109 | 'student_ans' => "error: $parser->{htmlerror}", |
|
|
| 1110 | 'ans_message' => $parser -> {error_msg}, |
|
|
| 1111 | 'type' => "${mode}_number", |
|
|
| 1112 | 'preview_text_string' => $preview_text_string, |
|
|
| 1113 | 'preview_latex_string' => $preview_latex_string, |
|
|
| 1114 | 'original_student_ans' => $original_student_answer |
|
|
| 1115 | ); |
|
|
| 1116 | |
|
|
| 1117 | return $ans_hash; |
|
|
| 1118 | } |
1451 | } |
| 1119 | |
1452 | |
| 1120 | my $PGanswerMessage = ''; |
1453 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; |
| 1121 | |
|
|
| 1122 | my ($inVal,$correctVal,$PG_eval_errors,$PG_full_error_report); |
|
|
| 1123 | |
|
|
| 1124 | $inVal = ''; |
|
|
| 1125 | $correctAnswer = math_constants($correctAnswer); |
|
|
| 1126 | my $formattedSubmittedAnswer = ''; |
|
|
| 1127 | |
|
|
| 1128 | #special variable $@ holds the last error from a Perl eval statement |
|
|
| 1129 | $@=''; |
|
|
| 1130 | |
|
|
| 1131 | if ($correctAnswer =~ /\S/) { |
|
|
| 1132 | ($correctVal, $PG_eval_errors,$PG_full_error_report) = PG_answer_eval($correctAnswer); |
|
|
| 1133 | } |
|
|
| 1134 | else { |
|
|
| 1135 | $PG_eval_errors = ' '; |
|
|
| 1136 | } |
|
|
| 1137 | |
|
|
| 1138 | if ( $PG_eval_errors or not is_a_number($correctVal) ) { ##error message from eval or above |
|
|
| 1139 | $formattedSubmittedAnswer = $PG_eval_errors; |
|
|
| 1140 | $formattedSubmittedAnswer = clean_up_error_msg($formattedSubmittedAnswer); |
|
|
| 1141 | $PGanswerMessage = 'Tell your professor that there is an error in this problem'; |
|
|
| 1142 | my $ans_hash = new AnswerHash( |
|
|
| 1143 | 'score' => $score, |
|
|
| 1144 | 'correct_ans' => $formattedCorrectAnswer, |
|
|
| 1145 | 'student_ans' => $formattedSubmittedAnswer, |
|
|
| 1146 | 'ans_message' => $PGanswerMessage, |
|
|
| 1147 | 'type' => 'number', |
|
|
| 1148 | 'preview_text_string' => $preview_text_string, |
|
|
| 1149 | 'preview_latex_string' => $preview_latex_string, |
|
|
| 1150 | 'original_student_ans' => $original_student_answer |
|
|
| 1151 | ); |
|
|
| 1152 | |
|
|
| 1153 | return $ans_hash; |
|
|
| 1154 | } |
|
|
| 1155 | |
|
|
| 1156 | $in = &math_constants($in); |
|
|
| 1157 | |
|
|
| 1158 | MODE_CASE: { ## bare block for "case" statement |
|
|
| 1159 | if ($mode eq 'std') { |
|
|
| 1160 | last MODE_CASE; |
|
|
| 1161 | } |
|
|
| 1162 | elsif ($mode eq 'strict') { |
|
|
| 1163 | unless (is_a_number($in)) { |
|
|
| 1164 | $PGanswerMessage = 'You must enter a number, e.g. -6, 5.3, or 6.12E-3'; |
|
|
| 1165 | $formattedSubmittedAnswer = 'Incorrect number format'; |
|
|
| 1166 | } |
|
|
| 1167 | else { |
|
|
| 1168 | last MODE_CASE; |
|
|
| 1169 | } |
|
|
| 1170 | } |
|
|
| 1171 | elsif ($mode eq 'arith') { |
|
|
| 1172 | unless (is_an_arithmetic_expression($in)) { |
|
|
| 1173 | $PGanswerMessage = 'You must enter an arithmetic expression, e.g. -6 or (2.3*4+5/3)^2'; |
|
|
| 1174 | $formattedSubmittedAnswer = 'Not an arithmetic expression'; |
|
|
| 1175 | } |
|
|
| 1176 | else { |
|
|
| 1177 | last MODE_CASE; |
|
|
| 1178 | } |
|
|
| 1179 | } |
|
|
| 1180 | elsif ($mode eq 'frac') { |
|
|
| 1181 | unless (is_a_fraction($in)) { |
|
|
| 1182 | $PGanswerMessage = 'You must enter a number or fraction , e.g. -6 or 7/13'; |
|
|
| 1183 | $formattedSubmittedAnswer = 'Not a number or fraction'; |
|
|
| 1184 | } |
|
|
| 1185 | else { |
|
|
| 1186 | last MODE_CASE; |
|
|
| 1187 | } |
|
|
| 1188 | } |
|
|
| 1189 | else { |
|
|
| 1190 | $PGanswerMessage = 'Tell your professor that there is an error in his or her answer mechanism. No mode was specified.'; |
|
|
| 1191 | $formattedSubmittedAnswer = $in; |
|
|
| 1192 | } |
|
|
| 1193 | |
|
|
| 1194 | my $ans_hash = new AnswerHash( |
|
|
| 1195 | score => $score, |
|
|
| 1196 | correct_ans => $formattedCorrectAnswer, |
|
|
| 1197 | student_ans => $formattedSubmittedAnswer, |
|
|
| 1198 | ans_message => $PGanswerMessage, |
|
|
| 1199 | type => "${mode}_number", |
|
|
| 1200 | preview_text_string => $preview_text_string, |
|
|
| 1201 | preview_latex_string => $preview_latex_string, |
|
|
| 1202 | original_student_ans => $original_student_answer |
|
|
| 1203 | ); |
|
|
| 1204 | |
|
|
| 1205 | return $ans_hash; |
|
|
| 1206 | } # end of MODE_CASES bare block |
|
|
| 1207 | |
|
|
| 1208 | $@ = ''; |
|
|
| 1209 | if ($in =~ /\S/) { |
|
|
| 1210 | |
|
|
| 1211 | ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($in); |
|
|
| 1212 | } |
|
|
| 1213 | else { |
|
|
| 1214 | $PG_eval_errors = ' '; |
|
|
| 1215 | } |
|
|
| 1216 | |
|
|
| 1217 | if ($PG_eval_errors) { ##error message from eval or above |
|
|
| 1218 | $formattedSubmittedAnswer = $PG_eval_errors; |
|
|
| 1219 | $formattedSubmittedAnswer =clean_up_error_msg($formattedSubmittedAnswer); |
|
|
| 1220 | $PGanswerMessage = 'There is a syntax error in your answer'; |
|
|
| 1221 | $PGanswerMessage = '' if $PG_eval_errors eq ' '; |
|
|
| 1222 | my $ans_hash = new AnswerHash( |
|
|
| 1223 | 'score' => $score, |
|
|
| 1224 | 'correct_ans' => $formattedCorrectAnswer, |
|
|
| 1225 | 'student_ans' => $formattedSubmittedAnswer, |
|
|
| 1226 | 'ans_message' => $PGanswerMessage, |
|
|
| 1227 | 'type' => "${mode}_number", |
|
|
| 1228 | 'preview_text_string' => $preview_text_string, |
|
|
| 1229 | 'preview_latex_string' => $preview_latex_string, |
|
|
| 1230 | 'original_student_ans' => $original_student_answer |
|
|
| 1231 | ); |
|
|
| 1232 | |
|
|
| 1233 | return $ans_hash; |
|
|
| 1234 | } |
|
|
| 1235 | else { |
|
|
| 1236 | $formattedSubmittedAnswer = prfmt($inVal,$format); |
|
|
| 1237 | } |
|
|
| 1238 | |
|
|
| 1239 | my $permitted_error; |
|
|
| 1240 | if (defined($tolType) && $tolType eq 'absolute') { |
|
|
| 1241 | $permitted_error = $tol; |
|
|
| 1242 | } |
|
|
| 1243 | elsif ( abs($correctVal) <= $zeroLevel) { |
|
|
| 1244 | $permitted_error = $zeroLevelTol; ## want $tol to be non zero |
|
|
| 1245 | } |
|
|
| 1246 | else { |
|
|
| 1247 | $permitted_error = abs($tol*$correctVal); |
|
|
| 1248 | } |
|
|
| 1249 | |
|
|
| 1250 | my $is_a_number = is_a_number($inVal); |
|
|
| 1251 | $score = 1 if ( ($is_a_number) and |
|
|
| 1252 | (abs( $inVal - $correctVal ) <= $permitted_error) ); |
|
|
| 1253 | if ($PG_eval_errors) { |
|
|
| 1254 | $PGanswerMessage = 'There is a syntax error in your answer'; |
|
|
| 1255 | } |
|
|
| 1256 | elsif (not $is_a_number) { |
|
|
| 1257 | $PGanswerMessage = 'Your answer does not evaluate to a number'; |
|
|
| 1258 | } |
|
|
| 1259 | |
|
|
| 1260 | my $ans_hash = new AnswerHash( |
|
|
| 1261 | 'score' => $score, |
|
|
| 1262 | 'correct_ans' => $formattedCorrectAnswer, |
|
|
| 1263 | 'student_ans' => $formattedSubmittedAnswer, |
|
|
| 1264 | 'ans_message' => $PGanswerMessage, |
|
|
| 1265 | 'type' => "${mode}_number", |
|
|
| 1266 | 'preview_text_string' => $preview_text_string, |
|
|
| 1267 | 'preview_latex_string' => $preview_latex_string, |
|
|
| 1268 | 'original_student_ans' => $original_student_answer |
|
|
| 1269 | ); |
|
|
| 1270 | |
|
|
| 1271 | return $ans_hash; |
|
|
| 1272 | }; |
|
|
| 1273 | |
|
|
| 1274 | return $answer_evaluator; |
|
|
| 1275 | } |
|
|
| 1276 | |
|
|
| 1277 | ## LOW-LEVEL ROUTINE -- NOT NORMALLY FOR END USERS -- USE WITH CAUTION |
|
|
| 1278 | sub NUM_CMP_LIST { # low level numeric list compare |
|
|
| 1279 | my %num_params = @_; |
|
|
| 1280 | |
|
|
| 1281 | my @outputList; |
|
|
| 1282 | my $ans; |
|
|
| 1283 | |
|
|
| 1284 | while ( @{$num_params{'answerList'}} ) { |
|
|
| 1285 | $ans = shift @{$num_params{'answerList'}}; |
|
|
| 1286 | push( @outputList, NUM_CMP( 'correctAnswer' => $ans, |
|
|
| 1287 | 'tolerance' => $num_params{'tolerance'}, |
|
|
| 1288 | 'tolType' => $num_params{'tolType'}, |
|
|
| 1289 | 'format' => $num_params{'format'}, |
|
|
| 1290 | 'mode' => $num_params{'mode'}, |
|
|
| 1291 | 'zeroLevel' => $num_params{'zeroLevel'}, |
|
|
| 1292 | 'zeroLevelTol' => $num_params{'zeroLevelTol'} |
|
|
| 1293 | ) |
1454 | |
|
|
1455 | $rh_ans->{student_ans} = $rh_ans->{original_student_ans}; |
|
|
1456 | $rh_ans->{correct_ans} = $rh_ans->{original_correct_ans}; |
|
|
1457 | $rh_ans;} |
| 1294 | ); |
1458 | ); |
| 1295 | } |
|
|
| 1296 | |
1459 | |
| 1297 | return @outputList; |
1460 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; |
|
|
1461 | return $rh_ans unless $rh_ans->catch_error('EVAL'); |
|
|
1462 | $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message}; |
|
|
1463 | $rh_ans->clear_error('EVAL'); } ); |
|
|
1464 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } ); |
|
|
1465 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('UNITS'); } ); |
|
|
1466 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('NUMBER'); } ); |
|
|
1467 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } ); |
|
|
1468 | |
|
|
1469 | |
|
|
1470 | $answer_evaluator; |
| 1298 | } |
1471 | } |
|
|
1472 | |
|
|
1473 | |
| 1299 | |
1474 | |
| 1300 | |
1475 | |
| 1301 | |
1476 | |
| 1302 | ########################################################################## |
1477 | ########################################################################## |
| 1303 | ########################################################################## |
1478 | ########################################################################## |
| … | |
… | |
| 1487 | 'params' => [], |
1662 | 'params' => [], |
| 1488 | 'limits' => [ [0,1], [0,1]], |
1663 | 'limits' => [ [0,1], [0,1]], |
| 1489 | 'reltol' => $main::functRelPercentTolDefault, |
1664 | 'reltol' => $main::functRelPercentTolDefault, |
| 1490 | 'numPoints' => $main::functNumOfPoints, |
1665 | 'numPoints' => $main::functNumOfPoints, |
| 1491 | 'zeroLevel' => $main::functZeroLevelDefault, |
1666 | 'zeroLevel' => $main::functZeroLevelDefault, |
| 1492 | 'zeroLevelTol' => $main::functZeroLevelTolDefault, |
1667 | 'zeroLevelTol' => $main::functZeroLevelTolDefault, |
| 1493 | 'debug' => 0, |
1668 | 'debug' => 0, |
| 1494 | ); |
1669 | ); |
| 1495 | |
1670 | |
| 1496 | my $var_ref = $options{'vars'}; |
1671 | my $var_ref = $options{'vars'}; |
| 1497 | my $ra_params = $options{ 'params'}; |
1672 | my $ra_params = $options{ 'params'}; |
| … | |
… | |
| 1499 | my $relPercentTol= $options{'reltol'}; |
1674 | my $relPercentTol= $options{'reltol'}; |
| 1500 | my $numPoints = $options{'numPoints'}; |
1675 | my $numPoints = $options{'numPoints'}; |
| 1501 | my $zeroLevel = $options{'zeroLevel'}; |
1676 | my $zeroLevel = $options{'zeroLevel'}; |
| 1502 | my $zeroLevelTol = $options{'zeroLevelTol'}; |
1677 | my $zeroLevelTol = $options{'zeroLevelTol'}; |
| 1503 | |
1678 | |
| 1504 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
1679 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
| 1505 | 'var' => $var_ref, |
1680 | 'var' => $var_ref, |
| 1506 | 'limits' => $limit_ref, |
1681 | 'limits' => $limit_ref, |
| 1507 | 'tolerance' => $relPercentTol, |
1682 | 'tolerance' => $relPercentTol, |
| 1508 | 'tolType' => 'relative', |
1683 | 'tolType' => 'relative', |
| 1509 | 'numPoints' => $numPoints, |
1684 | 'numPoints' => $numPoints, |
| 1510 | 'mode' => 'std', |
1685 | 'mode' => 'std', |
| 1511 | 'maxConstantOfIntegration' => 10**100, |
1686 | 'maxConstantOfIntegration' => 10**100, |
| 1512 | 'zeroLevel' => $zeroLevel, |
1687 | 'zeroLevel' => $zeroLevel, |
| 1513 | 'zeroLevelTol' => $zeroLevelTol, |
1688 | 'zeroLevelTol' => $zeroLevelTol, |
| 1514 | 'scale_norm' => 1, |
1689 | 'scale_norm' => 1, |
| 1515 | 'params' => $ra_params, |
1690 | 'params' => $ra_params, |
| 1516 | 'debug' => $options{debug} , |
1691 | 'debug' => $options{debug} , |
| 1517 | ); |
1692 | ); |
| 1518 | |
1693 | |
| 1519 | } |
1694 | } |
| 1520 | |
1695 | |
| 1521 | sub function_cmp { |
1696 | sub function_cmp { |
| … | |
… | |
| 1523 | |
1698 | |
| 1524 | if ( (scalar(@_) == 3) or (scalar(@_) > 8) or (scalar(@_) == 0) ) { |
1699 | if ( (scalar(@_) == 3) or (scalar(@_) > 8) or (scalar(@_) == 0) ) { |
| 1525 | function_invalid_params( $correctEqn ); |
1700 | function_invalid_params( $correctEqn ); |
| 1526 | } |
1701 | } |
| 1527 | else { |
1702 | else { |
| 1528 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
1703 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
| 1529 | 'var' => $var, |
1704 | 'var' => $var, |
| 1530 | 'limits' => [$llimit, $ulimit], |
1705 | 'limits' => [$llimit, $ulimit], |
| 1531 | 'tolerance' => $relPercentTol, |
1706 | 'tolerance' => $relPercentTol, |
| 1532 | 'tolType' => 'relative', |
1707 | 'tolType' => 'relative', |
| 1533 | 'numPoints' => $numPoints, |
1708 | 'numPoints' => $numPoints, |
| 1534 | 'mode' => 'std', |
1709 | 'mode' => 'std', |
| 1535 | 'maxConstantOfIntegration' => 0, |
1710 | 'maxConstantOfIntegration' => 0, |
| 1536 | 'zeroLevel' => $zeroLevel, |
1711 | 'zeroLevel' => $zeroLevel, |
| 1537 | 'zeroLevelTol' => $zeroLevelTol |
1712 | 'zeroLevelTol' => $zeroLevelTol |
| 1538 | ); |
1713 | ); |
| 1539 | } |
1714 | } |
| 1540 | } |
1715 | } |
| 1541 | |
1716 | |
| 1542 | sub function_cmp_up_to_constant { ## for antiderivative problems |
1717 | sub function_cmp_up_to_constant { ## for antiderivative problems |
| … | |
… | |
| 1544 | |
1719 | |
| 1545 | if ( (scalar(@_) == 3) or (scalar(@_) > 9) or (scalar(@_) == 0) ) { |
1720 | if ( (scalar(@_) == 3) or (scalar(@_) > 9) or (scalar(@_) == 0) ) { |
| 1546 | function_invalid_params( $correctEqn ); |
1721 | function_invalid_params( $correctEqn ); |
| 1547 | } |
1722 | } |
| 1548 | else { |
1723 | else { |
| 1549 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
1724 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
| 1550 | 'var' => $var, |
1725 | 'var' => $var, |
| 1551 | 'limits' => [$llimit, $ulimit], |
1726 | 'limits' => [$llimit, $ulimit], |
| 1552 | 'tolerance' => $relPercentTol, |
1727 | 'tolerance' => $relPercentTol, |
| 1553 | 'tolType' => 'relative', |
1728 | 'tolType' => 'relative', |
| 1554 | 'numPoints' => $numPoints, |
1729 | 'numPoints' => $numPoints, |
| 1555 | 'mode' => 'antider', |
1730 | 'mode' => 'antider', |
| 1556 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
1731 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
| 1557 | 'zeroLevel' => $zeroLevel, |
1732 | 'zeroLevel' => $zeroLevel, |
| 1558 | 'zeroLevelTol' => $zeroLevelTol |
1733 | 'zeroLevelTol' => $zeroLevelTol |
| 1559 | ); |
1734 | ); |
| 1560 | } |
1735 | } |
| 1561 | } |
1736 | } |
| 1562 | |
1737 | |
| 1563 | sub function_cmp_abs { ## similar to function_cmp but uses absolute tolerance |
1738 | sub function_cmp_abs { ## similar to function_cmp but uses absolute tolerance |
| … | |
… | |
| 1590 | if ( (scalar(@_) == 3) or (scalar(@_) > 7) or (scalar(@_) == 0) ) { |
1765 | if ( (scalar(@_) == 3) or (scalar(@_) > 7) or (scalar(@_) == 0) ) { |
| 1591 | function_invalid_params( $correctEqn ); |
1766 | function_invalid_params( $correctEqn ); |
| 1592 | } |
1767 | } |
| 1593 | |
1768 | |
| 1594 | else { |
1769 | else { |
| 1595 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
1770 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
| 1596 | 'var' => $var, |
1771 | 'var' => $var, |
| 1597 | 'limits' => [$llimit, $ulimit], |
1772 | 'limits' => [$llimit, $ulimit], |
| 1598 | 'tolerance' => $absTol, |
1773 | 'tolerance' => $absTol, |
| 1599 | 'tolType' => 'absolute', |
1774 | 'tolType' => 'absolute', |
| 1600 | 'numPoints' => $numPoints, |
1775 | 'numPoints' => $numPoints, |
| 1601 | 'mode' => 'antider', |
1776 | 'mode' => 'antider', |
| 1602 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
1777 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
| 1603 | 'zeroLevel' => 0, |
1778 | 'zeroLevel' => 0, |
| 1604 | 'zeroLevelTol' => 0 |
1779 | 'zeroLevelTol' => 0 |
| 1605 | ); |
1780 | ); |
| 1606 | } |
1781 | } |
| 1607 | } |
1782 | } |
| 1608 | |
1783 | |
| 1609 | ## The following answer evaluator for comparing multivarable functions was |
1784 | ## The following answer evaluator for comparing multivarable functions was |
| … | |
… | |
| 1735 | sub fun_cmp { |
1910 | sub fun_cmp { |
| 1736 | my $correctAnswer = shift @_; |
1911 | my $correctAnswer = shift @_; |
| 1737 | my %opt = @_; |
1912 | my %opt = @_; |
| 1738 | |
1913 | |
| 1739 | assign_option_aliases( \%opt, |
1914 | assign_option_aliases( \%opt, |
| 1740 | 'vars' => 'var', # set the standard option 'var' to the one specified as vars |
1915 | 'vars' => 'var', # set the standard option 'var' to the one specified as vars |
| 1741 | 'domain' => 'limits', # set the standard option 'limits' to the one specified as domain |
1916 | 'domain' => 'limits', # set the standard option 'limits' to the one specified as domain |
| 1742 | 'reltol' => 'relTol', |
1917 | 'reltol' => 'relTol', |
| 1743 | 'param' => 'params', |
1918 | 'param' => 'params', |
| 1744 | ); |
1919 | ); |
| 1745 | |
1920 | |
| 1746 | set_default_options( \%opt, |
1921 | set_default_options( \%opt, |
| 1747 | 'var' => $functVarDefault, |
1922 | 'var' => $functVarDefault, |
| 1748 | 'params' => [], |
1923 | 'params' => [], |
| 1749 | 'limits' => [[$functLLimitDefault, $functULimitDefault]], |
1924 | 'limits' => [[$functLLimitDefault, $functULimitDefault]], |
| 1750 | 'mode' => 'std', |
1925 | 'mode' => 'std', |
| 1751 | 'tolType' => (defined($opt{tol}) ) ? 'absolute' : 'relative', |
1926 | 'tolType' => (defined($opt{tol}) ) ? 'absolute' : 'relative', |
| 1752 | 'tol' => .01, # default mode should be relative, to obtain this tol must not be defined |
1927 | 'tol' => .01, # default mode should be relative, to obtain this tol must not be defined |
| 1753 | 'relTol' => $functRelPercentTolDefault, |
1928 | 'relTol' => $functRelPercentTolDefault, |
| 1754 | 'numPoints' => $functNumOfPoints, |
1929 | 'numPoints' => $functNumOfPoints, |
| 1755 | 'maxConstantOfIntegration' => $functMaxConstantOfIntegration, |
1930 | 'maxConstantOfIntegration' => $functMaxConstantOfIntegration, |
| 1756 | 'zeroLevel' => $functZeroLevelDefault, |
1931 | 'zeroLevel' => $functZeroLevelDefault, |
| 1757 | 'zeroLevelTol' => $functZeroLevelTolDefault, |
1932 | 'zeroLevelTol' => $functZeroLevelTolDefault, |
| 1758 | 'debug' => 0, |
1933 | 'debug' => 0, |
| 1759 | ); |
1934 | ); |
| 1760 | |
1935 | |
| 1761 | |
1936 | |
| 1762 | |
1937 | |
| 1763 | # allow var => 'x' as an abbreviation for var => ['x'] |
1938 | # allow var => 'x' as an abbreviation for var => ['x'] |
| … | |
… | |
| 1778 | $tolType = 'relative'; |
1953 | $tolType = 'relative'; |
| 1779 | $tol = $out_options{'relTol'}; |
1954 | $tol = $out_options{'relTol'}; |
| 1780 | delete($out_options{'tol'}) if exists( $out_options{'tol'} ); |
1955 | delete($out_options{'tol'}) if exists( $out_options{'tol'} ); |
| 1781 | } |
1956 | } |
| 1782 | |
1957 | |
| 1783 | |
|
|
| 1784 | |
|
|
| 1785 | my @output_list = (); |
1958 | my @output_list = (); |
| 1786 | # thread over lists |
1959 | # thread over lists |
| 1787 | my @ans_list = (); |
1960 | my @ans_list = (); |
| 1788 | |
1961 | |
| 1789 | if ( ref($correctAnswer) eq 'ARRAY' ) { |
1962 | if ( ref($correctAnswer) eq 'ARRAY' ) { |
| … | |
… | |
| 1791 | } |
1964 | } |
| 1792 | else { |
1965 | else { |
| 1793 | push( @ans_list, $correctAnswer ); |
1966 | push( @ans_list, $correctAnswer ); |
| 1794 | } |
1967 | } |
| 1795 | |
1968 | |
| 1796 | |
|
|
| 1797 | |
|
|
| 1798 | # produce answer evaluators |
1969 | # produce answer evaluators |
| 1799 | foreach my $ans (@ans_list) { |
1970 | foreach my $ans (@ans_list) { |
| 1800 | push(@output_list, |
1971 | push(@output_list, |
| 1801 | FUNCTION_CMP( 'correctEqn' => $ans, |
1972 | FUNCTION_CMP( 'correctEqn' => $ans, |
| 1802 | 'var' => $out_options{'var'}, |
1973 | 'var' => $out_options{'var'}, |
| 1803 | 'limits' => $out_options{'limits'}, |
1974 | 'limits' => $out_options{'limits'}, |
| 1804 | 'tolerance' => $tol, |
1975 | 'tolerance' => $tol, |
| 1805 | 'tolType' => $tolType, |
1976 | 'tolType' => $tolType, |
| 1806 | 'numPoints' => $out_options{'numPoints'}, |
1977 | 'numPoints' => $out_options{'numPoints'}, |
| 1807 | 'mode' => $out_options{'mode'}, |
1978 | 'mode' => $out_options{'mode'}, |
| 1808 | 'maxConstantOfIntegration' => $out_options{'maxConstantOfIntegration'}, |
1979 | 'maxConstantOfIntegration' => $out_options{'maxConstantOfIntegration'}, |
| 1809 | 'zeroLevel' => $out_options{'zeroLevel'}, |
1980 | 'zeroLevel' => $out_options{'zeroLevel'}, |
| 1810 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
1981 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
| 1811 | 'params' => $out_options{'params'}, |
1982 | 'params' => $out_options{'params'}, |
| 1812 | 'debug' => $out_options{'debug'}, |
1983 | 'debug' => $out_options{'debug'}, |
| 1813 | ), |
1984 | ), |
| 1814 | ); |
1985 | ); |
| 1815 | } |
1986 | } |
| 1816 | |
1987 | |
| 1817 | return @output_list; |
1988 | return @output_list; |
| … | |
… | |
| 1839 | |
2010 | |
| 1840 | sub FUNCTION_CMP { |
2011 | sub FUNCTION_CMP { |
| 1841 | my %func_params = @_; |
2012 | my %func_params = @_; |
| 1842 | |
2013 | |
| 1843 | my $correctEqn = $func_params{'correctEqn'}; |
2014 | my $correctEqn = $func_params{'correctEqn'}; |
| 1844 | my $var = $func_params{'var'}; |
2015 | my $var = $func_params{'var'}; |
| 1845 | my $ra_limits = $func_params{'limits'}; |
2016 | my $ra_limits = $func_params{'limits'}; |
| 1846 | my $tol = $func_params{'tolerance'}; |
2017 | my $tol = $func_params{'tolerance'}; |
| 1847 | my $tolType = $func_params{'tolType'}; |
2018 | my $tolType = $func_params{'tolType'}; |
| 1848 | my $numPoints = $func_params{'numPoints'}; |
2019 | my $numPoints = $func_params{'numPoints'}; |
| 1849 | my $mode = $func_params{'mode'}; |
2020 | my $mode = $func_params{'mode'}; |
| 1850 | my $maxConstantOfIntegration = $func_params{'maxConstantOfIntegration'}; |
2021 | my $maxConstantOfIntegration = $func_params{'maxConstantOfIntegration'}; |
| 1851 | my $zeroLevel = $func_params{'zeroLevel'}; |
2022 | my $zeroLevel = $func_params{'zeroLevel'}; |
| 1852 | my $zeroLevelTol = $func_params{'zeroLevelTol'}; |
2023 | my $zeroLevelTol = $func_params{'zeroLevelTol'}; |
| 1853 | |
2024 | |
| 1854 | |
2025 | |
| 1855 | # Check that everything is defined: |
2026 | # Check that everything is defined: |
| … | |
… | |
| 1886 | $numPoints = $functNumOfPoints unless defined $numPoints; |
2057 | $numPoints = $functNumOfPoints unless defined $numPoints; |
| 1887 | $maxConstantOfIntegration = $functMaxConstantOfIntegration unless defined $maxConstantOfIntegration; |
2058 | $maxConstantOfIntegration = $functMaxConstantOfIntegration unless defined $maxConstantOfIntegration; |
| 1888 | $zeroLevel = $functZeroLevelDefault unless defined $zeroLevel; |
2059 | $zeroLevel = $functZeroLevelDefault unless defined $zeroLevel; |
| 1889 | $zeroLevelTol = $functZeroLevelTolDefault unless defined $zeroLevelTol; |
2060 | $zeroLevelTol = $functZeroLevelTolDefault unless defined $zeroLevelTol; |
| 1890 | |
2061 | |
| 1891 | $func_params{'var'} = $var; |
2062 | $func_params{'var'} = $var; |
| 1892 | $func_params{'limits'} = \@limits; |
2063 | $func_params{'limits'} = \@limits; |
| 1893 | $func_params{'tolerance'}= $tol; |
2064 | $func_params{'tolerance'} = $tol; |
| 1894 | $func_params{'tolType'} = $tolType; |
2065 | $func_params{'tolType'} = $tolType; |
| 1895 | $func_params{'numPoints'}= $numPoints; |
2066 | $func_params{'numPoints'} = $numPoints; |
| 1896 | $func_params{'mode'} = $mode; |
2067 | $func_params{'mode'} = $mode; |
| 1897 | $func_params{'maxConstantOfIntegration'} = $maxConstantOfIntegration; |
2068 | $func_params{'maxConstantOfIntegration'} = $maxConstantOfIntegration; |
| 1898 | $func_params{'zeroLevel'} = $zeroLevel; |
2069 | $func_params{'zeroLevel'} = $zeroLevel; |
| 1899 | $func_params{'zeroLevelTol'} = $zeroLevelTol; |
2070 | $func_params{'zeroLevelTol'} = $zeroLevelTol; |
|
|
2071 | |
| 1900 | ######################################################## |
2072 | ######################################################## |
| 1901 | # End of cleanup of calling parameters |
2073 | # End of cleanup of calling parameters |
| 1902 | ######################################################## |
2074 | ######################################################## |
| 1903 | my $i; #for use with loops |
2075 | my $i; #for use with loops |
| 1904 | my $PGanswerMessage = ""; |
2076 | my $PGanswerMessage = ""; |
| 1905 | my $originalCorrEqn = $correctEqn; |
2077 | my $originalCorrEqn = $correctEqn; |
| 1906 | |
2078 | |
| 1907 | #prepare the correct answer and check it's syntax |
2079 | #prepare the correct answer and check it's syntax |
| 1908 | my $rh_correct_ans = new AnswerHash; |
2080 | my $rh_correct_ans = new AnswerHash; |
| 1909 | $rh_correct_ans->input($correctEqn); |
2081 | $rh_correct_ans->input($correctEqn); |
| 1910 | $rh_correct_ans = check_syntax($rh_correct_ans); |
2082 | $rh_correct_ans = check_syntax($rh_correct_ans); |
| 1911 | warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag}; |
2083 | warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag}; |
| 1912 | $rh_correct_ans->clear_error(); |
2084 | $rh_correct_ans->clear_error(); |
| 1913 | $rh_correct_ans = function_from_string2($rh_correct_ans, ra_vars => [ @VARS, @PARAMS ], |
2085 | $rh_correct_ans = function_from_string2($rh_correct_ans, ra_vars => [ @VARS, @PARAMS ], |
| … | |
… | |
| 1916 | my $correct_eqn_sub = $rh_correct_ans->{rf_correct_ans}; |
2088 | my $correct_eqn_sub = $rh_correct_ans->{rf_correct_ans}; |
| 1917 | warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag}; |
2089 | warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag}; |
| 1918 | |
2090 | |
| 1919 | #create the evaluation points |
2091 | #create the evaluation points |
| 1920 | my $random_for_answers = new PGrandom($main::PG_original_problemSeed); |
2092 | my $random_for_answers = new PGrandom($main::PG_original_problemSeed); |
| 1921 | my $NUMBER_OF_STEPS_IN_RANDOM = 1000; # determines the granularity of the random_for_answers number generator |
2093 | my $NUMBER_OF_STEPS_IN_RANDOM = 1000; # determines the granularity of the random_for_answers number generator |
| 1922 | my (@evaluation_points); |
2094 | my (@evaluation_points); |
| 1923 | for( my $count = 0; $count < @PARAMS+1+$numPoints; $count++ ) { |
2095 | for( my $count = 0; $count < @PARAMS+1+$numPoints; $count++ ) { |
| 1924 | my (@vars,$iteration_limit); |
2096 | my (@vars,$iteration_limit); |
| 1925 | for( my $i = 0; $i < @VARS; $i++ ) { |
2097 | for( my $i = 0; $i < @VARS; $i++ ) { |
| 1926 | my $iteration_limit = 10; |
2098 | my $iteration_limit = 10; |
| … | |
… | |
| 1935 | push(@evaluation_points,\@vars); |
2107 | push(@evaluation_points,\@vars); |
| 1936 | } |
2108 | } |
| 1937 | my $evaluation_points = Matrix->new_from_array_ref(\@evaluation_points); |
2109 | my $evaluation_points = Matrix->new_from_array_ref(\@evaluation_points); |
| 1938 | |
2110 | |
| 1939 | #my $COEFFS = determine_param_coeffs($correct_eqn_sub,$evaluation_points[0],$numOfParameters); |
2111 | #my $COEFFS = determine_param_coeffs($correct_eqn_sub,$evaluation_points[0],$numOfParameters); |
| 1940 | #warn "coeff", join(" | ", @{$COEFFS}); |
2112 | #warn "coeff", join(" | ", @{$COEFFS}); |
| 1941 | |
2113 | |
| 1942 | #construct the answer evaluator |
2114 | #construct the answer evaluator |
| 1943 | my $answer_evaluator = new AnswerEvaluator; |
2115 | my $answer_evaluator = new AnswerEvaluator; |
| 1944 | $answer_evaluator->{debug} = $func_params{debug}; |
2116 | $answer_evaluator->{debug} = $func_params{debug}; |
| 1945 | $answer_evaluator->ans_hash( correct_ans => $originalCorrEqn, |
2117 | $answer_evaluator->ans_hash( correct_ans => $originalCorrEqn, |
| … | |
… | |
| 2098 | push(@out, $temp_hash->input()); |
2270 | push(@out, $temp_hash->input()); |
| 2099 | |
2271 | |
| 2100 | } |
2272 | } |
| 2101 | if ($PGanswerMessage) { |
2273 | if ($PGanswerMessage) { |
| 2102 | $rh_ans->input( "( " . join(", ", @out ) . " )" ); |
2274 | $rh_ans->input( "( " . join(", ", @out ) . " )" ); |
| 2103 | $rh_ans->throw_error('SYTNAX', 'There is a syntax error in your answer.'); |
2275 | $rh_ans->throw_error('SYNTAX', 'There is a syntax error in your answer.'); |
| 2104 | } else { |
2276 | } else { |
| 2105 | $rh_ans->input( [@out] ); |
2277 | $rh_ans->input( [@out] ); |
| 2106 | } |
2278 | } |
| 2107 | $rh_ans; |
2279 | $rh_ans; |
| 2108 | } |
2280 | } |
| … | |
… | |
| 2440 | #warn "taking the difference of ", $inVal, " and ", $correctVal, " is ", $diff; |
2612 | #warn "taking the difference of ", $inVal, " and ", $correctVal, " is ", $diff; |
| 2441 | |
2613 | |
| 2442 | if (defined($options{tolType}) and $options{tolType} eq 'relative' ) { #relative tolerance |
2614 | if (defined($options{tolType}) and $options{tolType} eq 'relative' ) { #relative tolerance |
| 2443 | #warn "diff = $diff"; |
2615 | #warn "diff = $diff"; |
| 2444 | |
2616 | |
| 2445 | $diff = abs(( $inVal - ($correctVal-$tol_val ) )/$tol_val -1 ) if abs($tol_val) > $options{zeroLevel}; |
2617 | $diff = ( $inVal - ($correctVal-$tol_val ) )/abs($tol_val) -1 if abs($tol_val) > $options{zeroLevel}; |
| 2446 | #$diff = ( $inVal - ($correctVal-$tol_val- $tol_val ) )/abs($tol_val) if abs($tol_val) > $options{zeroLevel}; |
2618 | #$diff = ( $inVal - ($correctVal-$tol_val- $tol_val ) )/abs($tol_val) if abs($tol_val) > $options{zeroLevel}; |
| 2447 | #warn "diff = $diff, ", abs( &$rf_correct_fun(@inputs) ) , "-- $correctVal"; |
2619 | #warn "diff = $diff, ", abs( &$rf_correct_fun(@inputs) ) , "-- $correctVal"; |
| 2448 | } |
2620 | } |
| 2449 | } |
2621 | } |
| 2450 | last if $errors; # break if there are any errors. |
2622 | last if $errors; # break if there are any errors. |
| … | |
… | |
| 2455 | push(@correct_values,( $inVal - ($correctVal-$tol_val ) )); |
2627 | push(@correct_values,( $inVal - ($correctVal-$tol_val ) )); |
| 2456 | push(@differences, $diff); |
2628 | push(@differences, $diff); |
| 2457 | push(@tol_values,$tol_val); |
2629 | push(@tol_values,$tol_val); |
| 2458 | } |
2630 | } |
| 2459 | $rh_ans ->{ra_differences} = \@differences; |
2631 | $rh_ans ->{ra_differences} = \@differences; |
| 2460 | $rh_ans ->{ra_student_values} = \@student_values; # values from student function |
2632 | $rh_ans ->{ra_student_values} = \@student_values; |
| 2461 | $rh_ans ->{ra_adjusted_instructor_values} = \@correct_values; #values |
2633 | $rh_ans ->{ra_adjusted_student_values} = \@correct_values; |
| 2462 | $rh_ans->{ra_instructor_values}=\@tol_values; |
2634 | $rh_ans->{ra_tol_values}=\@tol_values; |
| 2463 | $rh_ans->throw_error('EVAL', $errors) if defined($errors); |
2635 | $rh_ans->throw_error('EVAL', $errors) if defined($errors); |
| 2464 | $rh_ans; |
2636 | $rh_ans; |
| 2465 | } |
2637 | } |
| 2466 | |
2638 | |
| 2467 | |
2639 | |
| … | |
… | |
| 2523 | ## individual filters below it |
2695 | ## individual filters below it |
| 2524 | sub str_filters { |
2696 | sub str_filters { |
| 2525 | my $stringToFilter = shift @_; |
2697 | my $stringToFilter = shift @_; |
| 2526 | my @filters_to_use = @_; |
2698 | my @filters_to_use = @_; |
| 2527 | my %known_filters = ( 'remove_whitespace' => undef, |
2699 | my %known_filters = ( 'remove_whitespace' => undef, |
| 2528 | 'compress_whitespace' => undef, |
2700 | 'compress_whitespace' => undef, |
| 2529 | 'trim_whitespace' => undef, |
2701 | 'trim_whitespace' => undef, |
| 2530 | 'ignore_case' => undef, |
2702 | 'ignore_case' => undef, |
| 2531 | 'ignore_order' => undef |
2703 | 'ignore_order' => undef |
| 2532 | ); |
2704 | ); |
| 2533 | |
2705 | |
| 2534 | #test for unknown filters |
2706 | #test for unknown filters |
| 2535 | my $filter; |
2707 | my $filter; |
| 2536 | foreach $filter (@filters_to_use) { |
2708 | foreach $filter (@filters_to_use) { |
| … | |
… | |
| 2658 | sub std_str_cmp { # compare strings |
2830 | sub std_str_cmp { # compare strings |
| 2659 | my $correctAnswer = shift @_; |
2831 | my $correctAnswer = shift @_; |
| 2660 | my @filters = ( 'compress_whitespace', 'ignore_case' ); |
2832 | my @filters = ( 'compress_whitespace', 'ignore_case' ); |
| 2661 | my $type = 'std_str_cmp'; |
2833 | my $type = 'std_str_cmp'; |
| 2662 | STR_CMP( 'correctAnswer' => $correctAnswer, |
2834 | STR_CMP( 'correctAnswer' => $correctAnswer, |
| 2663 | 'filters' => \@filters, |
2835 | 'filters' => \@filters, |
| 2664 | 'type' => $type |
2836 | 'type' => $type |
| 2665 | ); |
2837 | ); |
| 2666 | } |
2838 | } |
| 2667 | |
2839 | |
| 2668 | sub std_str_cmp_list { # alias for std_str_cmp |
2840 | sub std_str_cmp_list { # alias for std_str_cmp |
| 2669 | my @answerList = @_; |
2841 | my @answerList = @_; |
| … | |
… | |
| 2872 | ## IN: a hashtable with the following entries (error-checking to be added later?): |
3044 | ## IN: a hashtable with the following entries (error-checking to be added later?): |
| 2873 | ## correctAnswer -- the correct answer, before filtering |
3045 | ## correctAnswer -- the correct answer, before filtering |
| 2874 | ## filters -- reference to an array containing the filters to be applied |
3046 | ## filters -- reference to an array containing the filters to be applied |
| 2875 | ## type -- a string containing the type of answer evaluator in use |
3047 | ## type -- a string containing the type of answer evaluator in use |
| 2876 | ## OUT: a reference to an answer evaluator subroutine |
3048 | ## OUT: a reference to an answer evaluator subroutine |
|
|
3049 | |
| 2877 | sub STR_CMP { |
3050 | sub STR_CMP { |
| 2878 | my %str_params = @_; |
3051 | my %str_params = @_; |
| 2879 | |
|
|
| 2880 | $str_params{'correctAnswer'} = str_filters( $str_params{'correctAnswer'}, @{$str_params{'filters'}} ); |
3052 | $str_params{'correctAnswer'} = str_filters( $str_params{'correctAnswer'}, @{$str_params{'filters'}} ); |
| 2881 | |
|
|
| 2882 | my $answer_evaluator = sub { |
3053 | my $answer_evaluator = sub { |
| 2883 | my $in = shift @_; |
3054 | my $in = shift @_; |
| 2884 | $in = '' unless defined $in; |
3055 | $in = '' unless defined $in; |
| 2885 | my $original_student_ans = $in; |
3056 | my $original_student_ans = $in; |
| 2886 | |
|
|
| 2887 | $in = str_filters( $in, @{$str_params{'filters'}} ); |
3057 | $in = str_filters( $in, @{$str_params{'filters'}} ); |
| 2888 | |
|
|
| 2889 | my $correctQ = ( $in eq $str_params{'correctAnswer'} ) ? 1: 0; |
3058 | my $correctQ = ( $in eq $str_params{'correctAnswer'} ) ? 1: 0; |
| 2890 | my $ans_hash = new AnswerHash( |
3059 | my $ans_hash = new AnswerHash( 'score' => $correctQ, |
| 2891 | 'score' => $correctQ, |
|
|
| 2892 | 'correct_ans' => $str_params{'correctAnswer'}, |
3060 | 'correct_ans' => $str_params{'correctAnswer'}, |
| 2893 | 'student_ans' => $in, |
3061 | 'student_ans' => $in, |
| 2894 | 'ans_message' => '', |
3062 | 'ans_message' => '', |
| 2895 | 'type' => $str_params{'type'}, |
3063 | 'type' => $str_params{'type'}, |
| 2896 | 'preview_text_string' => $in, |
3064 | 'preview_text_string' => $in, |
| 2897 | 'preview_latex_string' => $in, |
3065 | 'preview_latex_string' => $in, |
| 2898 | 'original_student_ans' => $original_student_ans |
3066 | 'original_student_ans' => $original_student_ans |
| 2899 | ); |
3067 | ); |
| 2900 | |
|
|
| 2901 | return $ans_hash; |
3068 | return $ans_hash; |
| 2902 | }; |
3069 | }; |
| 2903 | |
|
|
| 2904 | return $answer_evaluator; |
3070 | return $answer_evaluator; |
| 2905 | } |
3071 | } |
| 2906 | |
|
|
| 2907 | |
|
|
| 2908 | |
3072 | |
| 2909 | ########################################################################## |
3073 | ########################################################################## |
| 2910 | ########################################################################## |
3074 | ########################################################################## |
| 2911 | ## Miscellaneous answer evaluators |
3075 | ## Miscellaneous answer evaluators |
| 2912 | |
3076 | |
| … | |
… | |
| 3191 | $problem_result{score} = $allAnswersCorrectQ; |
3355 | $problem_result{score} = $allAnswersCorrectQ; |
| 3192 | |
3356 | |
| 3193 | # I don't like to put in this bit of code. |
3357 | # I don't like to put in this bit of code. |
| 3194 | # It makes it hard to construct error free problem graders |
3358 | # It makes it hard to construct error free problem graders |
| 3195 | # I would prefer to know that the problem score was numeric. |
3359 | # I would prefer to know that the problem score was numeric. |
| 3196 | unless (defined($problem_state{recorded_score}) and $problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) { |
3360 | unless ($problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) { |
| 3197 | $problem_state{recorded_score} = 0; # This gets rid of non-numeric scores |
3361 | $problem_state{recorded_score} = 0; # This gets rid of non-numeric scores |
| 3198 | } |
3362 | } |
| 3199 | # |
3363 | # |
| 3200 | if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) { |
3364 | if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) { |
| 3201 | $problem_state{recorded_score} = 1; |
3365 | $problem_state{recorded_score} = 1; |
| … | |
… | |
| 3424 | ## a reference to an array of arrays of limits -- [[llim,ulim], [llim,ulim]] |
3588 | ## a reference to an array of arrays of limits -- [[llim,ulim], [llim,ulim]] |
| 3425 | ## a reference to an array of limits -- [llim, ulim] |
3589 | ## a reference to an array of limits -- [llim, ulim] |
| 3426 | ## an array of array references -- ([llim,ulim], [llim,ulim]) |
3590 | ## an array of array references -- ([llim,ulim], [llim,ulim]) |
| 3427 | ## an array of limits -- (llim,ulim) |
3591 | ## an array of limits -- (llim,ulim) |
| 3428 | ## OUT: an array of array references -- ([llim,ulim], [llim,ulim]) or ([llim,ulim]) |
3592 | ## OUT: an array of array references -- ([llim,ulim], [llim,ulim]) or ([llim,ulim]) |
|
|
3593 | |
| 3429 | sub get_limits_array { |
3594 | sub get_limits_array { |
| 3430 | my $in = shift @_; |
3595 | my $in = shift @_; |
| 3431 | my @out; |
3596 | my @out; |
| 3432 | |
3597 | |
| 3433 | if( not defined($in) ) { #if nothing defined, build default array and return |
3598 | if( not defined($in) ) { #if nothing defined, build default array and return |
| … | |
… | |
| 3479 | }; |
3644 | }; |
| 3480 | |
3645 | |
| 3481 | return $error_response; |
3646 | return $error_response; |
| 3482 | } |
3647 | } |
| 3483 | |
3648 | |
| 3484 | # outputs a hash to the screen |
3649 | |
| 3485 | # sub display_options { |
3650 | ######################################################################### |
| 3486 | # my %options = @_; |
3651 | # Filters for answer evaluators |
| 3487 | # my $out_string = ""; |
3652 | ######################################################################### |
| 3488 | # foreach my $key (keys %options) { |
3653 | |
| 3489 | # $out_string .= " $key => $options{$key},<BR>"; |
|
|
| 3490 | # } |
|
|
| 3491 | # return $out_string; |
|
|
| 3492 | # } |
|
|
| 3493 | |
3654 | |
| 3494 | sub is_a_number { |
3655 | sub is_a_number { |
| 3495 | my ($num) = @_; |
3656 | my ($num,%options) = @_; |
|
|
3657 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
|
|
3658 | my ($rh_ans); |
|
|
3659 | if ($process_ans_hash) { |
|
|
3660 | $rh_ans = $num; |
|
|
3661 | $num = $rh_ans->{student_ans}; |
|
|
3662 | } |
|
|
3663 | |
| 3496 | my $is_a_number = 0; |
3664 | my $is_a_number = 0; |
| 3497 | return $is_a_number unless defined($num); |
3665 | return $is_a_number unless defined($num); |
| 3498 | $num =~ s/^\s*//; ## remove initial spaces |
3666 | $num =~ s/^\s*//; ## remove initial spaces |
| 3499 | $num =~ s/\s*$//; ## remove trailing spaces |
3667 | $num =~ s/\s*$//; ## remove trailing spaces |
| 3500 | |
3668 | |
| 3501 | ## the following is copied from the online perl manual |
3669 | ## the following is copied from the online perl manual |
| 3502 | if ($num =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/){ |
3670 | if ($num =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/){ |
| 3503 | $is_a_number = 1; |
3671 | $is_a_number = 1; |
| 3504 | } |
3672 | } |
| 3505 | |
3673 | |
|
|
3674 | if ($process_ans_hash) { |
|
|
3675 | if ($is_a_number == 1 ) { |
|
|
3676 | $rh_ans->{student_ans}=$num; |
|
|
3677 | return $rh_ans; |
|
|
3678 | } else { |
|
|
3679 | $rh_ans->{student_ans} = "Incorrect number format: You must enter a number, e.g. -6, 5.3, or 6.12E-3"; |
|
|
3680 | $rh_ans->throw_error('NUMBER', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3'); |
|
|
3681 | return $rh_ans; |
|
|
3682 | } |
|
|
3683 | } else { |
| 3506 | return $is_a_number; |
3684 | return $is_a_number; |
|
|
3685 | } |
| 3507 | } |
3686 | } |
| 3508 | |
3687 | |
| 3509 | sub is_a_fraction { |
3688 | sub is_a_fraction { |
| 3510 | |
3689 | my ($num,%options) = @_; |
| 3511 | ## does not test for validity, just for allowed characters |
3690 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
| 3512 | ## note that an integer will qualify as a fraction |
3691 | my ($rh_ans); |
| 3513 | my ($exp) = @_; |
3692 | if ($process_ans_hash) { |
|
|
3693 | $rh_ans = $num; |
|
|
3694 | $num = $rh_ans->{student_ans}; |
|
|
3695 | } |
|
|
3696 | |
| 3514 | my $is_a_fraction = 0; |
3697 | my $is_a_fraction = 0; |
| 3515 | return $is_a_fraction unless defined($exp); |
3698 | return $is_a_fraction unless defined($num); |
|
|
3699 | $num =~ s/^\s*//; ## remove initial spaces |
|
|
3700 | $num =~ s/\s*$//; ## remove trailing spaces |
|
|
3701 | |
| 3516 | if ($exp =~ /^\s*\-?\s*[\/\d\.Ee\s]*$/) { |
3702 | if ($num =~ /^\s*\-?\s*[\/\d\.Ee\s]*$/) { |
| 3517 | $is_a_fraction = 1; |
3703 | $is_a_fraction = 1; |
| 3518 | } |
3704 | } |
| 3519 | |
3705 | |
|
|
3706 | if ($process_ans_hash) { |
|
|
3707 | if ($is_a_fraction == 1 ) { |
|
|
3708 | $rh_ans->{student_ans}=$num; |
|
|
3709 | return $rh_ans; |
|
|
3710 | } else { |
|
|
3711 | $rh_ans->{student_ans} = "Not a number of fraction: You must enter a number or fraction, e.g. -6 or 7/13"; |
|
|
3712 | $rh_ans->throw_error('NUMBER', 'You must enter a number, e.g. -6, 5.3, or 6.12E-3'); |
|
|
3713 | return $rh_ans; |
|
|
3714 | } |
|
|
3715 | |
|
|
3716 | } else { |
| 3520 | return $is_a_fraction; |
3717 | return $is_a_fraction; |
|
|
3718 | } |
| 3521 | } |
3719 | } |
| 3522 | |
3720 | |
|
|
3721 | |
| 3523 | sub is_an_arithmetic_expression { |
3722 | sub is_an_arithmetic_expression { |
| 3524 | ## does not test for validity, just for allowed characters |
3723 | my ($num,%options) = @_; |
| 3525 | my ($exp) = @_; |
3724 | my $process_ans_hash = ( ref( $num ) eq 'AnswerHash' ) ? 1 : 0 ; |
|
|
3725 | my ($rh_ans); |
|
|
3726 | if ($process_ans_hash) { |
|
|
3727 | $rh_ans = $num; |
|
|
3728 | $num = $rh_ans->{student_ans}; |
|
|
3729 | } |
|
|
3730 | |
| 3526 | my $is_an_arithmetic_expression = 0; |
3731 | my $is_an_arithmetic_expression = 0; |
|
|
3732 | return $is_an_arithmetic_expression unless defined($num); |
|
|
3733 | $num =~ s/^\s*//; ## remove initial spaces |
|
|
3734 | $num =~ s/\s*$//; ## remove trailing spaces |
|
|
3735 | |
| 3527 | if ($exp =~ /^[+\-*\/\^\(\)\[\]\{\}\s\d\.Ee]*$/) { |
3736 | if ($num =~ /^[+\-*\/\^\(\)\[\]\{\}\s\d\.Ee]*$/) { |
| 3528 | $is_an_arithmetic_expression = 1; |
3737 | $is_an_arithmetic_expression = 1; |
| 3529 | } |
3738 | } |
| 3530 | |
3739 | |
|
|
3740 | if ($process_ans_hash) { |
|
|
3741 | if ($is_an_arithmetic_expression == 1 ) { |
|
|
3742 | $rh_ans->{student_ans}=$num; |
|
|
3743 | return $rh_ans; |
|
|
3744 | } else { |
|
|
3745 | |
|
|
3746 | $rh_ans->{student_ans} = "Not an arithmetic expression: You must enter an arithmetic expression, e.g. -6 or (2.3*4+5/3)^2"; |
|
|
3747 | $rh_ans->throw_error('NUMBER', 'You must enter an arithmetic expression, e.g. -6 or (2.3*4+5/3)^2'); |
|
|
3748 | return $rh_ans; |
|
|
3749 | } |
|
|
3750 | |
|
|
3751 | } else { |
| 3531 | return $is_an_arithmetic_expression; |
3752 | return $is_an_arithmetic_expression; |
|
|
3753 | } |
| 3532 | } |
3754 | } |
| 3533 | |
3755 | |
| 3534 | #replaces pi, e, and ^ with their Perl equivalents |
3756 | #replaces pi, e, and ^ with their Perl equivalents |
| 3535 | sub math_constants { |
3757 | sub math_constants { |
| 3536 | my($in) = @_; |
3758 | my($in,%options) = @_; |
|
|
3759 | my $rh_ans; |
|
|
3760 | my $process_ans_hash = ( ref( $in ) eq 'AnswerHash' ) ? 1 : 0 ; |
|
|
3761 | if ($process_ans_hash) { |
|
|
3762 | $rh_ans = $in; |
|
|
3763 | $in = $rh_ans->{student_ans}; |
|
|
3764 | } |
|
|
3765 | |
| 3537 | $in =~s/\bpi\b/(4*atan2(1,1))/ge; |
3766 | $in =~s/\bpi\b/(4*atan2(1,1))/ge; |
| 3538 | $in =~s/\be\b/(exp(1))/ge; |
3767 | $in =~s/\be\b/(exp(1))/ge; |
| 3539 | $in =~s/\^/**/g; |
3768 | $in =~s/\^/**/g; |
| 3540 | |
3769 | |
|
|
3770 | if ($process_ans_hash) { |
|
|
3771 | $rh_ans->{student_ans}=$in; |
|
|
3772 | return $rh_ans; |
|
|
3773 | } else { |
| 3541 | return $in; |
3774 | return $in; |
|
|
3775 | } |
| 3542 | } |
3776 | } |
| 3543 | |
3777 | |
| 3544 | sub clean_up_error_msg { |
3778 | sub clean_up_error_msg { |
| 3545 | my $msg = $_[0]; |
3779 | my $msg = $_[0]; |
| 3546 | $msg =~ s/^\[[^\]]*\][^:]*://; |
3780 | $msg =~ s/^\[[^\]]*\][^:]*://; |
| … | |
… | |
| 3574 | |
3808 | |
| 3575 | $out =~ s/e/E/g; # only use capital E's for exponents. Little e is for 2.71828... |
3809 | $out =~ s/e/E/g; # only use capital E's for exponents. Little e is for 2.71828... |
| 3576 | } |
3810 | } |
| 3577 | else { |
3811 | else { |
| 3578 | $out = $number; |
3812 | $out = $number; |
| 3579 | $out =~ s/e/E/g; # only use capital E's for exponents. Little e is for 2.71828... |
|
|
| 3580 | } |
3813 | } |
| 3581 | |
3814 | |
| 3582 | return $out; |
3815 | return $out; |
| 3583 | } |
3816 | } |
| 3584 | |
3817 | |
| … | |
… | |
| 3626 | warn "This option |$key1| is not recognized in this subroutine<br> ", pretty_print($rh_options) unless exists($default_options{$key1}); |
3859 | warn "This option |$key1| is not recognized in this subroutine<br> ", pretty_print($rh_options) unless exists($default_options{$key1}); |
| 3627 | } |
3860 | } |
| 3628 | } |
3861 | } |
| 3629 | foreach my $key (keys %default_options) { |
3862 | foreach my $key (keys %default_options) { |
| 3630 | if ( not defined($rh_options->{$key} ) and defined( $default_options{$key} ) ) { |
3863 | if ( not defined($rh_options->{$key} ) and defined( $default_options{$key} ) ) { |
| 3631 | $rh_options->{$key} = $default_options{$key}; # using 'defined' instead of 'exists' allows |
3864 | $rh_options->{$key} = $default_options{$key}; #this allows tol => undef to allow the tol option, but doesn't define |
| 3632 | # tol => undef to allow the tol option, but doesn't define |
|
|
| 3633 | # this key unless tol is explicitly defined. |
3865 | # this key unless tol is explicitly defined. |
| 3634 | } |
3866 | } |
| 3635 | } |
3867 | } |
| 3636 | } |
3868 | } |
| 3637 | # Use this to assign aliases for the standard options |
3869 | # Use this to assign aliases for the standard options |