[system] / trunk / webwork / system / courseScripts / PGanswermacros.pl Repository:
ViewVC logotype

Diff of /trunk/webwork/system/courseScripts/PGanswermacros.pl

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

Revision 80 Revision 81
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
100Technical note: the routines in this file are not actually answer evaluators. Instead, they create 100Technical note: the routines in this file are not actually answer evaluators. Instead, they create
101answer evaluators. An answer evaluator is an anonymous subroutine, referenced by a named scalar. The 101answer evaluators. An answer evaluator is an anonymous subroutine, referenced by a named scalar. The
109BEGIN { 109BEGIN {
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
114my ($BR , # convenient localizations. 114my ($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
132sub _PGanswermacros_init { 133sub _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;
170options. In addition, there is the special case of std_num_str_cmp(), which can evaluate 170options. In addition, there is the special case of std_num_str_cmp(), which can evaluate
171both numbers and strings. 171both numbers and strings.
172 172
173Numerical Comparison Options 173Numerical 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
384sub std_num_cmp_list { 385sub 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
406sub std_num_cmp_abs { # compare numbers allowing use of elementary functions with absolute tolerance 407sub 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
427sub std_num_cmp_abs_list { 428sub 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
448sub frac_num_cmp { # only allow fractions and numbers as submitted answer 448sub 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
473sub frac_num_cmp_list { 473sub 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
494sub frac_num_cmp_abs { # only allow fraction expressions as submitted answer with absolute tolerance 494sub 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
515sub frac_num_cmp_abs_list { 516sub 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
561sub arith_num_cmp_list { 562sub 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
581sub arith_num_cmp_abs { # only allow arithmetic expressions as submitted answer with absolute tolerance 583sub 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
601sub 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
604sub 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
621sub strict_num_cmp { # only allow numbers as submitted answer 624sub 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
644sub strict_num_cmp_list { # compare numbers 648sub 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
665sub strict_num_cmp_abs { # only allow numbers as submitted answer with absolute tolerance 670sub 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
688sub strict_num_cmp_abs_list { # compare numbers 691sub 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
722sub numerical_compare_with_units { 726sub 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
782ANS( num_cmp( answer or answer_array_ref, options_hash ) ); 786ANS( 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
816sub num_cmp { 822sub 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
1122sub fix_answers_for_display { 1112sub 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
1134sub evaluatesToNumber { 1125sub 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
1223variable functions. This usage is strongly discouraged as unnecessarily 1211variable functions. This usage is strongly discouraged as unnecessarily
1224confusing. Avoid it. 1212confusing. Avoid it.
1225 1213
1226Default Values (As of 7/24/2000) (Option -- Variable Name -- Value) 1214Default 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
1343sub adaptive_function_cmp { 1331sub 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
1382sub function_cmp { 1369sub 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
1424sub function_cmp_abs { ## similar to function_cmp but uses absolute tolerance 1411sub 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
1446sub function_cmp_up_to_constant_abs { ## for antiderivative problems 1433sub 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
1521Compares a function or a list of functions, using a named hash of options to set 1508Compares a function or a list of functions, using a named hash of options to set
1525ANS( fun_cmp( answer or answer_array_ref, options_hash ) ); 1512ANS( 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
1551ANSWER: 1538ANSWER:
1559VARIABLES: 1546VARIABLES:
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.
1596sub fun_cmp { 1583sub 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
1697sub FUNCTION_CMP { 1684sub 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
1836sub is_array { 1822sub 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
1900sub check_strings { 1881sub 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
1959sub check_units { 1941sub 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
2435sub calculate_difference_vector { 2413sub 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
2506ensure that there are no unexpected matches or rejections. 2482ensure that there are no unexpected matches or rejections.
2507 2483
2508String Filters 2484String 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
2553sub str_filters { 2530sub 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
2744sub unordered_str_cmp { # unordered, case insensitive, spaces ignored 2721sub 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
2754sub unordered_str_cmp_list { # alias for unordered_str_cmp 2731sub unordered_str_cmp_list { # alias for unordered_str_cmp
2755 my @answerList = @_; 2732 my @answerList = @_;
2762 2739
2763sub unordered_cs_str_cmp { # unordered, case sensitive, spaces ignored 2740sub 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
2773sub unordered_cs_str_cmp_list { # alias for unordered_cs_str_cmp 2750sub 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
2846EXAMPLES: 2823EXAMPLES:
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
2993sub radio_cmp { 2968sub 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
3004sub store_ans_at { 2976sub 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
3038my $QUESTIONNAIRE_ANSWERS=''; # stores the answers until it is time to send them 3008my $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
3046sub DUMMY_ANSWER { 3015sub 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
3060sub anstext { 3030sub 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
3077sub ansradio { 3047sub 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
3100sub mail_answers_to { #accepts the last answer and mails off the result 3069sub 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}
3124sub mail_answers_to2 { #accepts the last answer and mails off the result 3093sub 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
3198sub 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
3235sub 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);

Legend:
Removed from v.80  
changed lines
  Added in v.81

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9