| … | |
… | |
| 61 | type => 'typeString', |
61 | type => 'typeString', |
| 62 | preview_text_string => $preview_text_string, |
62 | preview_text_string => $preview_text_string, |
| 63 | preview_latex_string => $preview_latex_string |
63 | preview_latex_string => $preview_latex_string |
| 64 | |
64 | |
| 65 | |
65 | |
| 66 | $ans_hash{score} -- a number between 0 and 1 indicating |
66 | $ans_hash{score} -- a number between 0 and 1 indicating |
| 67 | whether the answer is correct. Fractions |
67 | whether the answer is correct. Fractions |
| 68 | allow the implementation of partial |
68 | allow the implementation of partial |
| 69 | credit for incorrect answers. |
69 | credit for incorrect answers. |
| 70 | $ans_hash{correct_ans} -- The correct answer, as supplied by the |
70 | $ans_hash{correct_ans} -- The correct answer, as supplied by the |
| 71 | instructor and then formatted. This can |
71 | instructor and then formatted. This can |
| … | |
… | |
| 75 | to capital letters for comparison with |
75 | to capital letters for comparison with |
| 76 | the instructors answer. For a numerical |
76 | the instructors answer. For a numerical |
| 77 | answer, it gives the evaluated answer. |
77 | answer, it gives the evaluated answer. |
| 78 | This is displayed in the section reporting |
78 | This is displayed in the section reporting |
| 79 | the results of checking the student answers. |
79 | the results of checking the student answers. |
| 80 | $ans_hash{original_student_ans} -- This is the original student answer. This is displayed |
80 | $ans_hash{original_student_ans} -- This is the original student answer. This is displayed |
| 81 | on the preview page and may be used for sticky answers. |
81 | on the preview page and may be used for sticky answers. |
| 82 | $ans_hash{ans_message} -- Any error message, or hint provided by the answer evaluator. |
82 | $ans_hash{ans_message} -- Any error message, or hint provided by the answer evaluator. |
| 83 | This is also displayed in the section reporting |
83 | This is also displayed in the section reporting |
| 84 | the results of checking the student answers. |
84 | the results of checking the student answers. |
| 85 | $ans_hash{type} -- A string indicating the type of answer evaluator. This |
85 | $ans_hash{type} -- A string indicating the type of answer evaluator. This |
| 86 | helps in preprocessing the student answer for errors. |
86 | helps in preprocessing the student answer for errors. |
| 87 | Some examples: |
87 | Some examples: |
| 88 | 'number_with_units' |
88 | 'number_with_units' |
| 89 | 'function' |
89 | 'function' |
| 90 | 'frac_number' |
90 | 'frac_number' |
| 91 | 'arith_number' |
91 | 'arith_number' |
| 92 | $ans_hash{preview_text_string} -- This typically shows how the student answer was parsed. It is |
92 | $ans_hash{preview_text_string} -- This typically shows how the student answer was parsed. It is |
| 93 | displayed on the preview page. For a student answer of 2sin(3x) |
93 | displayed on the preview page. For a student answer of 2sin(3x) |
| 94 | this would be 2*sin(3*x). For string answers it is typically the |
94 | this would be 2*sin(3*x). For string answers it is typically the |
| 95 | same as $ans_hash{student_ans}. |
95 | same as $ans_hash{student_ans}. |
| 96 | $ans_hash{preview_latex_string -- THIS IS OPTIONAL. This is latex version of the student answer |
96 | $ans_hash{preview_latex_string -- THIS IS OPTIONAL. This is latex version of the student answer |
| 97 | which is used to show a typeset view on the answer on the preview |
97 | which is used to show a typeset view on the answer on the preview |
| 98 | page. For a student answer of 2/3, this would be \frac{2}{3}. |
98 | page. For a student answer of 2/3, this would be \frac{2}{3}. |
| 99 | |
99 | |
| 100 | Technical note: the routines in this file are not actually answer evaluators. Instead, they create |
100 | Technical note: the routines in this file are not actually answer evaluators. Instead, they create |
| 101 | answer evaluators. An answer evaluator is an anonymous subroutine, referenced by a named scalar. The |
101 | answer evaluators. An answer evaluator is an anonymous subroutine, referenced by a named scalar. The |
| … | |
… | |
| 109 | BEGIN { |
109 | BEGIN { |
| 110 | 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. |
| 111 | } |
111 | } |
| 112 | |
112 | |
| 113 | |
113 | |
| 114 | my ($BR , # convenient localizations. |
114 | my ($BR , # convenient localizations. |
| 115 | $PAR , |
115 | $PAR , |
| 116 | $numRelPercentTolDefault , |
116 | $numRelPercentTolDefault , |
| 117 | $numZeroLevelDefault , |
117 | $numZeroLevelDefault , |
| 118 | $numZeroLevelTolDefault , |
118 | $numZeroLevelTolDefault , |
| 119 | $numAbsTolDefault , |
119 | $numAbsTolDefault , |
| 120 | $numFormatDefault , |
120 | $numFormatDefault , |
| 121 | $functRelPercentTolDefault , |
121 | $functRelPercentTolDefault , |
| 122 | $functZeroLevelDefault , |
122 | $functZeroLevelDefault , |
| 123 | $functZeroLevelTolDefault , |
123 | $functZeroLevelTolDefault , |
| 124 | $functAbsTolDefault , |
124 | $functAbsTolDefault , |
| 125 | $functNumOfPoints , |
125 | $functNumOfPoints , |
| 126 | $functVarDefault , |
126 | $functVarDefault , |
| 127 | $functLLimitDefault , |
127 | $functLLimitDefault , |
| 128 | $functULimitDefault , |
128 | $functULimitDefault , |
| 129 | $functMaxConstantOfIntegration |
129 | $functMaxConstantOfIntegration , |
|
|
130 | $CA |
| 130 | ); |
131 | ); |
| 131 | |
132 | |
| 132 | sub _PGanswermacros_init { |
133 | sub _PGanswermacros_init { |
| 133 | |
134 | |
| 134 | $BR = $main::BR; # convenient localizations. |
135 | $BR = $main::BR; # convenient localizations. |
| … | |
… | |
| 139 | $numRelPercentTolDefault = $main::numRelPercentTolDefault; |
140 | $numRelPercentTolDefault = $main::numRelPercentTolDefault; |
| 140 | $numZeroLevelDefault = $main::numZeroLevelDefault; |
141 | $numZeroLevelDefault = $main::numZeroLevelDefault; |
| 141 | $numZeroLevelTolDefault = $main::numZeroLevelTolDefault; |
142 | $numZeroLevelTolDefault = $main::numZeroLevelTolDefault; |
| 142 | $numAbsTolDefault = $main::numAbsTolDefault; |
143 | $numAbsTolDefault = $main::numAbsTolDefault; |
| 143 | $numFormatDefault = $main::numFormatDefault; |
144 | $numFormatDefault = $main::numFormatDefault; |
| 144 | |
|
|
| 145 | $functRelPercentTolDefault = $main::functRelPercentTolDefault; |
145 | $functRelPercentTolDefault = $main::functRelPercentTolDefault; |
| 146 | $functZeroLevelDefault = $main::functZeroLevelDefault; |
146 | $functZeroLevelDefault = $main::functZeroLevelDefault; |
| 147 | $functZeroLevelTolDefault = $main::functZeroLevelTolDefault; |
147 | $functZeroLevelTolDefault = $main::functZeroLevelTolDefault; |
| 148 | $functAbsTolDefault = $main::functAbsTolDefault; |
148 | $functAbsTolDefault = $main::functAbsTolDefault; |
| 149 | $functNumOfPoints = $main::functNumOfPoints; |
149 | $functNumOfPoints = $main::functNumOfPoints; |
| … | |
… | |
| 170 | options. In addition, there is the special case of std_num_str_cmp(), which can evaluate |
170 | options. In addition, there is the special case of std_num_str_cmp(), which can evaluate |
| 171 | both numbers and strings. |
171 | both numbers and strings. |
| 172 | |
172 | |
| 173 | Numerical Comparison Options |
173 | Numerical Comparison Options |
| 174 | |
174 | |
| 175 | correctAnswer -- This is the correct answer that the student answer will |
175 | correctAnswer -- This is the correct answer that the student answer will |
| 176 | be compared to. However, this does not mean that the |
176 | be compared to. However, this does not mean that the |
| 177 | student answer must match this exactly. How close the |
177 | student answer must match this exactly. How close the |
| 178 | student answer must be is determined by the other |
178 | student answer must be is determined by the other |
| 179 | options, especially tolerance and format. |
179 | options, especially tolerance and format. |
| 180 | |
180 | |
| … | |
… | |
| 209 | 'std' (default) -- allows any expression which evaluates |
209 | 'std' (default) -- allows any expression which evaluates |
| 210 | to a number, including those using |
210 | to a number, including those using |
| 211 | elementary functions like sin() and |
211 | elementary functions like sin() and |
| 212 | exp(), as well as the operations of |
212 | exp(), as well as the operations of |
| 213 | arithmetic (+, -, *, /, ^) |
213 | arithmetic (+, -, *, /, ^) |
| 214 | 'strict' -- only decimal numbers are allowed |
214 | 'strict' -- only decimal numbers are allowed |
| 215 | 'frac' -- whole numbers and fractions are allowed |
215 | 'frac' -- whole numbers and fractions are allowed |
| 216 | 'arith' -- arithmetic expressions are allowed, but |
216 | 'arith' -- arithmetic expressions are allowed, but |
| 217 | no functions |
217 | no functions |
| 218 | Note that all modes allow the use of "pi" and "e" as |
218 | Note that all modes allow the use of "pi" and "e" as |
| 219 | constants, and also the use of "E" to represent scientific |
219 | constants, and also the use of "E" to represent scientific |
| 220 | notation. |
220 | notation. |
| 221 | |
221 | |
| … | |
… | |
| 359 | |
359 | |
| 360 | my %options = ( 'tolerance' => $relPercentTol, |
360 | my %options = ( 'tolerance' => $relPercentTol, |
| 361 | 'format' => $format, |
361 | 'format' => $format, |
| 362 | 'zeroLevel' => $zeroLevel, |
362 | 'zeroLevel' => $zeroLevel, |
| 363 | 'zeroLevelTol' => $zeroLevelTol |
363 | 'zeroLevelTol' => $zeroLevelTol |
| 364 | ); |
364 | ); |
| 365 | |
365 | |
| 366 | set_default_options( \%options, |
366 | set_default_options( \%options, |
| 367 | 'tolType' => 'relative', |
367 | 'tolType' => 'relative', |
| 368 | 'tolerance' => $numRelPercentTolDefault, |
368 | 'tolerance' => $numRelPercentTolDefault, |
| 369 | 'mode' => 'std', |
369 | 'mode' => 'std', |
| 370 | 'format' => $numFormatDefault, |
370 | 'format' => $numFormatDefault, |
| 371 | 'relTol' => $numRelPercentTolDefault, |
371 | 'relTol' => $numRelPercentTolDefault, |
| 372 | 'zeroLevel' => $numZeroLevelDefault, |
372 | 'zeroLevel' => $numZeroLevelDefault, |
| 373 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
373 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 374 | 'debug' => 0, |
374 | 'debug' => 0, |
| 375 | ); |
375 | ); |
| 376 | |
376 | |
| 377 | num_cmp([$correctAnswer], %options); |
377 | num_cmp([$correctAnswer], %options); |
| 378 | } |
378 | } |
| 379 | |
379 | |
| 380 | ## Similar to std_num_cmp but accepts a list of numbers in the form |
380 | ## Similar to std_num_cmp but accepts a list of numbers in the form |
| 381 | ## std_num_cmp_list(relpercentTol,format,ans1,ans2,ans3,...) |
381 | ## std_num_cmp_list(relpercentTol,format,ans1,ans2,ans3,...) |
| 382 | ## format is of the form "%10.3g" or "", i.e., a format suitable for sprintf(). Use "" for default |
382 | ## format is of the form "%10.3g" or "", i.e., a format suitable for sprintf(). Use "" for default |
| 383 | ## You must enter a format and tolerance |
383 | ## You must enter a format and tolerance |
|
|
384 | |
| 384 | sub std_num_cmp_list { |
385 | sub std_num_cmp_list { |
| 385 | my ( $relPercentTol, $format, @answerList) = @_; |
386 | my ( $relPercentTol, $format, @answerList) = @_; |
| 386 | |
387 | |
| 387 | my %options = ( 'tolerance' => $relPercentTol, |
388 | my %options = ( 'tolerance' => $relPercentTol, |
| 388 | 'format' => $format, |
389 | 'format' => $format, |
| 389 | ); |
390 | ); |
| 390 | |
391 | |
| 391 | set_default_options( \%options, |
392 | set_default_options( \%options, |
| 392 | 'tolType' => 'relative', |
393 | 'tolType' => 'relative', |
| 393 | 'tolerance' => $numRelPercentTolDefault, |
394 | 'tolerance' => $numRelPercentTolDefault, |
| 394 | 'mode' => 'std', |
395 | 'mode' => 'std', |
| … | |
… | |
| 405 | |
406 | |
| 406 | sub std_num_cmp_abs { # compare numbers allowing use of elementary functions with absolute tolerance |
407 | sub std_num_cmp_abs { # compare numbers allowing use of elementary functions with absolute tolerance |
| 407 | my ( $correctAnswer, $absTol, $format) = @_; |
408 | my ( $correctAnswer, $absTol, $format) = @_; |
| 408 | my %options = ( 'tolerance' => $absTol, |
409 | my %options = ( 'tolerance' => $absTol, |
| 409 | 'format' => $format |
410 | 'format' => $format |
| 410 | ); |
411 | ); |
| 411 | |
412 | |
| 412 | set_default_options (\%options, |
413 | set_default_options (\%options, |
| 413 | 'tolType' => 'absolute', |
414 | 'tolType' => 'absolute', |
| 414 | 'tolerance' => $absTol, |
415 | 'tolerance' => $absTol, |
| 415 | 'mode' => 'std', |
416 | 'mode' => 'std', |
| 416 | 'format' => $numFormatDefault, |
417 | 'format' => $numFormatDefault, |
| 417 | 'zeroLevel' => 0, |
418 | 'zeroLevel' => 0, |
| 418 | 'zeroLevelTol' => 0, |
419 | 'zeroLevelTol' => 0, |
| 419 | 'debug' => 0, |
420 | 'debug' => 0, |
| 420 | ); |
421 | ); |
| 421 | |
422 | |
| 422 | num_cmp([$correctAnswer], %options); |
423 | num_cmp([$correctAnswer], %options); |
| 423 | } |
424 | } |
| 424 | |
425 | |
| 425 | ## See std_num_cmp_list for usage |
426 | ## See std_num_cmp_list for usage |
| … | |
… | |
| 427 | sub std_num_cmp_abs_list { |
428 | sub std_num_cmp_abs_list { |
| 428 | my ( $absTol, $format, @answerList ) = @_; |
429 | my ( $absTol, $format, @answerList ) = @_; |
| 429 | |
430 | |
| 430 | my %options = ( 'tolerance' => $absTol, |
431 | my %options = ( 'tolerance' => $absTol, |
| 431 | 'format' => $format, |
432 | 'format' => $format, |
| 432 | ); |
433 | ); |
| 433 | |
434 | |
| 434 | set_default_options( \%options, |
435 | set_default_options( \%options, |
| 435 | 'tolType' => 'absolute', |
436 | 'tolType' => 'absolute', |
| 436 | 'tolerance' => $absTol, |
437 | 'tolerance' => $absTol, |
| 437 | 'mode' => 'std', |
438 | 'mode' => 'std', |
| 438 | 'format' => $numFormatDefault, |
439 | 'format' => $numFormatDefault, |
| 439 | 'zeroLevel' => 0, |
440 | 'zeroLevel' => 0, |
| 440 | 'zeroLevelTol' => 0, |
441 | 'zeroLevelTol' => 0, |
| 441 | 'debug' => 0, |
442 | 'debug' => 0, |
| 442 | ); |
443 | ); |
| 443 | |
444 | |
| 444 | num_cmp(\@answerList, %options); |
445 | num_cmp(\@answerList, %options); |
| 445 | |
|
|
| 446 | } |
446 | } |
| 447 | |
447 | |
| 448 | sub frac_num_cmp { # only allow fractions and numbers as submitted answer |
448 | sub frac_num_cmp { # only allow fractions and numbers as submitted answer |
| 449 | |
449 | |
| 450 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
450 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 451 | |
451 | |
| 452 | my %options = ( 'tolerance' => $relPercentTol, |
452 | my %options = ( 'tolerance' => $relPercentTol, |
| 453 | 'format' => $format, |
453 | 'format' => $format, |
| 454 | 'zeroLevel' => $zeroLevel, |
454 | 'zeroLevel' => $zeroLevel, |
| 455 | 'zeroLevelTol' => $zeroLevelTol |
455 | 'zeroLevelTol' => $zeroLevelTol |
| 456 | ); |
456 | ); |
| 457 | |
457 | |
| 458 | set_default_options( \%options, |
458 | set_default_options( \%options, |
| 459 | 'tolType' => 'relative', |
459 | 'tolType' => 'relative', |
| 460 | 'tolerance' => $relPercentTol, |
460 | 'tolerance' => $relPercentTol, |
| 461 | 'mode' => 'frac', |
461 | 'mode' => 'frac', |
| 462 | 'format' => $numFormatDefault, |
462 | 'format' => $numFormatDefault, |
| 463 | 'zeroLevel' => $numZeroLevelDefault, |
463 | 'zeroLevel' => $numZeroLevelDefault, |
| 464 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
464 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 465 | 'relTol' => $numRelPercentTolDefault, |
465 | 'relTol' => $numRelPercentTolDefault, |
| 466 | 'debug' => 0, |
466 | 'debug' => 0, |
| 467 | ); |
467 | ); |
| 468 | |
468 | |
| 469 | num_cmp([$correctAnswer], %options); |
469 | num_cmp([$correctAnswer], %options); |
| 470 | } |
470 | } |
| 471 | |
471 | |
| 472 | ## See std_num_cmp_list for usage |
472 | ## See std_num_cmp_list for usage |
| 473 | sub frac_num_cmp_list { |
473 | sub frac_num_cmp_list { |
| 474 | my ( $relPercentTol, $format, @answerList ) = @_; |
474 | my ( $relPercentTol, $format, @answerList ) = @_; |
| 475 | |
475 | |
| 476 | my %options = ( 'tolerance' => $relPercentTol, |
476 | my %options = ( 'tolerance' => $relPercentTol, |
| 477 | 'format' => $format |
477 | 'format' => $format |
| 478 | ); |
478 | ); |
| 479 | |
479 | |
| 480 | set_default_options( \%options, |
480 | set_default_options( \%options, |
| 481 | 'tolType' => 'relative', |
481 | 'tolType' => 'relative', |
| 482 | 'tolerance' => $relPercentTol, |
482 | 'tolerance' => $relPercentTol, |
| 483 | 'mode' => 'frac', |
483 | 'mode' => 'frac', |
| 484 | 'format' => $numFormatDefault, |
484 | 'format' => $numFormatDefault, |
| 485 | 'zeroLevel' => $numZeroLevelDefault, |
485 | 'zeroLevel' => $numZeroLevelDefault, |
| 486 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
486 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 487 | 'relTol' => $numRelPercentTolDefault, |
487 | 'relTol' => $numRelPercentTolDefault, |
| 488 | 'debug' => 0, |
488 | 'debug' => 0, |
| 489 | ); |
489 | ); |
| 490 | |
490 | |
| 491 | num_cmp(\@answerList, %options); |
491 | num_cmp(\@answerList, %options); |
| 492 | } |
492 | } |
| 493 | |
493 | |
| 494 | sub frac_num_cmp_abs { # only allow fraction expressions as submitted answer with absolute tolerance |
494 | sub frac_num_cmp_abs { # only allow fraction expressions as submitted answer with absolute tolerance |
| 495 | my ( $correctAnswer, $absTol, $format ) = @_; |
495 | my ( $correctAnswer, $absTol, $format ) = @_; |
| 496 | |
496 | |
| 497 | my %options = ( 'tolerance' => $absTol, |
497 | my %options = ( 'tolerance' => $absTol, |
| 498 | 'format' => $format |
498 | 'format' => $format |
| 499 | ); |
499 | ); |
| 500 | |
500 | |
| 501 | set_default_options (\%options, |
501 | set_default_options (\%options, |
| 502 | 'tolType' => 'absolute', |
502 | 'tolType' => 'absolute', |
| 503 | 'tolerance' => $absTol, |
503 | 'tolerance' => $absTol, |
| 504 | 'mode' => 'frac', |
504 | 'mode' => 'frac', |
| 505 | 'format' => $numFormatDefault, |
505 | 'format' => $numFormatDefault, |
| 506 | 'zeroLevel' => 0, |
506 | 'zeroLevel' => 0, |
| 507 | 'zeroLevelTol' => 0, |
507 | 'zeroLevelTol' => 0, |
| 508 | 'debug' => 0, |
508 | 'debug' => 0, |
| 509 | ); |
509 | ); |
| 510 | |
510 | |
| 511 | num_cmp([$correctAnswer], %options); |
511 | num_cmp([$correctAnswer], %options); |
| 512 | } |
512 | } |
| 513 | |
513 | |
| 514 | ## See std_num_cmp_list for usage |
514 | ## See std_num_cmp_list for usage |
|
|
515 | |
| 515 | sub frac_num_cmp_abs_list { |
516 | sub frac_num_cmp_abs_list { |
| 516 | my ( $absTol, $format, @answerList ) = @_; |
517 | my ( $absTol, $format, @answerList ) = @_; |
| 517 | |
518 | |
| 518 | my %options = ( 'tolerance' => $absTol, |
519 | my %options = ( 'tolerance' => $absTol, |
| 519 | 'format' => $format |
520 | 'format' => $format |
| 520 | ); |
521 | ); |
| 521 | |
522 | |
| 522 | set_default_options (\%options, |
523 | set_default_options (\%options, |
| 523 | 'tolType' => 'absolute', |
524 | 'tolType' => 'absolute', |
| 524 | 'tolerance' => $absTol, |
525 | 'tolerance' => $absTol, |
| 525 | 'mode' => 'frac', |
526 | 'mode' => 'frac', |
| 526 | 'format' => $numFormatDefault, |
527 | 'format' => $numFormatDefault, |
| 527 | 'zeroLevel' => 0, |
528 | 'zeroLevel' => 0, |
| 528 | 'zeroLevelTol' => 0, |
529 | 'zeroLevelTol' => 0, |
| 529 | 'debug' => 0, |
530 | 'debug' => 0, |
| 530 | ); |
531 | ); |
| 531 | |
532 | |
| 532 | num_cmp(\@answerList, %options); |
533 | num_cmp(\@answerList, %options); |
| 533 | } |
534 | } |
| 534 | |
535 | |
| 535 | |
536 | |
| … | |
… | |
| 539 | |
540 | |
| 540 | my %options = ( 'tolerance' => $relPercentTol, |
541 | my %options = ( 'tolerance' => $relPercentTol, |
| 541 | 'format' => $format, |
542 | 'format' => $format, |
| 542 | 'zeroLevel' => $zeroLevel, |
543 | 'zeroLevel' => $zeroLevel, |
| 543 | 'zeroLevelTol' => $zeroLevelTol |
544 | 'zeroLevelTol' => $zeroLevelTol |
| 544 | ); |
545 | ); |
| 545 | |
546 | |
| 546 | set_default_options( \%options, |
547 | set_default_options( \%options, |
| 547 | 'tolType' => 'relative', |
548 | 'tolType' => 'relative', |
| 548 | 'tolerance' => $relPercentTol, |
549 | 'tolerance' => $relPercentTol, |
| 549 | 'mode' => 'arith', |
550 | 'mode' => 'arith', |
| 550 | 'format' => $numFormatDefault, |
551 | 'format' => $numFormatDefault, |
| 551 | 'zeroLevel' => $numZeroLevelDefault, |
552 | 'zeroLevel' => $numZeroLevelDefault, |
| 552 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
553 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 553 | 'relTol' => $numRelPercentTolDefault, |
554 | 'relTol' => $numRelPercentTolDefault, |
| 554 | 'debug' => 0, |
555 | 'debug' => 0, |
| 555 | ); |
556 | ); |
| 556 | |
557 | |
| 557 | num_cmp([$correctAnswer], %options); |
558 | num_cmp([$correctAnswer], %options); |
| 558 | } |
559 | } |
| 559 | |
560 | |
| 560 | ## See std_num_cmp_list for usage |
561 | ## See std_num_cmp_list for usage |
| 561 | sub arith_num_cmp_list { |
562 | sub arith_num_cmp_list { |
| 562 | my ( $relPercentTol, $format, @answerList ) = @_; |
563 | my ( $relPercentTol, $format, @answerList ) = @_; |
| 563 | |
564 | |
| 564 | my %options = ( 'tolerance' => $relPercentTol, |
565 | my %options = ( 'tolerance' => $relPercentTol, |
| 565 | 'format' => $format, |
566 | 'format' => $format, |
| 566 | ); |
567 | ); |
| 567 | |
568 | |
| 568 | set_default_options( \%options, |
569 | set_default_options( \%options, |
| 569 | 'tolType' => 'relative', |
570 | 'tolType' => 'relative', |
| 570 | 'tolerance' => $relPercentTol, |
571 | 'tolerance' => $relPercentTol, |
| 571 | 'mode' => 'arith', |
572 | 'mode' => 'arith', |
| 572 | 'format' => $numFormatDefault, |
573 | 'format' => $numFormatDefault, |
| 573 | 'zeroLevel' => $numZeroLevelDefault, |
574 | 'zeroLevel' => $numZeroLevelDefault, |
| 574 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
575 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 575 | 'relTol' => $numRelPercentTolDefault, |
576 | 'relTol' => $numRelPercentTolDefault, |
| 576 | 'debug' => 0, |
577 | 'debug' => 0, |
| 577 | ); |
578 | ); |
|
|
579 | |
| 578 | num_cmp(\@answerList, %options); |
580 | num_cmp(\@answerList, %options); |
| 579 | } |
581 | } |
| 580 | |
582 | |
| 581 | sub arith_num_cmp_abs { # only allow arithmetic expressions as submitted answer with absolute tolerance |
583 | sub arith_num_cmp_abs { # only allow arithmetic expressions as submitted answer with absolute tolerance |
| 582 | my ( $correctAnswer, $absTol, $format ) = @_; |
584 | my ( $correctAnswer, $absTol, $format ) = @_; |
| 583 | |
585 | |
| 584 | my %options = ( 'tolerance' => $absTol, |
586 | my %options = ( 'tolerance' => $absTol, |
| 585 | 'format' => $format |
587 | 'format' => $format |
| 586 | ); |
|
|
| 587 | |
588 | ); |
| 588 | set_default_options (\%options, |
|
|
| 589 | 'tolType' => 'absolute', |
|
|
| 590 | 'tolerance' => $absTol, |
|
|
| 591 | 'mode' => 'arith', |
|
|
| 592 | 'format' => $numFormatDefault, |
|
|
| 593 | 'zeroLevel' => 0, |
|
|
| 594 | 'zeroLevelTol' => 0, |
|
|
| 595 | 'debug' => 0, |
|
|
| 596 | ); |
|
|
| 597 | num_cmp([$correctAnswer], %options); |
|
|
| 598 | } |
|
|
| 599 | |
|
|
| 600 | ## See std_num_cmp_list for usage |
|
|
| 601 | sub arith_num_cmp_abs_list { |
|
|
| 602 | my ( $absTol, $format, @answerList ) = @_; |
|
|
| 603 | |
|
|
| 604 | my %options = ( 'tolerance' => $absTol, |
|
|
| 605 | 'format' => $format |
|
|
| 606 | ); |
|
|
| 607 | |
589 | |
| 608 | set_default_options (\%options, |
590 | set_default_options (\%options, |
| 609 | 'tolType' => 'absolute', |
591 | 'tolType' => 'absolute', |
| 610 | 'tolerance' => $absTol, |
592 | 'tolerance' => $absTol, |
| 611 | 'mode' => 'arith', |
593 | 'mode' => 'arith', |
| 612 | 'format' => $numFormatDefault, |
594 | 'format' => $numFormatDefault, |
| 613 | 'zeroLevel' => 0, |
595 | 'zeroLevel' => 0, |
| 614 | 'zeroLevelTol' => 0, |
596 | 'zeroLevelTol' => 0, |
| 615 | 'debug' => 0, |
597 | 'debug' => 0, |
| 616 | ); |
598 | ); |
|
|
599 | |
|
|
600 | num_cmp([$correctAnswer], %options); |
|
|
601 | } |
|
|
602 | |
|
|
603 | ## See std_num_cmp_list for usage |
|
|
604 | sub arith_num_cmp_abs_list { |
|
|
605 | my ( $absTol, $format, @answerList ) = @_; |
|
|
606 | |
|
|
607 | my %options = ( 'tolerance' => $absTol, |
|
|
608 | 'format' => $format |
|
|
609 | ); |
|
|
610 | |
|
|
611 | set_default_options (\%options, |
|
|
612 | 'tolType' => 'absolute', |
|
|
613 | 'tolerance' => $absTol, |
|
|
614 | 'mode' => 'arith', |
|
|
615 | 'format' => $numFormatDefault, |
|
|
616 | 'zeroLevel' => 0, |
|
|
617 | 'zeroLevelTol' => 0, |
|
|
618 | 'debug' => 0, |
|
|
619 | ); |
|
|
620 | |
| 617 | num_cmp(\@answerList, %options); |
621 | num_cmp(\@answerList, %options); |
| 618 | |
|
|
| 619 | } |
622 | } |
| 620 | |
623 | |
| 621 | sub strict_num_cmp { # only allow numbers as submitted answer |
624 | sub strict_num_cmp { # only allow numbers as submitted answer |
| 622 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
625 | my ( $correctAnswer, $relPercentTol, $format, $zeroLevel, $zeroLevelTol ) = @_; |
| 623 | |
626 | |
| … | |
… | |
| 636 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
639 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 637 | 'relTol' => $numRelPercentTolDefault, |
640 | 'relTol' => $numRelPercentTolDefault, |
| 638 | 'debug' => 0, |
641 | 'debug' => 0, |
| 639 | ); |
642 | ); |
| 640 | num_cmp([$correctAnswer], %options); |
643 | num_cmp([$correctAnswer], %options); |
|
|
644 | |
| 641 | } |
645 | } |
| 642 | |
646 | |
| 643 | ## See std_num_cmp_list for usage |
647 | ## See std_num_cmp_list for usage |
| 644 | sub strict_num_cmp_list { # compare numbers |
648 | sub strict_num_cmp_list { # compare numbers |
| 645 | my ( $relPercentTol, $format, @answerList ) = @_; |
649 | my ( $relPercentTol, $format, @answerList ) = @_; |
| 646 | |
650 | |
| 647 | my %options = ( 'tolerance' => $relPercentTol, |
651 | my %options = ( 'tolerance' => $relPercentTol, |
| 648 | 'format' => $format, |
652 | 'format' => $format, |
| 649 | ); |
653 | ); |
| 650 | |
654 | |
| 651 | set_default_options( \%options, |
655 | set_default_options( \%options, |
| 652 | 'tolType' => 'relative', |
656 | 'tolType' => 'relative', |
| 653 | 'tolerance' => $relPercentTol, |
657 | 'tolerance' => $relPercentTol, |
| 654 | 'mode' => 'strict', |
658 | 'mode' => 'strict', |
| 655 | 'format' => $numFormatDefault, |
659 | 'format' => $numFormatDefault, |
| 656 | 'zeroLevel' => $numZeroLevelDefault, |
660 | 'zeroLevel' => $numZeroLevelDefault, |
| 657 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
661 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 658 | 'relTol' => $numRelPercentTolDefault, |
662 | 'relTol' => $numRelPercentTolDefault, |
| 659 | 'debug' => 0, |
663 | 'debug' => 0, |
| 660 | ); |
664 | ); |
|
|
665 | |
| 661 | num_cmp(\@answerList, %options); |
666 | num_cmp(\@answerList, %options); |
| 662 | } |
667 | } |
| 663 | |
668 | |
| 664 | |
669 | |
| 665 | sub strict_num_cmp_abs { # only allow numbers as submitted answer with absolute tolerance |
670 | sub strict_num_cmp_abs { # only allow numbers as submitted answer with absolute tolerance |
| 666 | |
|
|
| 667 | my ( $correctAnswer, $absTol, $format ) = @_; |
671 | my ( $correctAnswer, $absTol, $format ) = @_; |
| 668 | |
672 | |
| 669 | my %options = ( 'tolerance' => $absTol, |
673 | my %options = ( 'tolerance' => $absTol, |
| 670 | 'format' => $format |
674 | 'format' => $format |
| 671 | ); |
675 | ); |
| 672 | |
676 | |
| 673 | set_default_options (\%options, |
677 | set_default_options (\%options, |
| 674 | 'tolType' => 'absolute', |
678 | 'tolType' => 'absolute', |
| 675 | 'tolerance' => $absTol, |
679 | 'tolerance' => $absTol, |
| 676 | 'mode' => 'strict', |
680 | 'mode' => 'strict', |
| 677 | 'format' => $numFormatDefault, |
681 | 'format' => $numFormatDefault, |
| 678 | 'zeroLevel' => 0, |
682 | 'zeroLevel' => 0, |
| 679 | 'zeroLevelTol' => 0, |
683 | 'zeroLevelTol' => 0, |
| 680 | 'debug' => 0, |
684 | 'debug' => 0, |
| 681 | ); |
685 | ); |
| 682 | |
|
|
| 683 | num_cmp([$correctAnswer], %options); |
686 | num_cmp([$correctAnswer], %options); |
| 684 | |
687 | |
| 685 | } |
688 | } |
| 686 | |
689 | |
| 687 | ## See std_num_cmp_list for usage |
690 | ## See std_num_cmp_list for usage |
| 688 | sub strict_num_cmp_abs_list { # compare numbers |
691 | sub strict_num_cmp_abs_list { # compare numbers |
| 689 | my ( $absTol, $format, @answerList ) = @_; |
692 | my ( $absTol, $format, @answerList ) = @_; |
|
|
693 | |
| 690 | my %options = ( 'tolerance' => $absTol, |
694 | my %options = ( 'tolerance' => $absTol, |
| 691 | 'format' => $format |
695 | 'format' => $format |
| 692 | ); |
696 | ); |
| 693 | |
697 | |
| 694 | set_default_options (\%options, |
698 | set_default_options (\%options, |
| 695 | 'tolType' => 'absolute', |
699 | 'tolType' => 'absolute', |
| 696 | 'tolerance' => $absTol, |
700 | 'tolerance' => $absTol, |
| 697 | 'mode' => 'strict', |
701 | 'mode' => 'strict', |
| 698 | 'format' => $numFormatDefault, |
702 | 'format' => $numFormatDefault, |
| 699 | 'zeroLevel' => 0, |
703 | 'zeroLevel' => 0, |
| 700 | 'zeroLevelTol' => 0, |
704 | 'zeroLevelTol' => 0, |
| 701 | 'debug' => 0, |
705 | 'debug' => 0, |
| 702 | ); |
706 | ); |
| 703 | |
707 | |
| 704 | num_cmp(\@answerList, %options); |
708 | num_cmp(\@answerList, %options); |
| 705 | |
|
|
| 706 | } |
709 | } |
| 707 | |
710 | |
|
|
711 | ## sub numerical_compare_with_units |
| 708 | ## Compares a number with units |
712 | ## Compares a number with units |
| 709 | ## Deprecated; use num_cmp() |
713 | ## Deprecated; use num_cmp() |
| 710 | ## |
714 | ## |
| 711 | ## IN: a string which includes the numerical answer and the units |
715 | ## IN: a string which includes the numerical answer and the units |
| 712 | ## a hash with the following keys (all optional): |
716 | ## a hash with the following keys (all optional): |
| 713 | ## mode -- 'std', 'frac', 'arith', or 'strict' |
717 | ## mode -- 'std', 'frac', 'arith', or 'strict' |
| 714 | ## format -- the format to use when displaying the answer |
718 | ## format -- the format to use when displaying the answer |
| 715 | ## tol -- an absolute tolerance, or |
719 | ## tol -- an absolute tolerance, or |
| 716 | ## relTol -- a relative tolerance |
720 | ## relTol -- a relative tolerance |
| 717 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
721 | ## zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
| 718 | ## zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero |
722 | ## zeroLevelTol -- absolute tolerance to allow when correct answer is close to zero |
| 719 | |
723 | |
| 720 | # This mode is depricated. send input through num_cmp -- it can handle units. |
724 | # This mode is depricated. send input through num_cmp -- it can handle units. |
| 721 | |
725 | |
| 722 | sub numerical_compare_with_units { |
726 | sub numerical_compare_with_units { |
| 723 | my $correct_answer = shift; # the answer is a string which includes both the numerical answer and the units. |
727 | my $correct_answer = shift; # the answer is a string which includes both the numerical answer and the units. |
| 724 | my %options = @_; # all of the other inputs are (key value) pairs |
728 | my %options = @_; # all of the other inputs are (key value) pairs |
| 725 | |
729 | |
| 726 | # Prepare the correct answer |
730 | # Prepare the correct answer |
| 727 | $correct_answer = str_filters( $correct_answer, 'trim_whitespace' ); |
731 | $correct_answer = str_filters( $correct_answer, 'trim_whitespace' ); |
| 728 | |
732 | |
| … | |
… | |
| 781 | |
785 | |
| 782 | ANS( num_cmp( answer or answer_array_ref, options_hash ) ); |
786 | ANS( num_cmp( answer or answer_array_ref, options_hash ) ); |
| 783 | |
787 | |
| 784 | 1. the correct answer, or a reference to an array of correct answers |
788 | 1. the correct answer, or a reference to an array of correct answers |
| 785 | 2. a hash with the following keys (all optional): |
789 | 2. a hash with the following keys (all optional): |
| 786 | mode -- 'std' (default) (allows any expression evaluating to a number) |
790 | mode -- 'std' (default) (allows any expression evaluating to a number) |
| 787 | 'strict' (only numbers are allowed) |
791 | 'strict' (only numbers are allowed) |
| 788 | 'frac' (fractions are allowed) |
792 | 'frac' (fractions are allowed) |
| 789 | 'arith' (arithmetic expressions allowed) |
793 | 'arith' (arithmetic expressions allowed) |
| 790 | format -- '%0.5f#' (default); defines formatting for the correct answer |
794 | format -- '%0.5f#' (default); defines formatting for the correct answer |
| 791 | tol -- an absolute tolerance, or |
795 | tol -- an absolute tolerance, or |
| 792 | relTol -- a relative tolerance |
796 | relTol -- a relative tolerance |
| 793 | units -- the units to use for the answer(s) |
797 | units -- the units to use for the answer(s) |
| 794 | strings -- a reference to an array of strings which are valid |
798 | strings -- a reference to an array of strings which are valid |
| 795 | answers (works like std_num_str_cmp() ) |
799 | answers (works like std_num_str_cmp() ) |
| 796 | zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
800 | zeroLevel -- if the correct answer is this close to zero, then zeroLevelTol applies |
| 797 | zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
801 | zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
|
|
802 | |
|
|
803 | debug -- if set to 1, provides verbose listing of hash entries throughout fliters. |
| 798 | |
804 | |
| 799 | Returns an answer evaluator, or (if given a reference to an array of |
805 | Returns an answer evaluator, or (if given a reference to an array of |
| 800 | answers), a list of answer evaluators. Note that a reference to an array of |
806 | answers), a list of answer evaluators. Note that a reference to an array of |
| 801 | answers results is just a shortcut to writing a separate cum_cmp() for each |
807 | answers results is just a shortcut to writing a separate cum_cmp() for each |
| 802 | answer. It does not mean that any of those answers are considered correct |
808 | answer. It does not mean that any of those answers are considered correct |
| … | |
… | |
| 812 | recognized as valid answers |
818 | recognized as valid answers |
| 813 | |
819 | |
| 814 | =cut |
820 | =cut |
| 815 | |
821 | |
| 816 | sub num_cmp { |
822 | sub num_cmp { |
| 817 | my $correctAnswer = shift @_; |
823 | my $correctAnswer = shift @_; |
|
|
824 | $CA = $correctAnswer; |
| 818 | my @opt = @_; |
825 | my @opt = @_; |
| 819 | my %out_options; |
826 | my %out_options; |
| 820 | |
827 | |
| 821 | ######################################################################### |
828 | ######################################################################### |
| 822 | # Retain this first check for backword compatibility. Allows input of the form |
829 | # Retain this first check for backword compatibility. Allows input of the form |
| … | |
… | |
| 833 | 'tolType' => 'relative', |
840 | 'tolType' => 'relative', |
| 834 | 'tolerance' => 1, |
841 | 'tolerance' => 1, |
| 835 | 'reltol' => undef, #alternate spelling |
842 | 'reltol' => undef, #alternate spelling |
| 836 | 'unit' => undef, #alternate spelling |
843 | 'unit' => undef, #alternate spelling |
| 837 | 'debug' => 0 |
844 | 'debug' => 0 |
| 838 | |
845 | ); |
| 839 | ); |
|
|
| 840 | |
846 | |
| 841 | my @output_list; |
847 | my @output_list; |
| 842 | my( $relPercentTol, $format, $zeroLevel, $zeroLevelTol) = @opt; |
848 | my( $relPercentTol, $format, $zeroLevel, $zeroLevelTol) = @opt; |
| 843 | |
849 | |
| 844 | unless( ref($correctAnswer) eq 'ARRAY' || scalar( @opt ) == 0 || |
850 | unless( ref($correctAnswer) eq 'ARRAY' || scalar( @opt ) == 0 || |
| 845 | ( defined($opt[0]) and exists $known_options{$opt[0]} ) ) { |
851 | ( defined($opt[0]) and exists $known_options{$opt[0]} ) ) { |
| 846 | # unless the first parameter is a list of arrays |
852 | # unless the first parameter is a list of arrays |
| 847 | # or the second parameter is a known option or |
853 | # or the second parameter is a known option or |
| 848 | # no options were used, |
854 | # no options were used, |
| 849 | # use the old num_cmp which does not use options, but has inputs |
855 | # use the old num_cmp which does not use options, but has inputs |
| 850 | # $relPercentTol,$format,$zeroLevel,$zeroLevelTol |
856 | # $relPercentTol,$format,$zeroLevel,$zeroLevelTol |
| 851 | warn "This method of using num_cmp() is deprecated. Please rewrite this" . |
857 | warn "This method of using num_cmp() is deprecated. Please rewrite this" . |
| 852 | " problem using the options style of parameter passing (or" . |
858 | " problem using the options style of parameter passing (or" . |
| 853 | " check that your first option is spelled correctly)."; |
859 | " check that your first option is spelled correctly)."; |
| 854 | |
860 | |
| 855 | |
|
|
| 856 | %out_options = ( 'relTol' => $relPercentTol, |
861 | %out_options = ( 'relTol' => $relPercentTol, |
| 857 | 'format' => $format, |
862 | 'format' => $format, |
| 858 | 'zeroLevel' => $zeroLevel, |
863 | 'zeroLevel' => $zeroLevel, |
| 859 | 'zeroLevelTol' => $zeroLevelTol, |
864 | 'zeroLevelTol' => $zeroLevelTol, |
| 860 | 'mode' => 'std' |
865 | 'mode' => 'std' |
| 861 | ); |
866 | ); |
| 862 | } |
867 | } |
| 863 | |
868 | |
| 864 | ######################################################################### |
869 | ######################################################################### |
| 865 | # Now handle the options assuming they are entered in the form |
870 | # Now handle the options assuming they are entered in the form |
| 866 | # num_cmp($ans, relTol=>1, format=>'%0.5f') |
871 | # num_cmp($ans, relTol=>1, format=>'%0.5f') |
| … | |
… | |
| 881 | 'units' => undef, |
886 | 'units' => undef, |
| 882 | 'strings' => undef, |
887 | 'strings' => undef, |
| 883 | 'zeroLevel' => $numZeroLevelDefault, |
888 | 'zeroLevel' => $numZeroLevelDefault, |
| 884 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
889 | 'zeroLevelTol' => $numZeroLevelTolDefault, |
| 885 | 'debug' => 0, |
890 | 'debug' => 0, |
| 886 | ); |
891 | ); |
| 887 | |
892 | |
| 888 | # can't use both units and strings |
893 | # can't use both units and strings |
| 889 | if( defined( $out_options{'units'} ) && defined( $out_options{'strings'} ) ) { |
894 | if( defined( $out_options{'units'} ) && defined( $out_options{'strings'} ) ) { |
| 890 | warn "Can't use both 'units' and 'strings' in the same problem " . |
895 | warn "Can't use both 'units' and 'strings' in the same problem " . |
| 891 | "(check your parameters to num_cmp() )"; |
896 | "(check your parameters to num_cmp() )"; |
| … | |
… | |
| 908 | } |
913 | } |
| 909 | else { push( @ans_list, $correctAnswer ); |
914 | else { push( @ans_list, $correctAnswer ); |
| 910 | } |
915 | } |
| 911 | |
916 | |
| 912 | # produce answer evaluators |
917 | # produce answer evaluators |
| 913 | foreach my $ans (@ans_list) { |
918 | foreach my $ans (@ans_list) { |
| 914 | if( defined( $out_options{'units'} ) ) { |
919 | if( defined( $out_options{'units'} ) ) { |
| 915 | $ans = "$ans $out_options{'units'}"; |
920 | $ans = "$ans $out_options{'units'}"; |
| 916 | |
921 | |
| 917 | push( @output_list, NUM_CMP( 'correctAnswer' => $ans, |
922 | push( @output_list, NUM_CMP( 'correctAnswer' => $ans, |
| 918 | 'tolerance' => $out_options{tolerance}, |
923 | 'tolerance' => $out_options{tolerance}, |
| … | |
… | |
| 921 | 'mode' => $out_options{'mode'}, |
926 | 'mode' => $out_options{'mode'}, |
| 922 | 'zeroLevel' => $out_options{'zeroLevel'}, |
927 | 'zeroLevel' => $out_options{'zeroLevel'}, |
| 923 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
928 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
| 924 | 'debug' => $out_options{'debug'}, |
929 | 'debug' => $out_options{'debug'}, |
| 925 | 'units' => $out_options{'units'}, |
930 | 'units' => $out_options{'units'}, |
| 926 | ) |
|
|
| 927 | ); |
931 | ) |
| 928 | } |
932 | ); |
|
|
933 | } |
| 929 | elsif( defined( $out_options{'strings'} ) ) { |
934 | elsif( defined( $out_options{'strings'} ) ) { |
| 930 | #if( defined $out_options{'tol'} ) { |
935 | #if( defined $out_options{'tol'} ) { |
| 931 | # warn "You are using 'tol' (for absolute tolerance) with a num/str " . |
936 | # warn "You are using 'tol' (for absolute tolerance) with a num/str " . |
| 932 | # "compare, which currently only uses relative tolerance. The default " . |
937 | # "compare, which currently only uses relative tolerance. The default " . |
| 933 | # "tolerance will be used."; |
938 | # "tolerance will be used."; |
| … | |
… | |
| 940 | 'mode' => $out_options{'mode'}, |
945 | 'mode' => $out_options{'mode'}, |
| 941 | 'zeroLevel' => $out_options{'zeroLevel'}, |
946 | 'zeroLevel' => $out_options{'zeroLevel'}, |
| 942 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
947 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
| 943 | 'debug' => $out_options{'debug'}, |
948 | 'debug' => $out_options{'debug'}, |
| 944 | 'strings' => $out_options{'strings'}, |
949 | 'strings' => $out_options{'strings'}, |
| 945 | ) |
|
|
| 946 | ); |
950 | ) |
| 947 | } else { |
951 | ); |
| 948 | push(@output_list, |
952 | } else { push(@output_list, |
| 949 | NUM_CMP( 'correctAnswer' => $ans, |
953 | NUM_CMP( 'correctAnswer' => $ans, |
| 950 | 'tolerance' => $out_options{tolerance}, |
954 | 'tolerance' => $out_options{tolerance}, |
| 951 | 'tolType' => $out_options{tolType}, |
955 | 'tolType' => $out_options{tolType}, |
| 952 | 'format' => $out_options{'format'}, |
956 | 'format' => $out_options{'format'}, |
| 953 | 'mode' => $out_options{'mode'}, |
957 | 'mode' => $out_options{'mode'}, |
| 954 | 'zeroLevel' => $out_options{'zeroLevel'}, |
958 | 'zeroLevel' => $out_options{'zeroLevel'}, |
| 955 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
959 | 'zeroLevelTol' => $out_options{'zeroLevelTol'}, |
| 956 | 'debug' => $out_options{'debug'}, |
960 | 'debug' => $out_options{'debug'}, |
| 957 | ), |
961 | ), |
| 958 | ); |
962 | ); |
| 959 | } |
963 | } |
| 960 | } |
964 | } |
| 961 | |
965 | |
| 962 | return @output_list; |
966 | return @output_list; |
| 963 | } |
967 | } |
| … | |
… | |
| 977 | |
981 | |
| 978 | my $correctAnswer = $num_params{'correctAnswer'}; |
982 | my $correctAnswer = $num_params{'correctAnswer'}; |
| 979 | my $format = $num_params{'format'}; |
983 | my $format = $num_params{'format'}; |
| 980 | my $mode = $num_params{'mode'}; |
984 | my $mode = $num_params{'mode'}; |
| 981 | |
985 | |
| 982 | # my $tol = $num_params{'tolerance'}; |
|
|
| 983 | # my $tolType = $num_params{'tolType'}; |
|
|
| 984 | # my $zeroLevel = $num_params{'zeroLevel'}; |
|
|
| 985 | # my $zeroLevelTol = $num_params{'zeroLevelTol'}; |
|
|
| 986 | |
|
|
| 987 | if( $num_params{tolType} eq 'relative' ) { |
986 | if( $num_params{tolType} eq 'relative' ) { |
| 988 | $num_params{'tolerance'} = .01*$num_params{'tolerance'}; |
987 | $num_params{'tolerance'} = .01*$num_params{'tolerance'}; |
| 989 | } |
988 | } |
| 990 | |
|
|
| 991 | #$format = $numFormatDefault unless defined $format; |
|
|
| 992 | #$mode = 'std' unless defined $mode; |
|
|
| 993 | #$zeroLevel = $numZeroLevelDefault unless defined $zeroLevel; |
|
|
| 994 | #$zeroLevelTol = $numZeroLevelTolDefault unless defined $zeroLevelTol; |
|
|
| 995 | |
989 | |
| 996 | my $formattedCorrectAnswer; |
990 | my $formattedCorrectAnswer; |
| 997 | my $correct_units; |
991 | my $correct_units; |
| 998 | my $correct_num_answer; |
992 | my $correct_num_answer; |
| 999 | my %correct_units; |
993 | my %correct_units; |
| … | |
… | |
| 1012 | } |
1006 | } |
| 1013 | # $formattedCorrectAnswer = spf($correct_num_answer,$num_params{'format'}) . " $correct_units"; |
1007 | # $formattedCorrectAnswer = spf($correct_num_answer,$num_params{'format'}) . " $correct_units"; |
| 1014 | $formattedCorrectAnswer = prfmt($correct_num_answer,$num_params{'format'}) . " $correct_units"; |
1008 | $formattedCorrectAnswer = prfmt($correct_num_answer,$num_params{'format'}) . " $correct_units"; |
| 1015 | |
1009 | |
| 1016 | } elsif (defined($num_params{strings}) && $num_params{strings}) { |
1010 | } elsif (defined($num_params{strings}) && $num_params{strings}) { |
| 1017 | |
|
|
| 1018 | my $legalString = ''; |
1011 | my $legalString = ''; |
| 1019 | my @legalStrings = @{$num_params{strings}}; |
1012 | my @legalStrings = @{$num_params{strings}}; |
| 1020 | $correct_num_answer = $correctAnswer; |
1013 | $correct_num_answer = $correctAnswer; |
| 1021 | $formattedCorrectAnswer = $correctAnswer; |
1014 | $formattedCorrectAnswer = $correctAnswer; |
| 1022 | foreach $legalString (@legalStrings) { |
1015 | foreach $legalString (@legalStrings) { |
| 1023 | if ( uc($correctAnswer) eq uc($legalString) ) { |
1016 | if ( uc($correctAnswer) eq uc($legalString) ) { |
| 1024 | $corrAnswerIsString = 1; |
1017 | $corrAnswerIsString = 1; |
| 1025 | last; |
1018 | last; |
| 1026 | } |
1019 | } |
| 1027 | } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric |
1020 | } ## at this point $corrAnswerIsString = 0 iff correct answer is numeric |
| 1028 | |
|
|
| 1029 | } else { |
1021 | } else { |
| 1030 | $correct_num_answer = $correctAnswer; |
1022 | $correct_num_answer = $correctAnswer; |
| 1031 | $formattedCorrectAnswer = prfmt( $correctAnswer, $num_params{'format'} ); |
1023 | $formattedCorrectAnswer = prfmt( $correctAnswer, $num_params{'format'} ); |
| 1032 | } |
1024 | } |
| 1033 | |
1025 | |
| … | |
… | |
| 1104 | # |
1096 | # |
| 1105 | ############################################################################### |
1097 | ############################################################################### |
| 1106 | |
1098 | |
| 1107 | $answer_evaluator->install_post_filter(\&fix_answers_for_display); |
1099 | $answer_evaluator->install_post_filter(\&fix_answers_for_display); |
| 1108 | |
1100 | |
| 1109 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; |
1101 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; |
| 1110 | return $rh_ans unless $rh_ans->catch_error('EVAL'); |
1102 | return $rh_ans unless $rh_ans->catch_error('EVAL'); |
| 1111 | $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message}; |
1103 | $rh_ans->{student_ans} = $rh_ans->{original_student_ans}. ' '. $rh_ans->{error_message}; |
| 1112 | $rh_ans->clear_error('EVAL'); } ); |
1104 | $rh_ans->clear_error('EVAL'); } ); |
| 1113 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } ); |
1105 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); } ); |
| 1114 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('UNITS'); } ); |
1106 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('UNITS'); } ); |
| 1115 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('NUMBER'); } ); |
1107 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('NUMBER'); } ); |
| 1116 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } ); |
1108 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('STRING'); } ); |
| 1117 | |
|
|
| 1118 | |
|
|
| 1119 | $answer_evaluator; |
1109 | $answer_evaluator; |
| 1120 | } |
1110 | } |
| 1121 | |
1111 | |
| 1122 | sub fix_answers_for_display { |
1112 | sub fix_answers_for_display { |
| 1123 | my ($rh_ans, %options) = @_; |
1113 | my ($rh_ans, %options) = @_; |
| … | |
… | |
| 1126 | } |
1116 | } |
| 1127 | if (defined ($rh_ans->{student_units})) { |
1117 | if (defined ($rh_ans->{student_units})) { |
| 1128 | $rh_ans->{student_ans} = $rh_ans->{student_ans}. ' '. $rh_ans->{student_units}; |
1118 | $rh_ans->{student_ans} = $rh_ans->{student_ans}. ' '. $rh_ans->{student_units}; |
| 1129 | } |
1119 | } |
| 1130 | $rh_ans->{correct_ans} = $rh_ans->{original_correct_ans}; |
1120 | $rh_ans->{correct_ans} = $rh_ans->{original_correct_ans}; |
|
|
1121 | |
| 1131 | $rh_ans; |
1122 | $rh_ans; |
| 1132 | } |
1123 | } |
| 1133 | |
1124 | |
| 1134 | sub evaluatesToNumber { |
1125 | sub evaluatesToNumber { |
| 1135 | my ($rh_ans, %options) = @_; |
1126 | my ($rh_ans, %options) = @_; |
| 1136 | if (is_a_numeric_expression($rh_ans->{student_ans})) { |
1127 | if (is_a_numeric_expression($rh_ans->{student_ans})) { |
| 1137 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans}); |
1128 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans}); |
| 1138 | if ($PG_eval_errors) { # this if statement should never be run |
1129 | if ($PG_eval_errors) { # this if statement should never be run |
| 1139 | # change nothing |
1130 | # change nothing |
| 1140 | } else { |
1131 | } else { |
| 1141 | # change this |
1132 | # change this |
| 1142 | $rh_ans->{student_ans} = prfmt($inVal,$options{format}); |
1133 | $rh_ans->{student_ans} = prfmt($inVal,$options{format}); |
| 1143 | } |
1134 | } |
| 1144 | } |
1135 | } |
| 1145 | $rh_ans; |
1136 | $rh_ans; |
| … | |
… | |
| 1151 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($testString); |
1142 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($testString); |
| 1152 | if ($PG_eval_errors) { |
1143 | if ($PG_eval_errors) { |
| 1153 | $is_a_numeric_expression = 0; |
1144 | $is_a_numeric_expression = 0; |
| 1154 | } else { |
1145 | } else { |
| 1155 | $is_a_numeric_expression = 1; |
1146 | $is_a_numeric_expression = 1; |
| 1156 | |
|
|
| 1157 | } |
1147 | } |
| 1158 | |
|
|
| 1159 | $is_a_numeric_expression; |
1148 | $is_a_numeric_expression; |
| 1160 | } |
1149 | } |
| 1161 | |
|
|
| 1162 | |
1150 | |
| 1163 | ########################################################################## |
1151 | ########################################################################## |
| 1164 | ########################################################################## |
1152 | ########################################################################## |
| 1165 | ## Function answer evaluators |
1153 | ## Function answer evaluators |
| 1166 | |
1154 | |
| … | |
… | |
| 1223 | variable functions. This usage is strongly discouraged as unnecessarily |
1211 | variable functions. This usage is strongly discouraged as unnecessarily |
| 1224 | confusing. Avoid it. |
1212 | confusing. Avoid it. |
| 1225 | |
1213 | |
| 1226 | Default Values (As of 7/24/2000) (Option -- Variable Name -- Value) |
1214 | Default Values (As of 7/24/2000) (Option -- Variable Name -- Value) |
| 1227 | |
1215 | |
| 1228 | Variable -- $functVarDefault -- 'x' |
1216 | Variable -- $functVarDefault -- 'x' |
| 1229 | Relative Tolerance -- $functRelPercentTolDefault -- .1 |
1217 | Relative Tolerance -- $functRelPercentTolDefault -- .1 |
| 1230 | Absolute Tolerance -- $functAbsTolDefault -- .001 |
1218 | Absolute Tolerance -- $functAbsTolDefault -- .001 |
| 1231 | Lower Limit -- $functLLimitDefault -- .0000001 |
1219 | Lower Limit -- $functLLimitDefault -- .0000001 |
| 1232 | Upper Limit -- $functULimitDefault -- 1 |
1220 | Upper Limit -- $functULimitDefault -- 1 |
| 1233 | Number of Points -- $functNumOfPoints -- 3 |
1221 | Number of Points -- $functNumOfPoints -- 3 |
| 1234 | Zero Level -- $functZeroLevelDefault -- 1E-14 |
1222 | Zero Level -- $functZeroLevelDefault -- 1E-14 |
| 1235 | Zero Level Tolerance -- $functZeroLevelTolDefault -- 1E-12 |
1223 | Zero Level Tolerance -- $functZeroLevelTolDefault -- 1E-12 |
| 1236 | Maximum Constant -- $functMaxConstantOfIntegration -- 1E8 |
1224 | Maximum Constant -- $functMaxConstantOfIntegration -- 1E8 |
| 1237 | of Integration |
1225 | of Integration |
| 1238 | |
1226 | |
| 1239 | =cut |
1227 | =cut |
| 1240 | |
1228 | |
| 1241 | =head3 Single-variable Function Comparisons |
1229 | =head3 Single-variable Function Comparisons |
| … | |
… | |
| 1342 | =cut |
1330 | =cut |
| 1343 | sub adaptive_function_cmp { |
1331 | sub adaptive_function_cmp { |
| 1344 | my $correctEqn = shift; |
1332 | my $correctEqn = shift; |
| 1345 | my %options = @_; |
1333 | my %options = @_; |
| 1346 | set_default_options( \%options, |
1334 | set_default_options( \%options, |
| 1347 | 'vars' => [qw( x y )], |
1335 | 'vars' => [qw( x y )], |
| 1348 | 'params' => [], |
1336 | 'params' => [], |
| 1349 | 'limits' => [ [0,1], [0,1]], |
1337 | 'limits' => [ [0,1], [0,1]], |
| 1350 | 'reltol' => $main::functRelPercentTolDefault, |
1338 | 'reltol' => $main::functRelPercentTolDefault, |
| 1351 | 'numPoints' => $main::functNumOfPoints, |
1339 | 'numPoints' => $main::functNumOfPoints, |
| 1352 | 'zeroLevel' => $main::functZeroLevelDefault, |
1340 | 'zeroLevel' => $main::functZeroLevelDefault, |
| 1353 | 'zeroLevelTol' => $main::functZeroLevelTolDefault, |
1341 | 'zeroLevelTol' => $main::functZeroLevelTolDefault, |
| 1354 | 'debug' => 0, |
1342 | 'debug' => 0, |
| 1355 | ); |
1343 | ); |
| 1356 | |
1344 | |
| 1357 | my $var_ref = $options{'vars'}; |
1345 | my $var_ref = $options{'vars'}; |
| 1358 | my $ra_params = $options{ 'params'}; |
1346 | my $ra_params = $options{ 'params'}; |
| 1359 | my $limit_ref = $options{'limits'}; |
1347 | my $limit_ref = $options{'limits'}; |
| 1360 | my $relPercentTol= $options{'reltol'}; |
1348 | my $relPercentTol= $options{'reltol'}; |
| 1361 | my $numPoints = $options{'numPoints'}; |
1349 | my $numPoints = $options{'numPoints'}; |
| 1362 | my $zeroLevel = $options{'zeroLevel'}; |
1350 | my $zeroLevel = $options{'zeroLevel'}; |
| … | |
… | |
| 1373 | 'zeroLevel' => $zeroLevel, |
1361 | 'zeroLevel' => $zeroLevel, |
| 1374 | 'zeroLevelTol' => $zeroLevelTol, |
1362 | 'zeroLevelTol' => $zeroLevelTol, |
| 1375 | 'scale_norm' => 1, |
1363 | 'scale_norm' => 1, |
| 1376 | 'params' => $ra_params, |
1364 | 'params' => $ra_params, |
| 1377 | 'debug' => $options{debug} , |
1365 | 'debug' => $options{debug} , |
| 1378 | ); |
1366 | ); |
| 1379 | |
|
|
| 1380 | } |
1367 | } |
| 1381 | |
1368 | |
| 1382 | sub function_cmp { |
1369 | sub function_cmp { |
| 1383 | my ($correctEqn,$var,$llimit,$ulimit,$relPercentTol,$numPoints,$zeroLevel,$zeroLevelTol) = @_; |
1370 | my ($correctEqn,$var,$llimit,$ulimit,$relPercentTol,$numPoints,$zeroLevel,$zeroLevelTol) = @_; |
| 1384 | |
1371 | |
| … | |
… | |
| 1415 | 'numPoints' => $numPoints, |
1402 | 'numPoints' => $numPoints, |
| 1416 | 'mode' => 'antider', |
1403 | 'mode' => 'antider', |
| 1417 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
1404 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
| 1418 | 'zeroLevel' => $zeroLevel, |
1405 | 'zeroLevel' => $zeroLevel, |
| 1419 | 'zeroLevelTol' => $zeroLevelTol |
1406 | 'zeroLevelTol' => $zeroLevelTol |
| 1420 | ); |
1407 | ); |
| 1421 | } |
1408 | } |
| 1422 | } |
1409 | } |
| 1423 | |
1410 | |
| 1424 | sub function_cmp_abs { ## similar to function_cmp but uses absolute tolerance |
1411 | sub function_cmp_abs { ## similar to function_cmp but uses absolute tolerance |
| 1425 | my ($correctEqn,$var,$llimit,$ulimit,$absTol,$numPoints) = @_; |
1412 | my ($correctEqn,$var,$llimit,$ulimit,$absTol,$numPoints) = @_; |
| … | |
… | |
| 1436 | 'numPoints' => $numPoints, |
1423 | 'numPoints' => $numPoints, |
| 1437 | 'mode' => 'std', |
1424 | 'mode' => 'std', |
| 1438 | 'maxConstantOfIntegration' => 0, |
1425 | 'maxConstantOfIntegration' => 0, |
| 1439 | 'zeroLevel' => 0, |
1426 | 'zeroLevel' => 0, |
| 1440 | 'zeroLevelTol' => 0 |
1427 | 'zeroLevelTol' => 0 |
| 1441 | ); |
1428 | ); |
| 1442 | } |
1429 | } |
| 1443 | } |
1430 | } |
| 1444 | |
1431 | |
| 1445 | |
1432 | |
| 1446 | sub function_cmp_up_to_constant_abs { ## for antiderivative problems |
1433 | sub function_cmp_up_to_constant_abs { ## for antiderivative problems |
| … | |
… | |
| 1461 | 'numPoints' => $numPoints, |
1448 | 'numPoints' => $numPoints, |
| 1462 | 'mode' => 'antider', |
1449 | 'mode' => 'antider', |
| 1463 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
1450 | 'maxConstantOfIntegration' => $maxConstantOfIntegration, |
| 1464 | 'zeroLevel' => 0, |
1451 | 'zeroLevel' => 0, |
| 1465 | 'zeroLevelTol' => 0 |
1452 | 'zeroLevelTol' => 0 |
| 1466 | ); |
1453 | ); |
| 1467 | } |
1454 | } |
| 1468 | } |
1455 | } |
| 1469 | |
1456 | |
| 1470 | ## The following answer evaluator for comparing multivarable functions was |
1457 | ## The following answer evaluator for comparing multivarable functions was |
| 1471 | ## contributed by Professor William K. Ziemer |
1458 | ## contributed by Professor William K. Ziemer |
| … | |
… | |
| 1501 | |
1488 | |
| 1502 | if ( (scalar(@_) > 7) or (scalar(@_) < 2) ) { |
1489 | if ( (scalar(@_) > 7) or (scalar(@_) < 2) ) { |
| 1503 | function_invalid_params( $correctEqn ); |
1490 | function_invalid_params( $correctEqn ); |
| 1504 | } |
1491 | } |
| 1505 | |
1492 | |
| 1506 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
1493 | FUNCTION_CMP( 'correctEqn' => $correctEqn, |
| 1507 | 'var' => $var_ref, |
1494 | 'var' => $var_ref, |
| 1508 | 'limits' => $limit_ref, |
1495 | 'limits' => $limit_ref, |
| 1509 | 'tolerance' => $relPercentTol, |
1496 | 'tolerance' => $relPercentTol, |
| 1510 | 'tolType' => 'relative', |
1497 | 'tolType' => 'relative', |
| 1511 | 'numPoints' => $numPoints, |
1498 | 'numPoints' => $numPoints, |
| 1512 | 'mode' => 'std', |
1499 | 'mode' => 'std', |
| 1513 | 'maxConstantOfIntegration' => 0, |
1500 | 'maxConstantOfIntegration' => 0, |
| 1514 | 'zeroLevel' => $zeroLevel, |
1501 | 'zeroLevel' => $zeroLevel, |
| 1515 | 'zeroLevelTol' => $zeroLevelTol |
1502 | 'zeroLevelTol' => $zeroLevelTol |
| 1516 | ); |
1503 | ); |
| 1517 | } |
1504 | } |
| 1518 | |
1505 | |
| 1519 | =head3 fun_cmp() |
1506 | =head3 fun_cmp() |
| 1520 | |
1507 | |
| 1521 | Compares a function or a list of functions, using a named hash of options to set |
1508 | Compares a function or a list of functions, using a named hash of options to set |
| … | |
… | |
| 1525 | ANS( fun_cmp( answer or answer_array_ref, options_hash ) ); |
1512 | ANS( fun_cmp( answer or answer_array_ref, options_hash ) ); |
| 1526 | |
1513 | |
| 1527 | 1. a string containing the correct function, or a reference to an |
1514 | 1. a string containing the correct function, or a reference to an |
| 1528 | array of correct functions |
1515 | array of correct functions |
| 1529 | 2. a hash containing the following items (all optional): |
1516 | 2. a hash containing the following items (all optional): |
| 1530 | var -- either the number of variables or a reference to an |
1517 | var -- either the number of variables or a reference to an |
| 1531 | array of variable names (see below) |
1518 | array of variable names (see below) |
| 1532 | limits -- reference to an array of arrays of limits (see below), or: |
1519 | limits -- reference to an array of arrays of limits (see below), or: |
| 1533 | mode -- 'std' (default) (function must match exactly), or: |
1520 | mode -- 'std' (default) (function must match exactly), or: |
| 1534 | 'antider' (function must match up to a constant) |
1521 | 'antider' (function must match up to a constant) |
| 1535 | relTol -- (default) a relative tolerance (as a percentage), or: |
1522 | relTol -- (default) a relative tolerance (as a percentage), or: |
| 1536 | tol -- an absolute tolerance for error |
1523 | tol -- an absolute tolerance for error |
| 1537 | numPoints -- the number of points to evaluate the function at |
1524 | numPoints -- the number of points to evaluate the function at |
| 1538 | maxConstantOfIntegration -- maximum size of the constant of integration |
1525 | maxConstantOfIntegration -- maximum size of the constant of integration |
| 1539 | zeroLevel -- if the correct answer is this close to zero, then |
1526 | zeroLevel -- if the correct answer is this close to zero, then |
| 1540 | zeroLevelTol applies |
1527 | zeroLevelTol applies |
| 1541 | zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
1528 | zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
| 1542 | params -- an array of "free" parameters which can be used to adapt |
1529 | params an array of "free" parameters which can be used to adapt |
| 1543 | -- the correct answer to the submitted answer. (e.g. ['c'] for |
1530 | the correct answer to the submitted answer. (e.g. ['c'] for |
| 1544 | -- a constant of integration in the answer x^3/3 + c. |
1531 | a constant of integration in the answer x^3/3 + c. |
| 1545 | debug -- when set to 1 this provides extra information while checking the |
1532 | debug -- when set to 1 this provides extra information while checking the |
| 1546 | -- the answer. |
1533 | the answer. |
| 1547 | |
1534 | |
| 1548 | Returns an answer evaluator, or (if given a reference to an array |
1535 | Returns an answer evaluator, or (if given a reference to an array |
| 1549 | of answers), a list of answer evaluators |
1536 | of answers), a list of answer evaluators |
| 1550 | |
1537 | |
| 1551 | ANSWER: |
1538 | ANSWER: |
| … | |
… | |
| 1559 | VARIABLES: |
1546 | VARIABLES: |
| 1560 | |
1547 | |
| 1561 | The var parameter can contain either a number or a reference to an array of |
1548 | The var parameter can contain either a number or a reference to an array of |
| 1562 | variable names. If it contains a number, the variables are named automatically |
1549 | variable names. If it contains a number, the variables are named automatically |
| 1563 | as follows: 1 variable -- x |
1550 | as follows: 1 variable -- x |
| 1564 | 2 variables -- x, y |
1551 | 2 variables -- x, y |
| 1565 | 3 variables -- x, y, z |
1552 | 3 variables -- x, y, z |
| 1566 | 4 or more -- x_1, x_2, x_3, etc. |
1553 | 4 or more -- x_1, x_2, x_3, etc. |
| 1567 | If the var parameter contains a reference to an array of variable names, then |
1554 | If the var parameter contains a reference to an array of variable names, then |
| 1568 | the number of variables is determined by the number of items in the array. A |
1555 | the number of variables is determined by the number of items in the array. A |
| 1569 | reference to an array is created with brackets, e.g. "var => ['r', 's', 't']". |
1556 | reference to an array is created with brackets, e.g. "var => ['r', 's', 't']". |
| 1570 | If only one variable is being used, you can write either "var => ['t']" for |
1557 | If only one variable is being used, you can write either "var => ['t']" for |
| 1571 | consistency or "var => 't'" as a shortcut. The default is one variable, x. |
1558 | consistency or "var => 't'" as a shortcut. The default is one variable, x. |
| … | |
… | |
| 1596 | sub fun_cmp { |
1583 | sub fun_cmp { |
| 1597 | my $correctAnswer = shift @_; |
1584 | my $correctAnswer = shift @_; |
| 1598 | my %opt = @_; |
1585 | my %opt = @_; |
| 1599 | |
1586 | |
| 1600 | assign_option_aliases( \%opt, |
1587 | assign_option_aliases( \%opt, |
| 1601 | 'vars' => 'var', # set the standard option 'var' to the one specified as vars |
1588 | 'vars' => 'var', # set the standard option 'var' to the one specified as vars |
| 1602 | 'domain' => 'limits', # set the standard option 'limits' to the one specified as domain |
1589 | 'domain' => 'limits', # set the standard option 'limits' to the one specified as domain |
| 1603 | 'reltol' => 'relTol', |
1590 | 'reltol' => 'relTol', |
| 1604 | 'param' => 'params', |
1591 | 'param' => 'params', |
| 1605 | ); |
1592 | ); |
| 1606 | |
1593 | |
| 1607 | set_default_options( \%opt, |
1594 | set_default_options( \%opt, |
| 1608 | 'var' => $functVarDefault, |
1595 | 'var' => $functVarDefault, |
| 1609 | 'params' => [], |
1596 | 'params' => [], |
| 1610 | 'limits' => [[$functLLimitDefault, $functULimitDefault]], |
1597 | 'limits' => [[$functLLimitDefault, $functULimitDefault]], |
| 1611 | 'mode' => 'std', |
1598 | 'mode' => 'std', |
| 1612 | 'tolType' => (defined($opt{tol}) ) ? 'absolute' : 'relative', |
1599 | 'tolType' => (defined($opt{tol}) ) ? 'absolute' : 'relative', |
| 1613 | 'tol' => .01, # default mode should be relative, to obtain this tol must not be defined |
1600 | 'tol' => .01, # default mode should be relative, to obtain this tol must not be defined |
| 1614 | 'relTol' => $functRelPercentTolDefault, |
1601 | 'relTol' => $functRelPercentTolDefault, |
| 1615 | 'numPoints' => $functNumOfPoints, |
1602 | 'numPoints' => $functNumOfPoints, |
| 1616 | 'maxConstantOfIntegration' => $functMaxConstantOfIntegration, |
1603 | 'maxConstantOfIntegration' => $functMaxConstantOfIntegration, |
| 1617 | 'zeroLevel' => $functZeroLevelDefault, |
1604 | 'zeroLevel' => $functZeroLevelDefault, |
| 1618 | 'zeroLevelTol' => $functZeroLevelTolDefault, |
1605 | 'zeroLevelTol' => $functZeroLevelTolDefault, |
| 1619 | 'debug' => 0, |
1606 | 'debug' => 0, |
| 1620 | ); |
1607 | ); |
| 1621 | |
1608 | |
| 1622 | |
1609 | |
| 1623 | |
1610 | |
| 1624 | # allow var => 'x' as an abbreviation for var => ['x'] |
1611 | # allow var => 'x' as an abbreviation for var => ['x'] |
| 1625 | my %out_options = %opt; |
1612 | my %out_options = %opt; |
| … | |
… | |
| 1678 | ## NOTE: PG_answer_eval is used instead of PG_restricted_eval in order to insure that the answer |
1665 | ## NOTE: PG_answer_eval is used instead of PG_restricted_eval in order to insure that the answer |
| 1679 | ## evaluated within the context of the package the problem was originally defined in. |
1666 | ## evaluated within the context of the package the problem was originally defined in. |
| 1680 | ## Includes multivariable modifications contributed by Professor William K. Ziemer |
1667 | ## Includes multivariable modifications contributed by Professor William K. Ziemer |
| 1681 | ## |
1668 | ## |
| 1682 | ## IN: a hash consisting of the following keys (error checking to be added later?) |
1669 | ## IN: a hash consisting of the following keys (error checking to be added later?) |
| 1683 | ## correctEqn -- the correct equation as a string |
1670 | ## correctEqn -- the correct equation as a string |
| 1684 | ## var -- the variable name as a string, |
1671 | ## var -- the variable name as a string, |
| 1685 | ## or a reference to an array of variables |
1672 | ## or a reference to an array of variables |
| 1686 | ## limits -- reference to an array of arrays of type [lower,upper] |
1673 | ## limits -- reference to an array of arrays of type [lower,upper] |
| 1687 | ## tolerance -- the allowable margin of error |
1674 | ## tolerance -- the allowable margin of error |
| 1688 | ## tolType -- 'relative' or 'absolute' |
1675 | ## tolType -- 'relative' or 'absolute' |
| 1689 | ## numPoints -- the number of points to evaluate the function at |
1676 | ## numPoints -- the number of points to evaluate the function at |
| 1690 | ## mode -- 'std' or 'antider' |
1677 | ## mode -- 'std' or 'antider' |
| 1691 | ## maxConstantOfIntegration -- maximum size of the constant of integration |
1678 | ## maxConstantOfIntegration -- maximum size of the constant of integration |
| 1692 | ## zeroLevel -- if the correct answer is this close to zero, |
1679 | ## zeroLevel -- if the correct answer is this close to zero, |
| 1693 | ## then zeroLevelTol applies |
1680 | ## then zeroLevelTol applies |
| 1694 | ## zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
1681 | ## zeroLevelTol -- absolute tolerance to allow when answer is close to zero |
| 1695 | |
1682 | |
| 1696 | |
1683 | |
| 1697 | sub FUNCTION_CMP { |
1684 | sub FUNCTION_CMP { |
| 1698 | my %func_params = @_; |
1685 | my %func_params = @_; |
| 1699 | |
1686 | |
| … | |
… | |
| 1726 | push(@PARAMS, $CONSTANT_PARAM); |
1713 | push(@PARAMS, $CONSTANT_PARAM); |
| 1727 | } |
1714 | } |
| 1728 | my $dim_of_param_space = @PARAMS; # dimension of equivalence space |
1715 | my $dim_of_param_space = @PARAMS; # dimension of equivalence space |
| 1729 | |
1716 | |
| 1730 | if( $tolType eq 'relative' ) { |
1717 | if( $tolType eq 'relative' ) { |
| 1731 | $tol = $functRelPercentTolDefault unless defined $tol; |
1718 | $tol = $functRelPercentTolDefault unless defined $tol; |
| 1732 | $tol *= .01; |
1719 | $tol *= .01; |
| 1733 | } |
1720 | } |
| 1734 | else { |
1721 | else { |
| 1735 | $tol = $functAbsTolDefault unless defined $tol; |
1722 | $tol = $functAbsTolDefault unless defined $tol; |
| 1736 | } |
1723 | } |
| 1737 | |
1724 | |
| 1738 | #loop ensures that number of limits matches number of variables |
1725 | #loop ensures that number of limits matches number of variables |
| 1739 | for( my $i = 0; $i < scalar(@VARS); $i++ ) { |
1726 | for( my $i = 0; $i < scalar(@VARS); $i++ ) { |
| 1740 | $limits[$i][0] = $functLLimitDefault unless defined $limits[$i][0]; |
1727 | $limits[$i][0] = $functLLimitDefault unless defined $limits[$i][0]; |
| 1741 | $limits[$i][1] = $functULimitDefault unless defined $limits[$i][1]; |
1728 | $limits[$i][1] = $functULimitDefault unless defined $limits[$i][1]; |
| 1742 | } |
1729 | } |
| 1743 | $numPoints = $functNumOfPoints unless defined $numPoints; |
1730 | $numPoints = $functNumOfPoints unless defined $numPoints; |
| 1744 | $maxConstantOfIntegration = $functMaxConstantOfIntegration unless defined $maxConstantOfIntegration; |
1731 | $maxConstantOfIntegration = $functMaxConstantOfIntegration unless defined $maxConstantOfIntegration; |
| 1745 | $zeroLevel = $functZeroLevelDefault unless defined $zeroLevel; |
1732 | $zeroLevel = $functZeroLevelDefault unless defined $zeroLevel; |
| 1746 | $zeroLevelTol = $functZeroLevelTolDefault unless defined $zeroLevelTol; |
1733 | $zeroLevelTol = $functZeroLevelTolDefault unless defined $zeroLevelTol; |
| 1747 | |
1734 | |
| 1748 | $func_params{'var'} = $var; |
1735 | $func_params{'var'} = $var; |
| 1749 | $func_params{'limits'} = \@limits; |
1736 | $func_params{'limits'} = \@limits; |
| 1750 | $func_params{'tolerance'} = $tol; |
1737 | $func_params{'tolerance'} = $tol; |
| 1751 | $func_params{'tolType'} = $tolType; |
1738 | $func_params{'tolType'} = $tolType; |
| … | |
… | |
| 1813 | $answer_evaluator->install_pre_filter(\&best_approx_parameters, %func_params, param_vars => \@PARAMS); |
1800 | $answer_evaluator->install_pre_filter(\&best_approx_parameters, %func_params, param_vars => \@PARAMS); |
| 1814 | $answer_evaluator->install_evaluator(\&calculate_difference_vector, %func_params); |
1801 | $answer_evaluator->install_evaluator(\&calculate_difference_vector, %func_params); |
| 1815 | $answer_evaluator->install_evaluator(\&is_zero_array, tol => $tol ); |
1802 | $answer_evaluator->install_evaluator(\&is_zero_array, tol => $tol ); |
| 1816 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); $rh_ans;} ); |
1803 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; $rh_ans->clear_error('SYNTAX'); $rh_ans;} ); |
| 1817 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; |
1804 | $answer_evaluator->install_post_filter(sub {my $rh_ans = shift; |
| 1818 | if ($rh_ans->catch_error('EVAL') ) { |
1805 | if ($rh_ans->catch_error('EVAL') ) { |
| 1819 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
1806 | $rh_ans->{ans_message} = $rh_ans->{error_message}; |
| 1820 | $rh_ans->clear_error('EVAL'); |
1807 | $rh_ans->clear_error('EVAL'); |
| 1821 | } |
1808 | } |
| 1822 | $rh_ans; |
1809 | $rh_ans;}); |
| 1823 | }); |
|
|
| 1824 | $answer_evaluator; |
1810 | $answer_evaluator; |
| 1825 | } |
1811 | } |
| 1826 | |
1812 | |
| 1827 | =head4 Filters |
1813 | =head4 Filters |
| 1828 | |
1814 | |
| 1829 | =pod |
1815 | =pod |
| 1830 | |
1816 | |
| 1831 | is_array($rh_ans) |
1817 | is_array($rh_ans) |
| 1832 | returns: $rh_ans. Throws error "NOTARRAY" if this is not an array |
1818 | returns: $rh_ans. Throws error "NOTARRAY" if this is not an array |
| 1833 | |
1819 | |
| 1834 | =cut |
1820 | =cut |
| 1835 | |
1821 | |
| 1836 | sub is_array { |
1822 | sub is_array { |
| 1837 | my $rh_ans = shift; |
1823 | my $rh_ans = shift; |
| … | |
… | |
| 1862 | $rh_ans->throw_error("1","{student_ans} field not defined"); |
1848 | $rh_ans->throw_error("1","{student_ans} field not defined"); |
| 1863 | return $rh_ans; |
1849 | return $rh_ans; |
| 1864 | } |
1850 | } |
| 1865 | my $in = $rh_ans->{student_ans}; |
1851 | my $in = $rh_ans->{student_ans}; |
| 1866 | my $parser = new AlgParserWithImplicitExpand; |
1852 | my $parser = new AlgParserWithImplicitExpand; |
| 1867 | my $ret = $parser -> parse($in); #for use with loops |
1853 | my $ret = $parser -> parse($in); #for use with loops |
| 1868 | |
1854 | |
| 1869 | if ( ref($ret) ) { ## parsed successfully |
1855 | if ( ref($ret) ) { ## parsed successfully |
| 1870 | $parser -> tostring(); |
1856 | $parser -> tostring(); |
| 1871 | $parser -> normalize(); |
1857 | $parser -> normalize(); |
| 1872 | $rh_ans->input( $parser -> tostring() ); |
1858 | $rh_ans->input( $parser -> tostring() ); |
| … | |
… | |
| 1879 | $rh_ans->{'ans_message'} = $parser -> {error_msg}, |
1865 | $rh_ans->{'ans_message'} = $parser -> {error_msg}, |
| 1880 | $rh_ans->{'preview_text_string'} = '', |
1866 | $rh_ans->{'preview_text_string'} = '', |
| 1881 | $rh_ans->{'preview_latex_string'} = '', |
1867 | $rh_ans->{'preview_latex_string'} = '', |
| 1882 | $rh_ans->throw_error('SYNTAX', 'syntax error in answer:'. $parser->{htmlerror} . "$BR" .$parser -> {error_msg}); |
1868 | $rh_ans->throw_error('SYNTAX', 'syntax error in answer:'. $parser->{htmlerror} . "$BR" .$parser -> {error_msg}); |
| 1883 | } |
1869 | } |
| 1884 | |
|
|
| 1885 | |
|
|
| 1886 | |
|
|
| 1887 | $rh_ans; |
1870 | $rh_ans; |
| 1888 | |
1871 | |
| 1889 | |
|
|
| 1890 | } |
1872 | } |
| 1891 | |
1873 | |
| 1892 | =pod |
1874 | =pod |
| 1893 | |
1875 | |
| 1894 | check_strings ($rh_ans, %options) |
1876 | check_strings ($rh_ans, %options) |
| 1895 | returns $rh_ans |
1877 | returns $rh_ans |
| 1896 | |
1878 | |
| 1897 | |
|
|
| 1898 | =cut |
1879 | =cut |
| 1899 | |
1880 | |
| 1900 | sub check_strings { |
1881 | sub check_strings { |
| 1901 | my ($rh_ans, %options) = @_; |
1882 | my ($rh_ans, %options) = @_; |
| 1902 | |
1883 | |
| 1903 | # if the student's answer is a number, simply return the answer hash (unchanged). |
1884 | # if the student's answer is a number, simply return the answer hash (unchanged). |
| 1904 | |
|
|
| 1905 | |
1885 | |
| 1906 | if ( $rh_ans->{student_ans} =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/) { |
1886 | if ( $rh_ans->{student_ans} =~ m/[\d+\-*\/^(){}\[\]]|^\s*e\s*$|^\s*pi\s*$/) { |
| 1907 | if ( $rh_ans->{answerIsString} == 1) { |
1887 | if ( $rh_ans->{answerIsString} == 1) { |
| 1908 | #$rh_ans->throw_error('STRING','Incorrect Answer'); # student's answer is a number |
1888 | #$rh_ans->throw_error('STRING','Incorrect Answer'); # student's answer is a number |
| 1909 | } |
1889 | } |
| 1910 | return $rh_ans; |
1890 | return $rh_ans; |
| 1911 | } |
1891 | } |
| 1912 | # the student's answer is recognized as a string |
1892 | # the student's answer is recognized as a string |
| 1913 | my $ans = $rh_ans->{student_ans}; |
1893 | my $ans = $rh_ans->{student_ans}; |
|
|
1894 | |
| 1914 | # OVERVIEW of remindar of function: |
1895 | # OVERVIEW of remindar of function: |
| 1915 | # if answer is correct, return correct. (adjust score to 1) |
1896 | # if answer is correct, return correct. (adjust score to 1) |
| 1916 | # if answer is incorect: |
1897 | # if answer is incorect: |
| 1917 | # 1) determine if the answer is sensible. if it is, return incorrect. |
1898 | # 1) determine if the answer is sensible. if it is, return incorrect. |
| 1918 | # 2) if the answer is not sensible (and incorrect), then return an error message indicating so. |
1899 | # 2) if the answer is not sensible (and incorrect), then return an error message indicating so. |
| 1919 | # no matter what: throw a 'STRING' error to skip numerical evaluations. (error flag skips remainder of pre_filters and evaluators) |
1900 | # no matter what: throw a 'STRING' error to skip numerical evaluations. (error flag skips remainder of pre_filters and evaluators) |
| 1920 | # last: 'STRING' post_filter will clear the error (avoiding pink screen.) |
1901 | # last: 'STRING' post_filter will clear the error (avoiding pink screen.) |
|
|
1902 | |
| 1921 | my $sensibleAnswer = 0; |
1903 | my $sensibleAnswer = 0; |
| 1922 | $ans = str_filters( $ans, 'compress_whitespace' ); # remove trailing, leading, and double spaces. |
1904 | $ans = str_filters( $ans, 'compress_whitespace' ); # remove trailing, leading, and double spaces. |
| 1923 | my ($ans_eval) = str_cmp($rh_ans->{correct_ans}); |
1905 | my ($ans_eval) = str_cmp($rh_ans->{correct_ans}); |
| 1924 | my $temp_ans_hash = &$ans_eval($ans); |
1906 | my $temp_ans_hash = &$ans_eval($ans); |
| 1925 | $rh_ans->{test} = $temp_ans_hash; |
1907 | $rh_ans->{test} = $temp_ans_hash; |
| 1926 | if ($temp_ans_hash->{score} ==1 ) { # students answer matches the correct answer. |
1908 | if ($temp_ans_hash->{score} ==1 ) { # students answer matches the correct answer. |
| 1927 | $rh_ans->{score} = 1; |
1909 | $rh_ans->{score} = 1; |
| 1928 | $sensibleAnswer = 1; |
1910 | $sensibleAnswer = 1; |
| 1929 | } else { # students answer does not match the correct answer. |
1911 | } else { # students answer does not match the correct answer. |
| 1930 | my $legalString = ''; ## find out if string makes sense |
1912 | my $legalString = ''; # find out if string makes sense |
| 1931 | my @legalStrings = @{$options{strings}}; |
1913 | my @legalStrings = @{$options{strings}}; |
| 1932 | foreach $legalString (@legalStrings) { |
1914 | foreach $legalString (@legalStrings) { |
| 1933 | if ( uc($ans) eq uc($legalString) ) { |
1915 | if ( uc($ans) eq uc($legalString) ) { |
| 1934 | $sensibleAnswer = 1; |
1916 | $sensibleAnswer = 1; |
| 1935 | last; |
1917 | last; |
| 1936 | } |
1918 | } |
| 1937 | } |
1919 | } |
| 1938 | $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible |
1920 | $sensibleAnswer = 1 unless $ans =~ /\S/; ## empty answers are sensible |
| 1939 | $rh_ans->throw_error('EVAL', "$BR Your answer is not a recognized answer") unless ($sensibleAnswer); |
1921 | $rh_ans->throw_error('EVAL', "Your answer is not a recognized answer") unless ($sensibleAnswer); |
| 1940 | # $temp_ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' ) unless ($sensibleAnswer); |
1922 | # $temp_ans_hash -> setKeys( 'ans_message' => 'Your answer is not a recognized answer' ) unless ($sensibleAnswer); |
| 1941 | # $temp_ans_hash -> setKeys( 'student_ans' => uc($ans) ); |
1923 | # $temp_ans_hash -> setKeys( 'student_ans' => uc($ans) ); |
| 1942 | } |
1924 | } |
| 1943 | $rh_ans->{student_ans} = $ans; |
1925 | $rh_ans->{student_ans} = $ans; |
| 1944 | if ($sensibleAnswer) { |
1926 | if ($sensibleAnswer) { |
| … | |
… | |
| 1949 | } |
1931 | } |
| 1950 | |
1932 | |
| 1951 | =pod |
1933 | =pod |
| 1952 | |
1934 | |
| 1953 | check_strings ($rh_ans, %options) |
1935 | check_strings ($rh_ans, %options) |
| 1954 | returns $rh_ans |
1936 | returns $rh_ans |
| 1955 | |
1937 | |
| 1956 | |
1938 | |
| 1957 | =cut |
1939 | =cut |
| 1958 | |
1940 | |
| 1959 | sub check_units { |
1941 | sub check_units { |
| … | |
… | |
| 2034 | my ($rh_ans, %options) = @_; |
2016 | my ($rh_ans, %options) = @_; |
| 2035 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans}); |
2017 | my ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($rh_ans->{student_ans}); |
| 2036 | if ($PG_eval_errors) { |
2018 | if ($PG_eval_errors) { |
| 2037 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); |
2019 | $rh_ans->throw_error('EVAL','There is a syntax error in your answer'); |
| 2038 | $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); |
2020 | $rh_ans->{ans_message} = clean_up_error_msg($PG_eval_errors); |
| 2039 | |
2021 | # return $rh_ans; |
| 2040 | } else { |
2022 | } else { |
| 2041 | $rh_ans->{student_ans} = prfmt($inVal,$options{format}); |
2023 | $rh_ans->{student_ans} = prfmt($inVal,$options{format}); |
| 2042 | } |
2024 | } |
| 2043 | |
2025 | |
| 2044 | my $permitted_error; |
2026 | my $permitted_error; |
| … | |
… | |
| 2055 | |
2037 | |
| 2056 | my $is_a_number = is_a_number($inVal); |
2038 | my $is_a_number = is_a_number($inVal); |
| 2057 | $rh_ans->{score} = 1 if ( ($is_a_number) and |
2039 | $rh_ans->{score} = 1 if ( ($is_a_number) and |
| 2058 | (abs( $inVal - $rh_ans->{correct_ans} ) <= $permitted_error) ); |
2040 | (abs( $inVal - $rh_ans->{correct_ans} ) <= $permitted_error) ); |
| 2059 | if (not $is_a_number) { |
2041 | if (not $is_a_number) { |
| 2060 | $rh_ans->throw_error('EVAL','Your answer does not evaluate to a number'); |
2042 | $rh_ans->{error_message} = "$rh_ans->{error_message}". 'Your answer does not evaluate to a number '; |
| 2061 | } |
2043 | } |
| 2062 | |
2044 | |
| 2063 | $rh_ans; |
2045 | $rh_ans; |
| 2064 | } |
2046 | } |
| 2065 | |
|
|
| 2066 | |
|
|
| 2067 | |
2047 | |
| 2068 | =pod |
2048 | =pod |
| 2069 | |
2049 | |
| 2070 | std_num_filter($rh_ans, %options) |
2050 | std_num_filter($rh_ans, %options) |
| 2071 | returns $rh_ans |
2051 | returns $rh_ans |
| … | |
… | |
| 2082 | $in = math_constants($in); |
2062 | $in = math_constants($in); |
| 2083 | $rh_ans->{type} = 'std_number'; |
2063 | $rh_ans->{type} = 'std_number'; |
| 2084 | my ($inVal,$PG_eval_errors,$PG_full_error_report); |
2064 | my ($inVal,$PG_eval_errors,$PG_full_error_report); |
| 2085 | if ($in =~ /\S/) { |
2065 | if ($in =~ /\S/) { |
| 2086 | ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($in); |
2066 | ($inVal,$PG_eval_errors,$PG_full_error_report) = PG_answer_eval($in); |
| 2087 | } else { |
2067 | } else { |
| 2088 | $PG_eval_errors = ''; |
2068 | $PG_eval_errors = ''; |
| 2089 | } |
2069 | } |
| 2090 | |
2070 | |
| 2091 | if ($PG_eval_errors) { ##error message from eval or above |
2071 | if ($PG_eval_errors) { ##error message from eval or above |
| 2092 | $rh_ans->{ans_message} = 'There is a syntax error in your answer'; |
2072 | $rh_ans->{ans_message} = 'There is a syntax error in your answer'; |
| 2093 | $rh_ans->{student_ans} = clean_up_error_msg($PG_eval_errors); |
2073 | $rh_ans->{student_ans} = clean_up_error_msg($PG_eval_errors); |
| … | |
… | |
| 2132 | push(@out, $temp_hash->input()); |
2112 | push(@out, $temp_hash->input()); |
| 2133 | |
2113 | |
| 2134 | } |
2114 | } |
| 2135 | if ($PGanswerMessage) { |
2115 | if ($PGanswerMessage) { |
| 2136 | $rh_ans->input( "( " . join(", ", @out ) . " )" ); |
2116 | $rh_ans->input( "( " . join(", ", @out ) . " )" ); |
| 2137 | $rh_ans->throw_error('SYNTAX', 'There is a syntax error in your answer.'); |
2117 | $rh_ans->throw_error('SYNTAX', 'There is a syntax error in your answer.'); |
| 2138 | } else { |
2118 | } else { |
| 2139 | $rh_ans->input( [@out] ); |
2119 | $rh_ans->input( [@out] ); |
| 2140 | } |
2120 | } |
| 2141 | $rh_ans; |
2121 | $rh_ans; |
| 2142 | } |
2122 | } |
| … | |
… | |
| 2234 | $rh_ans->{score} = 0; |
2214 | $rh_ans->{score} = 0; |
| 2235 | my $error = "WeBWorK was unable evaluate your function. Please check that your |
2215 | my $error = "WeBWorK was unable evaluate your function. Please check that your |
| 2236 | expression doesn't take roots of negative numbers, or divide by zero."; |
2216 | expression doesn't take roots of negative numbers, or divide by zero."; |
| 2237 | $rh_ans->throw_error('EVAL',$error); |
2217 | $rh_ans->throw_error('EVAL',$error); |
| 2238 | } else { |
2218 | } else { |
| 2239 | my $tol = $options{tol} if defined($options{tol}); |
2219 | my $tol = $options{tol} if defined($options{tol}); |
| 2240 | #$tol = 0.01*$options{reltol} if defined($options{reltol}); |
2220 | #$tol = 0.01*$options{reltol} if defined($options{reltol}); |
| 2241 | $tol = .000001 unless defined($tol); |
2221 | $tol = .000001 unless defined($tol); |
| 2242 | |
2222 | |
| 2243 | $rh_ans->{score} = ($max <$tol) ? 1: 0; # 1 if the array is close to 0; |
2223 | $rh_ans->{score} = ($max <$tol) ? 1: 0; # 1 if the array is close to 0; |
| 2244 | } |
2224 | } |
| 2245 | $rh_ans; |
2225 | $rh_ans; |
| 2246 | } |
2226 | } |
| … | |
… | |
| 2295 | my %options = @_; |
2275 | my %options = @_; |
| 2296 | my $errors = undef; |
2276 | my $errors = undef; |
| 2297 | # This subroutine for the determining the coefficents of the parameters at a given point |
2277 | # This subroutine for the determining the coefficents of the parameters at a given point |
| 2298 | # is pretty specialized, so it is included here as a sub-subroutine. |
2278 | # is pretty specialized, so it is included here as a sub-subroutine. |
| 2299 | my $determine_param_coeffs = sub { |
2279 | my $determine_param_coeffs = sub { |
| 2300 | my ($rf_fun, $ra_variables, $dim_of_params_space, %options) =@_; |
2280 | my ($rf_fun, $ra_variables, $dim_of_params_space, %options) =@_; |
| 2301 | my @zero_params=(); |
2281 | my @zero_params=(); |
| 2302 | for(my $i=1;$i<=$dim_of_params_space;$i++){push(@zero_params,0); } |
2282 | for(my $i=1;$i<=$dim_of_params_space;$i++){push(@zero_params,0); } |
| 2303 | my @vars = @$ra_variables; |
2283 | my @vars = @$ra_variables; |
| 2304 | my @coeff = (); |
2284 | my @coeff = (); |
| 2305 | my @inputs = (@vars,@zero_params); |
2285 | my @inputs = (@vars,@zero_params); |
| 2306 | my ($f0, $f1, $err); |
2286 | my ($f0, $f1, $err); |
| 2307 | ($f0, $err) = &{$rf_fun}(@inputs); |
2287 | ($f0, $err) = &{$rf_fun}(@inputs); |
| 2308 | if (defined($err) ) { |
2288 | if (defined($err) ) { |
| 2309 | $errors .= "$err "; |
2289 | $errors .= "$err "; |
| 2310 | } else { |
2290 | } else { |
| 2311 | for (my $i=@vars;$i<@inputs;$i++) { |
2291 | for (my $i=@vars;$i<@inputs;$i++) { |
| 2312 | $inputs[$i]=1; # set one parameter to 1; |
2292 | $inputs[$i]=1; # set one parameter to 1; |
| 2313 | my($f1,$err) = &$rf_fun(@inputs); |
2293 | my($f1,$err) = &$rf_fun(@inputs); |
| 2314 | if (defined($err) ) { |
2294 | if (defined($err) ) { |
| 2315 | $errors .= " $err "; |
2295 | $errors .= " $err "; |
| 2316 | } else { |
2296 | } else { |
| 2317 | push(@coeff, $f1-$f0); |
2297 | push(@coeff, $f1-$f0); |
| 2318 | } |
|
|
| 2319 | $inputs[$i]=0; # set it back |
|
|
| 2320 | } |
2298 | } |
|
|
2299 | $inputs[$i]=0; # set it back |
| 2321 | } |
2300 | } |
|
|
2301 | } |
| 2322 | (\@coeff, $errors); |
2302 | (\@coeff, $errors); |
| 2323 | }; |
2303 | }; |
| 2324 | my $rf_fun = $rh_ans->{rf_student_ans}; |
2304 | my $rf_fun = $rh_ans->{rf_student_ans}; |
| 2325 | my $rf_correct_fun = $rh_ans->{rf_correct_ans}; |
2305 | my $rf_correct_fun = $rh_ans->{rf_correct_ans}; |
| 2326 | my $ra_vars_matrix = $rh_ans->{evaluation_points}; |
2306 | my $ra_vars_matrix = $rh_ans->{evaluation_points}; |
| 2327 | my $dim_of_param_space = @{$options{param_vars}}; |
2307 | my $dim_of_param_space = @{$options{param_vars}}; |
| 2328 | # Short cut. Bail if there are no param_vars |
2308 | # Short cut. Bail if there are no param_vars |
| 2329 | unless ($dim_of_param_space >0) { |
2309 | unless ($dim_of_param_space >0) { |
| 2330 | $rh_ans ->{ra_parameters} = []; |
2310 | $rh_ans ->{ra_parameters} = []; |
| 2331 | return $rh_ans; |
2311 | return $rh_ans; |
| 2332 | } |
2312 | } |
| 2333 | # inputs are row arrays in this case. |
2313 | # inputs are row arrays in this case. |
| 2334 | my @zero_params=(); |
2314 | my @zero_params=(); |
| 2335 | |
2315 | |
| 2336 | for(my $i=1;$i<=$dim_of_param_space;$i++){push(@zero_params,0); } |
2316 | for(my $i=1;$i<=$dim_of_param_space;$i++){push(@zero_params,0); } |
| 2337 | my @rows_of_vars = @$ra_vars_matrix; |
2317 | my @rows_of_vars = @$ra_vars_matrix; |
| 2338 | warn "input rows ", pretty_print(\@rows_of_vars) if defined($options{debug}) and $options{debug}; |
2318 | warn "input rows ", pretty_print(\@rows_of_vars) if defined($options{debug}) and $options{debug}; |
| 2339 | my $rows = @rows_of_vars; |
2319 | my $rows = @rows_of_vars; |
| 2340 | my $matrix =new Matrix($rows,$dim_of_param_space); |
2320 | my $matrix =new Matrix($rows,$dim_of_param_space); |
| 2341 | my $rhs_vec = new Matrix($rows, 1); |
2321 | my $rhs_vec = new Matrix($rows, 1); |
| 2342 | my $row_num = 1; |
2322 | my $row_num = 1; |
| 2343 | my ($ra_coeff,$val2, $val1, $err1,$err2,@inputs,@vars); |
2323 | my ($ra_coeff,$val2, $val1, $err1,$err2,@inputs,@vars); |
| 2344 | my $number_of_data_points = $dim_of_param_space +2; |
2324 | my $number_of_data_points = $dim_of_param_space +2; |
| 2345 | while (@rows_of_vars and $row_num <= $number_of_data_points) { |
2325 | while (@rows_of_vars and $row_num <= $number_of_data_points) { |
| 2346 | |
|
|
| 2347 | # get one set of data points from the test function; |
2326 | # get one set of data points from the test function; |
| 2348 | @vars = @{ shift(@rows_of_vars) }; |
2327 | @vars = @{ shift(@rows_of_vars) }; |
| 2349 | ($val2, $err1) = &{$rf_fun}(@vars); |
2328 | ($val2, $err1) = &{$rf_fun}(@vars); |
| 2350 | $errors .= " $err1 " if defined($err1); |
2329 | $errors .= " $err1 " if defined($err1); |
| 2351 | @inputs = (@vars,@zero_params); |
2330 | @inputs = (@vars,@zero_params); |
| 2352 | ($val1, $err2) = &{$rf_correct_fun}(@inputs); |
2331 | ($val1, $err2) = &{$rf_correct_fun}(@inputs); |
| 2353 | $errors .= " $err2 " if defined($err2); |
2332 | $errors .= " $err2 " if defined($err2); |
| … | |
… | |
| 2407 | ( the maximum allowed is $options{maxConstantOfIntegration} )"; |
2386 | ( the maximum allowed is $options{maxConstantOfIntegration} )"; |
| 2408 | } |
2387 | } |
| 2409 | |
2388 | |
| 2410 | $rh_ans->{ra_parameters} = \@array; |
2389 | $rh_ans->{ra_parameters} = \@array; |
| 2411 | $rh_ans->throw_error('EVAL', $errors) if defined($errors); |
2390 | $rh_ans->throw_error('EVAL', $errors) if defined($errors); |
| 2412 | $rh_ans; |
2391 | $rh_ans; |
| 2413 | } |
2392 | } |
| 2414 | |
2393 | |
| 2415 | =pod |
2394 | =pod |
| 2416 | |
2395 | |
| 2417 | calculate_difference_vector( $ans_hash, %options); |
2396 | calculate_difference_vector( $ans_hash, %options); |
| 2418 | |
2397 | |
| 2419 | {rf_student_ans}, # a reference to the test function |
2398 | {rf_student_ans}, # a reference to the test function |
| 2420 | {rf_correct_ans}, # a reference to the correct answer function |
2399 | {rf_correct_ans}, # a reference to the correct answer function |
| 2421 | {evaluation_points}, # an array of row vectors indicating the points |
2400 | {evaluation_points}, # an array of row vectors indicating the points |
| 2422 | # to evaluate when comparing the functions |
2401 | # to evaluate when comparing the functions |
| 2423 | {ra_parameters} # these are the (optional) additional inputs to |
2402 | {ra_parameters} # these are the (optional) additional inputs to |
| 2424 | # the comparison function which adapt it properly |
2403 | # the comparison function which adapt it properly |
| 2425 | # to the problem at hand. |
2404 | # to the problem at hand. |
| 2426 | |
2405 | |
| 2427 | %options # mode => 'rel' specifies that each element in the |
2406 | %options # mode => 'rel' specifies that each element in the |
| 2428 | # difference matrix is divided by the correct answer. |
2407 | # difference matrix is divided by the correct answer. |
| 2429 | # unless the correct answer is nearly 0. |
2408 | # unless the correct answer is nearly 0. |
| 2430 | ) |
2409 | ) |
| 2431 | |
2410 | |
| 2432 | =cut |
2411 | =cut |
| 2433 | |
|
|
| 2434 | |
2412 | |
| 2435 | sub calculate_difference_vector { |
2413 | sub calculate_difference_vector { |
| 2436 | my $rh_ans = shift; |
2414 | my $rh_ans = shift; |
| 2437 | my %options = @_; |
2415 | my %options = @_; |
| 2438 | # initialize |
2416 | # initialize |
| … | |
… | |
| 2466 | $errors .= " There is an error in WeBWorK's answer to this problem, please alert your instructor.<br> $err3 " if defined($err3); |
2444 | $errors .= " There is an error in WeBWorK's answer to this problem, please alert your instructor.<br> $err3 " if defined($err3); |
| 2467 | $errors .= " Error detected evaluating correct answer at (".join(' , ',@vars, @zero_params) ." ) " if defined($options{debug}) and $options{debug}=1 and defined($err3); |
2445 | $errors .= " Error detected evaluating correct answer at (".join(' , ',@vars, @zero_params) ." ) " if defined($options{debug}) and $options{debug}=1 and defined($err3); |
| 2468 | unless (defined($err1) or defined($err2) or defined($err3) ) { |
2446 | unless (defined($err1) or defined($err2) or defined($err3) ) { |
| 2469 | $diff = ( $inVal - ($correctVal -$tol_val ) ) - $tol_val; #prevents entering too high a number? |
2447 | $diff = ( $inVal - ($correctVal -$tol_val ) ) - $tol_val; #prevents entering too high a number? |
| 2470 | #warn "taking the difference of ", $inVal, " and ", $correctVal, " is ", $diff; |
2448 | #warn "taking the difference of ", $inVal, " and ", $correctVal, " is ", $diff; |
| 2471 | |
|
|
| 2472 | if (defined($options{tolType}) and $options{tolType} eq 'relative' ) { #relative tolerance |
2449 | if (defined($options{tolType}) and $options{tolType} eq 'relative' ) { #relative tolerance |
| 2473 | #warn "diff = $diff"; |
2450 | #warn "diff = $diff"; |
| 2474 | $diff = ( $inVal - ($correctVal-$tol_val ) )/abs($tol_val) -1 if abs($tol_val) > $options{zeroLevel}; |
2451 | $diff = ( $inVal - ($correctVal-$tol_val ) )/abs($tol_val) -1 if abs($tol_val) > $options{zeroLevel}; |
| 2475 | #$diff = ( $inVal - ($correctVal-$tol_val- $tol_val ) )/abs($tol_val) if abs($tol_val) > $options{zeroLevel}; |
2452 | #$diff = ( $inVal - ($correctVal-$tol_val- $tol_val ) )/abs($tol_val) if abs($tol_val) > $options{zeroLevel}; |
| 2476 | #warn "diff = $diff, ", abs( &$rf_correct_fun(@inputs) ) , "-- $correctVal"; |
2453 | #warn "diff = $diff, ", abs( &$rf_correct_fun(@inputs) ) , "-- $correctVal"; |
| … | |
… | |
| 2491 | $rh_ans->{ra_tol_values}=\@tol_values; |
2468 | $rh_ans->{ra_tol_values}=\@tol_values; |
| 2492 | $rh_ans->throw_error('EVAL', $errors) if defined($errors); |
2469 | $rh_ans->throw_error('EVAL', $errors) if defined($errors); |
| 2493 | $rh_ans; |
2470 | $rh_ans; |
| 2494 | } |
2471 | } |
| 2495 | |
2472 | |
| 2496 | |
|
|
| 2497 | ########################################################################## |
2473 | ########################################################################## |
| 2498 | ########################################################################## |
2474 | ########################################################################## |
| 2499 | ## String answer evaluators |
2475 | ## String answer evaluators |
| 2500 | |
2476 | |
| 2501 | =head2 String Answer Evaluators |
2477 | =head2 String Answer Evaluators |
| … | |
… | |
| 2506 | ensure that there are no unexpected matches or rejections. |
2482 | ensure that there are no unexpected matches or rejections. |
| 2507 | |
2483 | |
| 2508 | String Filters |
2484 | String Filters |
| 2509 | |
2485 | |
| 2510 | remove_whitespace -- Removes all whitespace from the string. |
2486 | remove_whitespace -- Removes all whitespace from the string. |
| 2511 | It applies the following substitution |
2487 | It applies the following substitution |
| 2512 | to the string: |
2488 | to the string: |
| 2513 | $filteredAnswer =~ s/\s+//g; |
2489 | $filteredAnswer =~ s/\s+//g; |
| 2514 | |
2490 | |
| 2515 | compress_whitespace -- Removes leading and trailing whitespace, and |
2491 | compress_whitespace -- Removes leading and trailing whitespace, and |
| 2516 | replaces all other blocks of whitespace by a |
2492 | replaces all other blocks of whitespace by a |
| 2517 | single space. Applies the following substitutions: |
2493 | single space. Applies the following substitutions: |
| 2518 | $filteredAnswer =~ s/^\s*//; |
2494 | $filteredAnswer =~ s/^\s*//; |
| 2519 | $filteredAnswer =~ s/\s*$//; |
2495 | $filteredAnswer =~ s/\s*$//; |
| 2520 | $filteredAnswer =~ s/\s+/ /g; |
2496 | $filteredAnswer =~ s/\s+/ /g; |
| 2521 | |
2497 | |
| 2522 | trim_whitespace -- Removes leading and trailing whitespace. |
2498 | trim_whitespace -- Removes leading and trailing whitespace. |
| 2523 | Applies the following substitutions: |
2499 | Applies the following substitutions: |
| 2524 | $filteredAnswer =~ s/^\s*//; |
2500 | $filteredAnswer =~ s/^\s*//; |
| 2525 | $filteredAnswer =~ s/\s*$//; |
2501 | $filteredAnswer =~ s/\s*$//; |
| 2526 | |
2502 | |
| 2527 | ignore_case -- Ignores the case of the string. More accurately, |
2503 | ignore_case -- Ignores the case of the string. More accurately, |
| 2528 | it converts the string to uppercase (by convention). |
2504 | it converts the string to uppercase (by convention). |
| 2529 | Applies the following function: |
2505 | Applies the following function: |
| 2530 | $filteredAnswer = uc $filteredAnswer; |
2506 | $filteredAnswer = uc $filteredAnswer; |
| 2531 | |
2507 | |
| 2532 | ignore_order -- Ignores the order of the letters in the string. |
2508 | ignore_order -- Ignores the order of the letters in the string. |
| 2533 | This is used for problems of the form "Choose all |
2509 | This is used for problems of the form "Choose all |
| 2534 | that apply." Specifically, it removes all |
2510 | that apply." Specifically, it removes all |
| 2535 | whitespace and lexically sorts the letters in |
2511 | whitespace and lexically sorts the letters in |
| 2536 | ascending alphabetical order. Applies the following |
2512 | ascending alphabetical order. Applies the following |
| 2537 | functions: |
2513 | functions: |
| 2538 | $filteredAnswer = join( "", lex_sort( |
2514 | $filteredAnswer = join( "", lex_sort( |
| 2539 | split( /\s*/, $filteredAnswer ) ) ); |
2515 | split( /\s*/, $filteredAnswer ) ) ); |
| 2540 | |
2516 | |
| 2541 | =cut |
2517 | =cut |
| 2542 | |
2518 | |
| 2543 | ################################ |
2519 | ################################ |
| 2544 | ## STRING ANSWER FILTERS |
2520 | ## STRING ANSWER FILTERS |
| … | |
… | |
| 2548 | ## |
2524 | ## |
| 2549 | ## OUT: --the modified string |
2525 | ## OUT: --the modified string |
| 2550 | ## |
2526 | ## |
| 2551 | ## Use this subroutine instead of the |
2527 | ## Use this subroutine instead of the |
| 2552 | ## individual filters below it |
2528 | ## individual filters below it |
|
|
2529 | |
| 2553 | sub str_filters { |
2530 | sub str_filters { |
| 2554 | my $stringToFilter = shift @_; |
2531 | my $stringToFilter = shift @_; |
| 2555 | my @filters_to_use = @_; |
2532 | my @filters_to_use = @_; |
| 2556 | my %known_filters = ( 'remove_whitespace' => undef, |
2533 | my %known_filters = ( 'remove_whitespace' => undef, |
| 2557 | 'compress_whitespace' => undef, |
2534 | 'compress_whitespace' => undef, |
| 2558 | 'trim_whitespace' => undef, |
2535 | 'trim_whitespace' => undef, |
| 2559 | 'ignore_case' => undef, |
2536 | 'ignore_case' => undef, |
| 2560 | 'ignore_order' => undef |
2537 | 'ignore_order' => undef |
| 2561 | ); |
2538 | ); |
| 2562 | |
2539 | |
| 2563 | #test for unknown filters |
2540 | #test for unknown filters |
| 2564 | my $filter; |
2541 | my $filter; |
| 2565 | foreach $filter (@filters_to_use) { |
2542 | foreach $filter (@filters_to_use) { |
| 2566 | die "Unknown string filter $filter (try checking the parameters to str_cmp() )" |
2543 | die "Unknown string filter $filter (try checking the parameters to str_cmp() )" |
| 2567 | unless exists $known_filters{$filter}; |
2544 | unless exists $known_filters{$filter}; |
| 2568 | } |
2545 | } |
| 2569 | |
2546 | |
| 2570 | if( grep( /remove_whitespace/i, @filters_to_use ) ) { |
2547 | if( grep( /remove_whitespace/i, @filters_to_use ) ) { |
| 2571 | $stringToFilter = remove_whitespace( $stringToFilter ); |
2548 | $stringToFilter = remove_whitespace( $stringToFilter ); |
| 2572 | } |
2549 | } |
| … | |
… | |
| 2743 | |
2720 | |
| 2744 | sub unordered_str_cmp { # unordered, case insensitive, spaces ignored |
2721 | sub unordered_str_cmp { # unordered, case insensitive, spaces ignored |
| 2745 | my $correctAnswer = shift @_; |
2722 | my $correctAnswer = shift @_; |
| 2746 | my @filters = ( 'ignore_order', 'ignore_case' ); |
2723 | my @filters = ( 'ignore_order', 'ignore_case' ); |
| 2747 | my $type = 'unordered_str_cmp'; |
2724 | my $type = 'unordered_str_cmp'; |
| 2748 | STR_CMP( 'correctAnswer' => $correctAnswer, |
2725 | STR_CMP( 'correctAnswer' => $correctAnswer, |
| 2749 | 'filters' => \@filters, |
2726 | 'filters' => \@filters, |
| 2750 | 'type' => $type |
2727 | 'type' => $type |
| 2751 | ); |
2728 | ); |
| 2752 | } |
2729 | } |
| 2753 | |
2730 | |
| 2754 | sub unordered_str_cmp_list { # alias for unordered_str_cmp |
2731 | sub unordered_str_cmp_list { # alias for unordered_str_cmp |
| 2755 | my @answerList = @_; |
2732 | my @answerList = @_; |
| … | |
… | |
| 2762 | |
2739 | |
| 2763 | sub unordered_cs_str_cmp { # unordered, case sensitive, spaces ignored |
2740 | sub unordered_cs_str_cmp { # unordered, case sensitive, spaces ignored |
| 2764 | my $correctAnswer = shift @_; |
2741 | my $correctAnswer = shift @_; |
| 2765 | my @filters = ( 'ignore_order' ); |
2742 | my @filters = ( 'ignore_order' ); |
| 2766 | my $type = 'unordered_cs_str_cmp'; |
2743 | my $type = 'unordered_cs_str_cmp'; |
| 2767 | STR_CMP( 'correctAnswer' => $correctAnswer, |
2744 | STR_CMP( 'correctAnswer' => $correctAnswer, |
| 2768 | 'filters' => \@filters, |
2745 | 'filters' => \@filters, |
| 2769 | 'type' => $type |
2746 | 'type' => $type |
| 2770 | ); |
2747 | ); |
| 2771 | } |
2748 | } |
| 2772 | |
2749 | |
| 2773 | sub unordered_cs_str_cmp_list { # alias for unordered_cs_str_cmp |
2750 | sub unordered_cs_str_cmp_list { # alias for unordered_cs_str_cmp |
| 2774 | my @answerList = @_; |
2751 | my @answerList = @_; |
| … | |
… | |
| 2838 | remove_whitespace -- removes all whitespace |
2815 | remove_whitespace -- removes all whitespace |
| 2839 | compress_whitespace -- removes whitespace from the beginning and end of the string, |
2816 | compress_whitespace -- removes whitespace from the beginning and end of the string, |
| 2840 | and treats one or more whitespace characters in a row as a |
2817 | and treats one or more whitespace characters in a row as a |
| 2841 | single space (true by default) |
2818 | single space (true by default) |
| 2842 | trim_whitespace -- removes whitespace from the beginning and end of the string |
2819 | trim_whitespace -- removes whitespace from the beginning and end of the string |
| 2843 | ignore_case -- ignores the case of the letters (true by default) |
2820 | ignore_case -- ignores the case of the letters (true by default) |
| 2844 | ignore_order -- ignores the order in which letters are entered |
2821 | ignore_order -- ignores the order in which letters are entered |
| 2845 | |
2822 | |
| 2846 | EXAMPLES: |
2823 | EXAMPLES: |
| 2847 | |
2824 | |
| 2848 | str_cmp( "Hello" ) -- matches "Hello", " hello" (same as std_str_cmp() ) |
2825 | str_cmp( "Hello" ) -- matches "Hello", " hello" (same as std_str_cmp() ) |
| … | |
… | |
| 2884 | # final_answer; |
2861 | # final_answer; |
| 2885 | my @output_list = (); |
2862 | my @output_list = (); |
| 2886 | |
2863 | |
| 2887 | foreach my $ans (@ans_list) { |
2864 | foreach my $ans (@ans_list) { |
| 2888 | push(@output_list, STR_CMP( 'correctAnswer' => $ans, |
2865 | push(@output_list, STR_CMP( 'correctAnswer' => $ans, |
| 2889 | 'filters' => $ra_filters, |
2866 | 'filters' => $ra_filters, |
| 2890 | 'type' => 'str_cmp' |
2867 | 'type' => 'str_cmp' |
| 2891 | ) |
2868 | ) |
| 2892 | ); |
2869 | ); |
| 2893 | } |
2870 | } |
| 2894 | |
2871 | |
| 2895 | return @output_list; |
2872 | return @output_list; |
| 2896 | } |
2873 | } |
| 2897 | |
2874 | |
| … | |
… | |
| 2969 | $in = str_filters( $in, 'ignore_order' ); |
2946 | $in = str_filters( $in, 'ignore_order' ); |
| 2970 | |
2947 | |
| 2971 | my $correctQ = ($in eq $correctAnswer) ? 1: 0; |
2948 | my $correctQ = ($in eq $correctAnswer) ? 1: 0; |
| 2972 | |
2949 | |
| 2973 | my $ans_hash = new AnswerHash( |
2950 | my $ans_hash = new AnswerHash( |
| 2974 | 'score' => $correctQ, |
2951 | 'score' => $correctQ, |
| 2975 | 'correct_ans' => $correctAnswer, |
2952 | 'correct_ans' => $correctAnswer, |
| 2976 | 'student_ans' => $in, |
2953 | 'student_ans' => $in, |
| 2977 | 'ans_message' => "", |
2954 | 'ans_message' => "", |
| 2978 | 'type' => "checkbox_cmp", |
2955 | 'type' => "checkbox_cmp", |
| 2979 | 'preview_text_string' => $in, |
2956 | 'preview_text_string' => $in, |
| 2980 | 'original_student_ans' => $original_student_ans |
2957 | 'original_student_ans' => $original_student_ans |
| 2981 | ); |
2958 | ); |
| 2982 | |
|
|
| 2983 | return $ans_hash; |
2959 | return $ans_hash; |
| 2984 | |
2960 | |
| 2985 | }; |
2961 | }; |
| 2986 | |
|
|
| 2987 | return $answer_evaluator; |
2962 | return $answer_evaluator; |
| 2988 | } |
2963 | } |
| 2989 | |
2964 | |
| 2990 | #added 6/28/2000 by David Etlinger |
2965 | #added 6/28/2000 by David Etlinger |
| 2991 | #exactly the same as strict_str_cmp, |
2966 | #exactly the same as strict_str_cmp, |
| 2992 | #but more intuitive to the user |
2967 | #but more intuitive to the user |
| 2993 | sub radio_cmp { |
2968 | sub radio_cmp { |
| 2994 | strict_str_cmp( @_ ); |
2969 | strict_str_cmp( @_ ); |
| 2995 | } |
2970 | } |
| 2996 | |
2971 | |
| 2997 | |
|
|
| 2998 | |
|
|
| 2999 | ########################################################################## |
2972 | ########################################################################## |
| 3000 | ########################################################################## |
2973 | ########################################################################## |
| 3001 | ## Text and e-mail routines |
2974 | ## Text and e-mail routines |
| 3002 | |
|
|
| 3003 | |
2975 | |
| 3004 | sub store_ans_at { |
2976 | sub store_ans_at { |
| 3005 | my $answerStringRef = shift; |
2977 | my $answerStringRef = shift; |
| 3006 | my %options = @_; |
2978 | my %options = @_; |
| 3007 | my $ans_eval= ''; |
2979 | my $ans_eval= ''; |
| … | |
… | |
| 3009 | $ans_eval= sub { |
2981 | $ans_eval= sub { |
| 3010 | my $text = shift; |
2982 | my $text = shift; |
| 3011 | $text = '' unless defined($text); |
2983 | $text = '' unless defined($text); |
| 3012 | $$answerStringRef = $$answerStringRef . $text; |
2984 | $$answerStringRef = $$answerStringRef . $text; |
| 3013 | my $ans_hash = new AnswerHash( |
2985 | my $ans_hash = new AnswerHash( |
| 3014 | 'score' => 1, |
2986 | 'score' => 1, |
| 3015 | 'correct_ans' => '', |
2987 | 'correct_ans' => '', |
| 3016 | 'student_ans' => $text, |
2988 | 'student_ans' => $text, |
| 3017 | 'ans_message' => '', |
2989 | 'ans_message' => '', |
| 3018 | 'type' => 'store_ans_at', |
2990 | 'type' => 'store_ans_at', |
| 3019 | 'original_student_ans' => $text, |
2991 | 'original_student_ans' => $text, |
| 3020 | 'preview_text_string' => '' |
2992 | 'preview_text_string' => '' |
| 3021 | |
|
|
| 3022 | ); |
2993 | ); |
| 3023 | |
2994 | |
| 3024 | return $ans_hash; |
2995 | return $ans_hash; |
| 3025 | }; |
2996 | }; |
| 3026 | } |
2997 | } |
| 3027 | else { |
2998 | else { |
| 3028 | die "Syntax error: \n The argument to store_ans_at() must be a pointer to a scalar.\n(e.g. store_ans_at(~~\$MSG) )\n\n"; |
2999 | die "Syntax error: \n The argument to store_ans_at() must be a pointer to a scalar.\n(e.g. store_ans_at(~~\$MSG) )\n\n"; |
| 3029 | } |
3000 | } |
| 3030 | |
3001 | |
| 3031 | return $ans_eval; |
3002 | return $ans_eval; |
| 3032 | } |
3003 | } |
| 3033 | |
|
|
| 3034 | |
3004 | |
| 3035 | #### subroutines used in producing a questionnaire |
3005 | #### subroutines used in producing a questionnaire |
| 3036 | #### these are at least good models for other answers of this type |
3006 | #### these are at least good models for other answers of this type |
| 3037 | |
3007 | |
| 3038 | my $QUESTIONNAIRE_ANSWERS=''; # stores the answers until it is time to send them |
3008 | my $QUESTIONNAIRE_ANSWERS=''; # stores the answers until it is time to send them |
| … | |
… | |
| 3040 | # but that happens long after all of the text in the problem is |
3010 | # but that happens long after all of the text in the problem is |
| 3041 | # evaluated. |
3011 | # evaluated. |
| 3042 | # this is a utility script for cleaning up the answer output for display in |
3012 | # this is a utility script for cleaning up the answer output for display in |
| 3043 | #the answers. |
3013 | #the answers. |
| 3044 | |
3014 | |
| 3045 | |
|
|
| 3046 | sub DUMMY_ANSWER { |
3015 | sub DUMMY_ANSWER { |
| 3047 | my $num = shift; |
3016 | my $num = shift; |
| 3048 | qq{<INPUT TYPE="HIDDEN" NAME="answer$num" VALUE="">} |
3017 | qq{<INPUT TYPE="HIDDEN" NAME="answer$num" VALUE="">} |
| 3049 | } |
3018 | } |
| 3050 | |
3019 | |
| … | |
… | |
| 3055 | } |
3024 | } |
| 3056 | |
3025 | |
| 3057 | # these next two subroutines show how to modify the "store_and_at()" answer |
3026 | # these next two subroutines show how to modify the "store_and_at()" answer |
| 3058 | # evaluator to add extra information before storing the info |
3027 | # evaluator to add extra information before storing the info |
| 3059 | # They provide a good model for how to tweak answer evaluators in special cases. |
3028 | # They provide a good model for how to tweak answer evaluators in special cases. |
|
|
3029 | |
| 3060 | sub anstext { |
3030 | sub anstext { |
| 3061 | my $num = shift; |
3031 | my $num = shift; |
| 3062 | my $ans_eval_template = store_ans_at(\$QUESTIONNAIRE_ANSWERS); |
3032 | my $ans_eval_template = store_ans_at(\$QUESTIONNAIRE_ANSWERS); |
| 3063 | my $ans_eval = sub { |
3033 | my $ans_eval = sub { |
| 3064 | my $text = shift; |
3034 | my $text = shift; |
| 3065 | $text = '' unless defined($text); |
3035 | $text = '' unless defined($text); |
| 3066 | my $new_text = "\n$main::psvnNumber-Problem-$main::probNum-Question-$num:\n $text "; # modify entered text |
3036 | my $new_text = "\n$main::psvnNumber-Problem-$main::probNum-Question-$num:\n $text "; # modify entered text |
| 3067 | my $out = &$ans_eval_template($new_text); # standard evaluator |
3037 | my $out = &$ans_eval_template($new_text); # standard evaluator |
| 3068 | #warn "$QUESTIONNAIRE_ANSWERS"; |
3038 | #warn "$QUESTIONNAIRE_ANSWERS"; |
| 3069 | $out->{student_ans} = escapeHTML($text); # restore original entered text |
3039 | $out->{student_ans} = escapeHTML($text); # restore original entered text |
| 3070 | $out->{correct_ans} = "Question $num answered"; |
3040 | $out->{correct_ans} = "Question $num answered"; |
| 3071 | $out->{original_student_ans} = escapeHTML($text); |
3041 | $out->{original_student_ans} = escapeHTML($text); |
| 3072 | $out; |
3042 | $out; |
| 3073 | }; |
3043 | }; |
| 3074 | $ans_eval; |
3044 | $ans_eval; |
| 3075 | } |
3045 | } |
| 3076 | |
3046 | |
| 3077 | sub ansradio { |
3047 | sub ansradio { |
| 3078 | my $num = shift; |
3048 | my $num = shift; |
| 3079 | my $ans_eval_template = store_ans_at(\$QUESTIONNAIRE_ANSWERS); |
3049 | my $ans_eval_template = store_ans_at(\$QUESTIONNAIRE_ANSWERS); |
| 3080 | my $ans_eval = sub { |
3050 | my $ans_eval = sub { |
| 3081 | my $text = shift; |
3051 | my $text = shift; |
| 3082 | $text = '' unless defined($text); |
3052 | $text = '' unless defined($text); |
| 3083 | my $new_text = "\n$main::psvnNumber-Problem-$main::probNum-RADIO-$num:\n $text "; # modify entered text |
3053 | my $new_text = "\n$main::psvnNumber-Problem-$main::probNum-RADIO-$num:\n $text "; # modify entered text |
| 3084 | my $out = $ans_eval_template->($new_text); # standard evaluator |
3054 | my $out = $ans_eval_template->($new_text); # standard evaluator |
| 3085 | $out->{student_ans} =escapeHTML($text); # restore original entered text |
3055 | $out->{student_ans} =escapeHTML($text); # restore original entered text |
| 3086 | $out->{original_student_ans} = escapeHTML($text); |
3056 | $out->{original_student_ans} = escapeHTML($text); |
| 3087 | $out; |
3057 | $out; |
| 3088 | }; |
3058 | }; |
| 3089 | |
|
|
| 3090 | |
3059 | |
| 3091 | $ans_eval; |
3060 | $ans_eval; |
| 3092 | } |
3061 | } |
| 3093 | |
3062 | |
| 3094 | # This is another example of how to modify an answer evaluator to obtain |
3063 | # This is another example of how to modify an answer evaluator to obtain |
| … | |
… | |
| 3099 | |
3068 | |
| 3100 | sub mail_answers_to { #accepts the last answer and mails off the result |
3069 | sub mail_answers_to { #accepts the last answer and mails off the result |
| 3101 | my $user_address = shift; |
3070 | my $user_address = shift; |
| 3102 | my $ans_eval = sub { |
3071 | my $ans_eval = sub { |
| 3103 | |
3072 | |
| 3104 | # then mail out all of the answers, including this last one. |
3073 | # then mail out all of the answers, including this last one. |
| 3105 | |
3074 | |
| 3106 | send_mail_to( $user_address, |
3075 | send_mail_to( $user_address, |
| 3107 | 'subject' => "$main::courseName WeBWorK questionnaire", |
3076 | 'subject' => "$main::courseName WeBWorK questionnaire", |
| 3108 | 'body' => $QUESTIONNAIRE_ANSWERS, |
3077 | 'body' => $QUESTIONNAIRE_ANSWERS, |
| 3109 | 'ALLOW_MAIL_TO' => $main::ALLOW_MAIL_TO |
3078 | 'ALLOW_MAIL_TO' => $main::ALLOW_MAIL_TO |
| 3110 | ); |
3079 | ); |
| 3111 | |
3080 | |
| 3112 | my $ans_hash = new AnswerHash( 'score' => 1, |
3081 | my $ans_hash = new AnswerHash( 'score' => 1, |
| 3113 | 'correct_ans' => '', |
3082 | 'correct_ans' => '', |
| 3114 | 'student_ans' => 'Answer recorded', |
3083 | 'student_ans' => 'Answer recorded', |
| 3115 | 'ans_message' => '', |
3084 | 'ans_message' => '', |
| 3116 | 'type' => 'send_mail_to', |
3085 | 'type' => 'send_mail_to', |
| 3117 | ); |
3086 | ); |
| 3118 | |
3087 | |
| 3119 | return $ans_hash; |
3088 | return $ans_hash; |
| 3120 | }; |
3089 | }; |
| 3121 | |
3090 | |
| 3122 | return $ans_eval; |
3091 | return $ans_eval; |
| 3123 | } |
3092 | } |
| 3124 | sub mail_answers_to2 { #accepts the last answer and mails off the result |
3093 | sub mail_answers_to2 { #accepts the last answer and mails off the result |
| 3125 | my $user_address = shift; |
3094 | my $user_address = shift; |
| 3126 | my $subject = shift; |
3095 | my $subject = shift; |
| 3127 | $subject = "$main::courseName WeBWorK questionnaire" unless defined $subject; |
3096 | $subject = "$main::courseName WeBWorK questionnaire" unless defined $subject; |
| 3128 | |
|
|
| 3129 | |
3097 | |
| 3130 | send_mail_to($user_address, |
3098 | send_mail_to($user_address, |
| 3131 | 'subject' => $subject, |
3099 | 'subject' => $subject, |
| 3132 | 'body' => $QUESTIONNAIRE_ANSWERS, |
3100 | 'body' => $QUESTIONNAIRE_ANSWERS, |
| 3133 | 'ALLOW_MAIL_TO' => $main::ALLOW_MAIL_TO |
3101 | 'ALLOW_MAIL_TO' => $main::ALLOW_MAIL_TO |
| 3134 | ); |
3102 | ); |
| 3135 | |
|
|
| 3136 | |
|
|
| 3137 | } |
3103 | } |
| 3138 | |
|
|
| 3139 | |
|
|
| 3140 | |
3104 | |
| 3141 | ########################################################################## |
3105 | ########################################################################## |
| 3142 | ########################################################################## |
3106 | ########################################################################## |
| 3143 | ## Problem Grader Subroutines |
3107 | ## Problem Grader Subroutines |
| 3144 | |
3108 | |
| … | |
… | |
| 3161 | # 'answer1' => 34, 'answer2'=> 'Mozart', etc. |
3125 | # 'answer1' => 34, 'answer2'=> 'Mozart', etc. |
| 3162 | |
3126 | |
| 3163 | # By default the old problem state is simply passed back out again. |
3127 | # By default the old problem state is simply passed back out again. |
| 3164 | my %problem_state = %$rh_problem_state; |
3128 | my %problem_state = %$rh_problem_state; |
| 3165 | |
3129 | |
| 3166 | |
|
|
| 3167 | # %form_options might include |
3130 | # %form_options might include |
| 3168 | # The user login name |
3131 | # The user login name |
| 3169 | # The permission level of the user |
3132 | # The permission level of the user |
| 3170 | # The studentLogin name for this psvn. |
3133 | # The studentLogin name for this psvn. |
| 3171 | # Whether the form is asking for a refresh or is submitting a new answer. |
3134 | # Whether the form is asking for a refresh or is submitting a new answer. |
| 3172 | |
3135 | |
| 3173 | # initial setup of the answer |
3136 | # initial setup of the answer |
|
|
3137 | my %problem_result = ( score => 0, |
|
|
3138 | errors => '', |
|
|
3139 | type => 'std_problem_grader', |
|
|
3140 | msg => '', |
|
|
3141 | ); |
|
|
3142 | # Checks |
|
|
3143 | |
|
|
3144 | my $ansCount = keys %evaluated_answers; # get the number of answers |
|
|
3145 | unless ($ansCount > 0 ) { |
|
|
3146 | $problem_result{msg} = "This problem did not ask any questions."; |
|
|
3147 | return(\%problem_result,\%problem_state); |
|
|
3148 | } |
|
|
3149 | |
|
|
3150 | if ($ansCount > 1 ) { |
|
|
3151 | $problem_result{msg} = 'In order to get credit for this problem all answers must be correct.' ; |
|
|
3152 | } |
|
|
3153 | |
|
|
3154 | unless ($form_options{answers_submitted} == 1) { |
|
|
3155 | return(\%problem_result,\%problem_state); |
|
|
3156 | } |
|
|
3157 | |
|
|
3158 | my $allAnswersCorrectQ=1; |
|
|
3159 | foreach my $ans_name (keys %evaluated_answers) { |
|
|
3160 | # I'm not sure if this check is really useful. |
|
|
3161 | if ( ( ref($evaluated_answers{$ans_name} ) eq 'HASH' ) or ( ref($evaluated_answers{$ans_name}) eq 'AnswerHash' ) ) { |
|
|
3162 | $allAnswersCorrectQ = 0 unless( 1 == $evaluated_answers{$ans_name}->{score} ); |
|
|
3163 | } |
|
|
3164 | else { |
|
|
3165 | die "Error at file ",__FILE__,"line ", __LINE__,": Answer |$ans_name| is not a hash reference\n". |
|
|
3166 | $evaluated_answers{$ans_name} . |
|
|
3167 | "This probably means that the answer evaluator for this answer\n" . |
|
|
3168 | "is not working correctly."; |
|
|
3169 | $problem_result{error} = "Error: Answer $ans_name is not a hash: $evaluated_answers{$ans_name}"; |
|
|
3170 | } |
|
|
3171 | } |
|
|
3172 | # report the results |
|
|
3173 | $problem_result{score} = $allAnswersCorrectQ; |
|
|
3174 | |
|
|
3175 | # I don't like to put in this bit of code. |
|
|
3176 | # It makes it hard to construct error free problem graders |
|
|
3177 | # I would prefer to know that the problem score was numeric. |
|
|
3178 | unless (defined($problem_state{recorded_score}) and $problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) { |
|
|
3179 | $problem_state{recorded_score} = 0; # This gets rid of non-numeric scores |
|
|
3180 | } |
|
|
3181 | # |
|
|
3182 | if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) { |
|
|
3183 | $problem_state{recorded_score} = 1; |
|
|
3184 | } |
|
|
3185 | else { |
|
|
3186 | $problem_state{recorded_score} = 0; |
|
|
3187 | } |
|
|
3188 | |
|
|
3189 | $problem_state{num_of_correct_ans}++ if $allAnswersCorrectQ == 1; |
|
|
3190 | $problem_state{num_of_incorrect_ans}++ if $allAnswersCorrectQ == 0; |
|
|
3191 | (\%problem_result, \%problem_state); |
|
|
3192 | } |
|
|
3193 | |
|
|
3194 | #the only difference between the two versions |
|
|
3195 | #is at the end of the subroutine, where std_problem_grader2 |
|
|
3196 | #records the attempt only if there have been no syntax errors, |
|
|
3197 | #whereas std_problem_grader records it regardless |
|
|
3198 | sub std_problem_grader2 { |
|
|
3199 | my $rh_evaluated_answers = shift; |
|
|
3200 | my $rh_problem_state = shift; |
|
|
3201 | my %form_options = @_; |
|
|
3202 | my %evaluated_answers = %{$rh_evaluated_answers}; |
|
|
3203 | # The hash $rh_evaluated_answers typically contains: |
|
|
3204 | # 'answer1' => 34, 'answer2'=> 'Mozart', etc. |
|
|
3205 | |
|
|
3206 | # By default the old problem state is simply passed back out again. |
|
|
3207 | my %problem_state = %$rh_problem_state; |
|
|
3208 | |
|
|
3209 | # %form_options might include |
|
|
3210 | # The user login name |
|
|
3211 | # The permission level of the user |
|
|
3212 | # The studentLogin name for this psvn. |
|
|
3213 | # Whether the form is asking for a refresh or is submitting a new answer. |
|
|
3214 | |
|
|
3215 | # initial setup of the answer |
| 3174 | my %problem_result = ( score => 0, |
3216 | my %problem_result = ( score => 0, |
| 3175 | errors => '', |
3217 | errors => '', |
| 3176 | type => 'std_problem_grader', |
3218 | type => 'std_problem_grader', |
| 3177 | msg => '', |
3219 | msg => '', |
| 3178 | ); |
3220 | ); |
|
|
3221 | |
|
|
3222 | # syntax errors are not counted. |
|
|
3223 | my $record_problem_attempt = 1; |
| 3179 | # Checks |
3224 | # Checks |
| 3180 | |
3225 | |
| 3181 | my $ansCount = keys %evaluated_answers; # get the number of answers |
3226 | my $ansCount = keys %evaluated_answers; # get the number of answers |
| 3182 | unless ($ansCount > 0 ) { |
3227 | unless ($ansCount > 0 ) { |
| 3183 | $problem_result{msg} = "This problem did not ask any questions."; |
3228 | $problem_result{msg} = "This problem did not ask any questions."; |
| … | |
… | |
| 3210 | $problem_result{score} = $allAnswersCorrectQ; |
3255 | $problem_result{score} = $allAnswersCorrectQ; |
| 3211 | |
3256 | |
| 3212 | # I don't like to put in this bit of code. |
3257 | # I don't like to put in this bit of code. |
| 3213 | # It makes it hard to construct error free problem graders |
3258 | # It makes it hard to construct error free problem graders |
| 3214 | # I would prefer to know that the problem score was numeric. |
3259 | # I would prefer to know that the problem score was numeric. |
| 3215 | unless (defined($problem_state{recorded_score}) and $problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) { |
|
|
| 3216 | $problem_state{recorded_score} = 0; # This gets rid of non-numeric scores |
|
|
| 3217 | } |
|
|
| 3218 | # |
|
|
| 3219 | if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) { |
|
|
| 3220 | $problem_state{recorded_score} = 1; |
|
|
| 3221 | } |
|
|
| 3222 | else { |
|
|
| 3223 | $problem_state{recorded_score} = 0; |
|
|
| 3224 | } |
|
|
| 3225 | |
|
|
| 3226 | $problem_state{num_of_correct_ans}++ if $allAnswersCorrectQ == 1; |
|
|
| 3227 | $problem_state{num_of_incorrect_ans}++ if $allAnswersCorrectQ == 0; |
|
|
| 3228 | (\%problem_result, \%problem_state); |
|
|
| 3229 | } |
|
|
| 3230 | |
|
|
| 3231 | #the only difference between the two versions |
|
|
| 3232 | #is at the end of the subroutine, where std_problem_grader2 |
|
|
| 3233 | #records the attempt only if there have been no syntax errors, |
|
|
| 3234 | #whereas std_problem_grader records it regardless |
|
|
| 3235 | sub std_problem_grader2 { |
|
|
| 3236 | my $rh_evaluated_answers = shift; |
|
|
| 3237 | my $rh_problem_state = shift; |
|
|
| 3238 | my %form_options = @_; |
|
|
| 3239 | my %evaluated_answers = %{$rh_evaluated_answers}; |
|
|
| 3240 | # The hash $rh_evaluated_answers typically contains: |
|
|
| 3241 | # 'answer1' => 34, 'answer2'=> 'Mozart', etc. |
|
|
| 3242 | |
|
|
| 3243 | # By default the old problem state is simply passed back out again. |
|
|
| 3244 | my %problem_state = %$rh_problem_state; |
|
|
| 3245 | |
|
|
| 3246 | |
|
|
| 3247 | # %form_options might include |
|
|
| 3248 | # The user login name |
|
|
| 3249 | # The permission level of the user |
|
|
| 3250 | # The studentLogin name for this psvn. |
|
|
| 3251 | # Whether the form is asking for a refresh or is submitting a new answer. |
|
|
| 3252 | |
|
|
| 3253 | # initial setup of the answer |
|
|
| 3254 | my %problem_result = ( score => 0, |
|
|
| 3255 | errors => '', |
|
|
| 3256 | type => 'std_problem_grader', |
|
|
| 3257 | msg => '', |
|
|
| 3258 | ); |
|
|
| 3259 | |
|
|
| 3260 | # syntax errors are not counted. |
|
|
| 3261 | my $record_problem_attempt = 1; |
|
|
| 3262 | # Checks |
|
|
| 3263 | |
|
|
| 3264 | my $ansCount = keys %evaluated_answers; # get the number of answers |
|
|
| 3265 | unless ($ansCount > 0 ) { |
|
|
| 3266 | $problem_result{msg} = "This problem did not ask any questions."; |
|
|
| 3267 | return(\%problem_result,\%problem_state); |
|
|
| 3268 | } |
|
|
| 3269 | |
|
|
| 3270 | if ($ansCount > 1 ) { |
|
|
| 3271 | $problem_result{msg} = 'In order to get credit for this problem all answers must be correct.' ; |
|
|
| 3272 | } |
|
|
| 3273 | |
|
|
| 3274 | unless ($form_options{answers_submitted} == 1) { |
|
|
| 3275 | return(\%problem_result,\%problem_state); |
|
|
| 3276 | } |
|
|
| 3277 | |
|
|
| 3278 | my $allAnswersCorrectQ=1; |
|
|
| 3279 | foreach my $ans_name (keys %evaluated_answers) { |
|
|
| 3280 | # I'm not sure if this check is really useful. |
|
|
| 3281 | if ( ( ref($evaluated_answers{$ans_name} ) eq 'HASH' ) or ( ref($evaluated_answers{$ans_name}) eq 'AnswerHash' ) ) { |
|
|
| 3282 | $allAnswersCorrectQ = 0 unless( 1 == $evaluated_answers{$ans_name}->{score} ); |
|
|
| 3283 | } |
|
|
| 3284 | else { |
|
|
| 3285 | die "Error at file ",__FILE__,"line ", __LINE__,": Answer |$ans_name| is not a hash reference\n". |
|
|
| 3286 | $evaluated_answers{$ans_name} . |
|
|
| 3287 | "This probably means that the answer evaluator for this answer\n" . |
|
|
| 3288 | "is not working correctly."; |
|
|
| 3289 | $problem_result{error} = "Error: Answer $ans_name is not a hash: $evaluated_answers{$ans_name}"; |
|
|
| 3290 | } |
|
|
| 3291 | } |
|
|
| 3292 | # report the results |
|
|
| 3293 | $problem_result{score} = $allAnswersCorrectQ; |
|
|
| 3294 | |
|
|
| 3295 | # I don't like to put in this bit of code. |
|
|
| 3296 | # It makes it hard to construct error free problem graders |
|
|
| 3297 | # I would prefer to know that the problem score was numeric. |
|
|
| 3298 | unless ($problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) { |
3260 | unless ($problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) { |
| 3299 | $problem_state{recorded_score} = 0; # This gets rid of non-numeric scores |
3261 | $problem_state{recorded_score} = 0; # This gets rid of non-numeric scores |
| 3300 | } |
3262 | } |
| 3301 | # |
3263 | # |
| 3302 | if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) { |
3264 | if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) { |
| … | |
… | |
| 3336 | # Whether the form is asking for a refresh or is submitting a new answer. |
3298 | # Whether the form is asking for a refresh or is submitting a new answer. |
| 3337 | |
3299 | |
| 3338 | # initial setup of the answer |
3300 | # initial setup of the answer |
| 3339 | my $total=0; |
3301 | my $total=0; |
| 3340 | my %problem_result = ( score => 0, |
3302 | my %problem_result = ( score => 0, |
| 3341 | errors => '', |
3303 | errors => '', |
| 3342 | type => 'avg_problem_grader', |
3304 | type => 'avg_problem_grader', |
| 3343 | msg => '', |
3305 | msg => '', |
| 3344 | ); |
3306 | ); |
| 3345 | my $count = keys %evaluated_answers; |
3307 | my $count = keys %evaluated_answers; |
| 3346 | $problem_result{msg} = 'You can earn partial credit on this problem.' if $count >1; |
3308 | $problem_result{msg} = 'You can earn partial credit on this problem.' if $count >1; |
| 3347 | # Return unless answers have been submitted |
3309 | # Return unless answers have been submitted |
| 3348 | unless ($form_options{answers_submitted} == 1) { |
3310 | unless ($form_options{answers_submitted} == 1) { |
| 3349 | return(\%problem_result,\%problem_state); |
3311 | return(\%problem_result,\%problem_state); |