[system] / trunk / pg / macros / extraAnswerEvaluators.pl Repository: Repository Listing bbplugincoursesdistsnplrochestersystemwww

Revision 1079 Revision 1080
1#!/usr/local/bin/webwork-perl 1
2 2
4 4
5# Most of the work is done in special namespaces 5# Most of the work is done in special namespaces
6# At the end, we provide one global function, the interval answer evaluator 6# At the end, we provide one global function, the interval answer evaluator
21 and equations. 21 and equations.
22 22
23 interval_cmp() -- checks answers which are unions of intervals. 23 interval_cmp() -- checks answers which are unions of intervals.
24 It can also be used for checking an ordered pair or 24 It can also be used for checking an ordered pair or
25 list of ordered pairs. 25 list of ordered pairs.
26 26
27 number_list_cmp() -- checks a comma separated list of numbers. By use of 27 number_list_cmp() -- checks a comma separated list of numbers. By use of
28 optional arguments, you can request that order be 28 optional arguments, you can request that order be
29 important, that complex numbers be allowed, and 29 important, that complex numbers be allowed, and
30 specify extra arguments to be sent to num_cmp (or 30 specify extra arguments to be sent to num_cmp (or
31 cplx_cmp) for checking individual entries. 31 cplx_cmp) for checking individual entries.
32 32
33 equation_cmp() -- provides a limited facility for checking equations. 33 equation_cmp() -- provides a limited facility for checking equations.
34 It makes no pretense of checking to see if the real locus 34 It makes no pretense of checking to see if the real locus
35 of the student's equation matches the real locus of the 35 of the student's equation matches the real locus of the
36 instructor's equation. The student's equation must be 36 instructor's equation. The student's equation must be
37 of the same general type as the instructors to get credit. 37 of the same general type as the instructors to get credit.
47=cut 47=cut
48 48
49 49
50{ 50{
51 package Intervals; 51 package Intervals;
52 52
53 # We accept any of the following as infinity (case insensitive) 53 # We accept any of the following as infinity (case insensitive)
54 @infinitywords = ("i", "inf", "infty", "infinity"); 54 @infinitywords = ("i", "inf", "infty", "infinity");
55 \$infinityre = join '|', @infinitywords; 55 \$infinityre = join '|', @infinitywords;
56 \$infinityre = "^([-+m]?)(\$infinityre)\\$"; 56 \$infinityre = "^([-+m]?)(\$infinityre)\\$";
57 57
123 delete(\$opts{'unions'}); 123 delete(\$opts{'unions'});
124 my(\$b1str,\$b2str) = (', ', ', '); 124 my(\$b1str,\$b2str) = (', ', ', ');
125 if(\$unions) { 125 if(\$unions) {
126 (\$b1str,\$b2str) = (' U ', ' \cup '); 126 (\$b1str,\$b2str) = (' U ', ' \cup ');
127 } 127 }
128 128
129 my(\$tmp_ae) = main::num_cmp(1, %opts); 129 my(\$tmp_ae) = main::num_cmp(1, %opts);
130 \$self->{'normalized'} = ''; 130 \$self->{'normalized'} = '';
131 \$self->{'value'} = ''; 131 \$self->{'value'} = '';
132 \$self->{'latex'} = ''; 132 \$self->{'latex'} = '';
133 \$self->{'htmlerror'} = ''; 133 \$self->{'htmlerror'} = '';
231 \$level--; 231 \$level--;
232 } 232 }
233 } 233 }
234 \$spot++; 234 \$spot++;
235 } 235 }
236 236
237 if(\$level>0) { 237 if(\$level>0) {
238 \$self->error("Your expression ended in the middle of an interval.", 238 \$self->error("Your expression ended in the middle of an interval.",
239 [\$hold, \$spot]); 239 [\$hold, \$spot]);
240 return 0; 240 return 0;
241 } 241 }
356 356
357 \$str = uc(\$str); 357 \$str = uc(\$str);
358 \$str =~ s/\s//g; # remove white space 358 \$str =~ s/\s//g; # remove white space
359 \$str; 359 \$str;
360 } 360 }
361 361
363 363
364 sub interval_cmp { 364 sub interval_cmp {
365 365
366 my \$right_ans = shift; 366 my \$right_ans = shift;
367 my %opts = @_; 367 my %opts = @_;
368 368
369 \$opts{'mode'} = 'std' unless defined(\$opts{'mode'}); 369 \$opts{'mode'} = 'std' unless defined(\$opts{'mode'});
370 \$opts{'tolType'} = 'relative' unless defined(\$opts{'tolType'}); 370 \$opts{'tolType'} = 'relative' unless defined(\$opts{'tolType'});
371 371
372 my \$ans_eval = sub { 372 my \$ans_eval = sub {
373 my \$student = shift; 373 my \$student = shift;
374 374
375 my \$ans_hash = new AnswerHash( 375 my \$ans_hash = new AnswerHash(
376 'score'=>0, 376 'score'=>0,
377 'correct_ans'=>\$right_ans, 377 'correct_ans'=>\$right_ans,
378 'student_ans'=>\$student, 378 'student_ans'=>\$student,
379 'original_student_ans' => \$student, 379 'original_student_ans' => \$student,
404 # Error in student input 404 # Error in student input
405 \$ans_hash->{'student_ans'} = "error: \$student_int->{htmlerror}"; 405 \$ans_hash->{'student_ans'} = "error: \$student_int->{htmlerror}";
406 \$ans_hash->{'ans_message'} = "\$student_int->{error_msg}"; 406 \$ans_hash->{'ans_message'} = "\$student_int->{error_msg}";
407 return \$ans_hash; 407 return \$ans_hash;
408 } 408 }
409 409
410 \$ans_hash->{'student_ans'} = \$student_int->{'value'}; 410 \$ans_hash->{'student_ans'} = \$student_int->{'value'};
411 \$ans_hash->{'preview_text_string'} = \$student_int->{'normalized'}; 411 \$ans_hash->{'preview_text_string'} = \$student_int->{'normalized'};
412 \$ans_hash->{'preview_latex_string'} = \$student_int->{'latex'}; 412 \$ans_hash->{'preview_latex_string'} = \$student_int->{'latex'};
413 } 413 }
414 414
439} 439}
440 440
441 441
442{ 442{
443 package Number_List; 443 package Number_List;
444 444
445 sub new { 445 sub new {
446 my \$class = shift; 446 my \$class = shift;
447 my \$base_string = shift; 447 my \$base_string = shift;
448 my \$self = {}; 448 my \$self = {};
449 \$self->{'original'} = \$base_string; 449 \$self->{'original'} = \$base_string;
464 my (\$in,\$PG_errors,\$PG_errors_long) = main::PG_restricted_eval(\$instring); 464 my (\$in,\$PG_errors,\$PG_errors_long) = main::PG_restricted_eval(\$instring);
465 return (\$in+0*Complex1::i()); 465 return (\$in+0*Complex1::i());
466 } 466 }
467 467
468 468
469 469
470 sub parse_number_list { 470 sub parse_number_list {
471 my(\$self) = shift; 471 my(\$self) = shift;
472 my(%opts) = @_; 472 my(%opts) = @_;
473 my(\$str) = \$self->{'original'}; 473 my(\$str) = \$self->{'original'};
474 my(@ans_list) = (); 474 my(@ans_list) = ();
532 \$level--; 532 \$level--;
533 } # end of closing brace 533 } # end of closing brace
534 } 534 }
535 \$spot++; 535 \$spot++;
536 } 536 }
537 537
538 if(\$level>1) { 538 if(\$level>1) {
539 \$self->error("Your expression has unmatched parens.", 539 \$self->error("Your expression has unmatched parens.",
540 [\$hold, \$spot]); 540 [\$hold, \$spot]);
541 return 0; 541 return 0;
542 } 542 }
543 \$cur = substr(\$str,\$hold, \$spot-\$hold); 543 \$cur = substr(\$str,\$hold, \$spot-\$hold);
544 544
545 my(\$tmp_ah); 545 my(\$tmp_ah);
546 \$tmp_ah = \$tmp_ae->evaluate(\$cur); 546 \$tmp_ah = \$tmp_ae->evaluate(\$cur);
547 547
548 if(has_errors(\$tmp_ah)) { 548 if(has_errors(\$tmp_ah)) {
549 \$self->error("I could not parse your input correctly",[\$hold, \$spot]); 549 \$self->error("I could not parse your input correctly",[\$hold, \$spot]);
550 return 0; 550 return 0;
551 } 551 }
552 if(not (\$cur =~ /\w/)) { # Input was empty 552 if(not (\$cur =~ /\w/)) { # Input was empty
578 \$opts{'mode'} = 'std' unless defined(\$opts{'mode'}); 578 \$opts{'mode'} = 'std' unless defined(\$opts{'mode'});
579 \$opts{'tolType'} = 'relative' unless defined(\$opts{'tolType'}); 579 \$opts{'tolType'} = 'relative' unless defined(\$opts{'tolType'});
580 580
581 my \$ans_eval = sub { 581 my \$ans_eval = sub {
582 my \$student = shift; 582 my \$student = shift;
583 583
584 my \$ans_hash = new AnswerHash( 584 my \$ans_hash = new AnswerHash(
585 'score'=>0, 585 'score'=>0,
586 'correct_ans'=>\$right_ans, 586 'correct_ans'=>\$right_ans,
587 'student_ans'=>\$student, 587 'student_ans'=>\$student,
588 'original_student_ans' => \$student, 588 'original_student_ans' => \$student,
600 } 600 }
601 601
602 \$ans_hash->{'student_ans'} = \$student_list->{'value'}; 602 \$ans_hash->{'student_ans'} = \$student_list->{'value'};
603 \$ans_hash->{'preview_text_string'} = \$student_list->{'normalized'}; 603 \$ans_hash->{'preview_text_string'} = \$student_list->{'normalized'};
604 \$ans_hash->{'preview_latex_string'} = \$student_list->{'latex'}; 604 \$ans_hash->{'preview_latex_string'} = \$student_list->{'latex'};
605 605
606 my \$correct_list = new Number_List(\$right_ans); 606 my \$correct_list = new Number_List(\$right_ans);
607 if(! \$correct_list->parse_number_list(%opts)) { 607 if(! \$correct_list->parse_number_list(%opts)) {
608 # Cannot parse instuctor's answer! 608 # Cannot parse instuctor's answer!
609 \$ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem."; 609 \$ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem.";
610 return \$ans_hash; 610 return \$ans_hash;
611 } 611 }
612 if (cmp_numlists(\$correct_list, \$student_list, %opts)) { 612 if (cmp_numlists(\$correct_list, \$student_list, %opts)) {
613 \$ans_hash -> setKeys('score' => 1); 613 \$ans_hash -> setKeys('score' => 1);
614 } 614 }
615 615
616 return \$ans_hash; 616 return \$ans_hash;
617 }; 617 };
618 618
619 return \$ans_eval; 619 return \$ans_eval;
620 } 620 }
630 my(\$strict_ordering) = 0; 630 my(\$strict_ordering) = 0;
631 if (defined(\$opts{'ordered'}) && (\$opts{'ordered'} eq 'yes')) { 631 if (defined(\$opts{'ordered'}) && (\$opts{'ordered'} eq 'yes')) {
632 \$strict_ordering = 1; 632 \$strict_ordering = 1;
633 } 633 }
634 delete(\$opts{'ordered'}); 634 delete(\$opts{'ordered'});
635 635
636 my \$complex=0; 636 my \$complex=0;
637 if(defined(\$opts{'complex'}) && 637 if(defined(\$opts{'complex'}) &&
638 (\$opts{'complex'} =~ /(yes|ok)/i)) { 638 (\$opts{'complex'} =~ /(yes|ok)/i)) {
639 \$complex=1; 639 \$complex=1;
640 delete(\$opts{'mode'}); 640 delete(\$opts{'mode'});
642 delete(\$opts{'complex'}); 642 delete(\$opts{'complex'});
643 643
644 my(@fs1) = @{\$in1->{'forsort'}}; 644 my(@fs1) = @{\$in1->{'forsort'}};
645 my(@fs2) = @{\$in2->{'forsort'}}; 645 my(@fs2) = @{\$in2->{'forsort'}};
646 646
647 647
648 # Same number of values? 648 # Same number of values?
649 if (scalar(@fs1) != scalar(@fs2)) { 649 if (scalar(@fs1) != scalar(@fs2)) {
650 return 0; 650 return 0;
651 } 651 }
652 652
658 658
659 if(\$strict_ordering==0) { 659 if(\$strict_ordering==0) {
660 @fs1 = main::PGsort(sub {\$_[0]->[1] <=> \$_[1]->[1];}, @fs1); 660 @fs1 = main::PGsort(sub {\$_[0]->[1] <=> \$_[1]->[1];}, @fs1);
661 @fs2 = main::PGsort(sub {\$_[0]->[1] <=> \$_[1]->[1];}, @fs2); 661 @fs2 = main::PGsort(sub {\$_[0]->[1] <=> \$_[1]->[1];}, @fs2);
662 } 662 }
663 663
664 for (\$j=0; \$j<scalar(@fs1);\$j++) { 664 for (\$j=0; \$j<scalar(@fs1);\$j++) {
665 my \$ae; 665 my \$ae;
666 if(\$complex) { 666 if(\$complex) {
667 \$ae = main::cplx_cmp(\$fs1[\$j]->[1], %opts); 667 \$ae = main::cplx_cmp(\$fs1[\$j]->[1], %opts);
668 } else { 668 } else {
689 \$ap->inittokenizer(\$self->{'original'}); 689 \$ap->inittokenizer(\$self->{'original'});
690 \$ap->error(@args); 690 \$ap->error(@args);
691 \$self->{htmlerror} = \$ap->{htmlerror}; 691 \$self->{htmlerror} = \$ap->{htmlerror};
692 \$self->{error_msg} = \$ap->{error_msg}; 692 \$self->{error_msg} = \$ap->{error_msg};
693 } 693 }
694 694
695 sub has_errors { 695 sub has_errors {
696 my(\$ah) = shift; 696 my(\$ah) = shift;
697 697
698 if(\$ah->{'student_ans'} =~ /error/) { 698 if(\$ah->{'student_ans'} =~ /error/) {
699 return 1; 699 return 1;
700 } 700 }
701 my(\$am) = \$ah->{'ans_message'}; 701 my(\$am) = \$ah->{'ans_message'};
702 if(\$am =~ /error/) { 702 if(\$am =~ /error/) {
715# interval_cmp("[1,2) U [3, infty)", options) 715# interval_cmp("[1,2) U [3, infty)", options)
716# where options are key/value pairs for num_cmp. Also, we allow the option 716# where options are key/value pairs for num_cmp. Also, we allow the option
717# 'ordering' which can be 'strict', which means that we do not want to test rearrangements 717# 'ordering' which can be 'strict', which means that we do not want to test rearrangements
718# of the intervals. 718# of the intervals.
719 719
720 720
721} 721}
722 722
723{ 723{
724 package Equation_eval; 724 package Equation_eval;
725 725
726 sub split_eqn { 726 sub split_eqn {
727 my \$instring = shift; 727 my \$instring = shift;
728 728
729 split /=/, \$instring; 729 split /=/, \$instring;
730 } 730 }
731 731
732 732
733 sub equation_cmp { 733 sub equation_cmp {
734 my \$right_ans = shift; 734 my \$right_ans = shift;
735 my %opts = @_; 735 my %opts = @_;
736 my \$vars = ['x','y']; 736 my \$vars = ['x','y'];
737 737
738 738
739 \$vars = \$opts{'vars'} if defined(\$opts{'vars'}); 739 \$vars = \$opts{'vars'} if defined(\$opts{'vars'});
740 740
741 my \$ans_eval = sub { 741 my \$ans_eval = sub {
742 my \$student = shift; 742 my \$student = shift;
743 743
744 my \$ans_hash = new AnswerHash( 744 my \$ans_hash = new AnswerHash(
745 'score'=>0, 745 'score'=>0,
746 'correct_ans'=>\$right_ans, 746 'correct_ans'=>\$right_ans,
747 'student_ans'=>\$student, 747 'student_ans'=>\$student,
748 'original_student_ans' => \$student, 748 'original_student_ans' => \$student,
751 'preview_text_string'=>'', 751 'preview_text_string'=>'',
752 'preview_latex_string'=>'', 752 'preview_latex_string'=>'',
753 ); 753 );
754 754
755 if(! (\$student =~ /\S/)) { return \$ans_hash; } 755 if(! (\$student =~ /\S/)) { return \$ans_hash; }
756 756
757 my @right= split_eqn(\$right_ans); 757 my @right= split_eqn(\$right_ans);
758 if(scalar(@right) != 2) { 758 if(scalar(@right) != 2) {
759 \$ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem."; 759 \$ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem.";
760 return \$ans_hash; 760 return \$ans_hash;
761 } 761 }
772 \$ah=main::check_syntax(\$ah); 772 \$ah=main::check_syntax(\$ah);
773 if(\$ah->{error_flag}) { 773 if(\$ah->{error_flag}) {
774 \$ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem."; 774 \$ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem.";
775 return \$ans_hash; 775 return \$ans_hash;
776 } 776 }
777 777
778 \$ah->input(\$right[1]); 778 \$ah->input(\$right[1]);
779 \$ah=main::check_syntax(\$ah); 779 \$ah=main::check_syntax(\$ah);
780 if(\$ah->{error_flag}) { 780 if(\$ah->{error_flag}) {
781 \$ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem."; 781 \$ans_hash->{'ans_message'} = "Tell your professor that there is an error in this problem.";
782 return \$ans_hash; 782 return \$ans_hash;
792 \$ans_hash->{'ans_message'} = "Syntax error on the left side of your equation."; 792 \$ans_hash->{'ans_message'} = "Syntax error on the left side of your equation.";
793 return \$ans_hash; 793 return \$ans_hash;
794 } 794 }
795 \$prevs[0] = \$ah->{'preview_latex_string'}; 795 \$prevs[0] = \$ah->{'preview_latex_string'};
796 \$prevstxt[0] = \$ah->{'preview_text_string'}; 796 \$prevstxt[0] = \$ah->{'preview_text_string'};
797 797
798 798
799 \$ah->input(\$studsplit[1]); 799 \$ah->input(\$studsplit[1]);
800 \$ah=main::check_syntax(\$ah); 800 \$ah=main::check_syntax(\$ah);
801 if(\$ah->{error_flag}) { 801 if(\$ah->{error_flag}) {
802 \$ans_hash->{'ans_message'} = "Syntax error on the right side of your equation."; 802 \$ans_hash->{'ans_message'} = "Syntax error on the right side of your equation.";
803 return \$ans_hash; 803 return \$ans_hash;
805 \$prevs[1] = \$ah->{'preview_latex_string'}; 805 \$prevs[1] = \$ah->{'preview_latex_string'};
806 \$prevstxt[1] = \$ah->{'preview_text_string'}; 806 \$prevstxt[1] = \$ah->{'preview_text_string'};
807 807
808 \$ans_hash->{'preview_latex_string'} = "\$prevs[0] = \$prevs[1]"; 808 \$ans_hash->{'preview_latex_string'} = "\$prevs[0] = \$prevs[1]";
809 \$ans_hash->{'preview_text_string'} = "\$prevstxt[0] = \$prevstxt[1]"; 809 \$ans_hash->{'preview_text_string'} = "\$prevstxt[0] = \$prevstxt[1]";
810 810
811 811
812 # Check for answer equivalent to 0=0 812 # Check for answer equivalent to 0=0
813 # Could be false positive below because of parameter 813 # Could be false positive below because of parameter
814 my \$ae = main::fun_cmp("0", %opts); 814 my \$ae = main::fun_cmp("0", %opts);
815 my \$res = \$ae->evaluate("\$studsplit[0]-(\$studsplit[1])"); 815 my \$res = \$ae->evaluate("\$studsplit[0]-(\$studsplit[1])");
816 if(\$res->{'score'}==1) { 816 if(\$res->{'score'}==1) {
827 if(\$res->{'score'}==1) { 827 if(\$res->{'score'}==1) {
828 return \$ans_hash; 828 return \$ans_hash;
829 } 829 }
830 830
831 # Finally, use fun_cmp to check the answers 831 # Finally, use fun_cmp to check the answers
832 832
833 \$ae = main::fun_cmp("o*(\$right[0]-(\$right[1]))", vars=>\$vars, params=>['o'], %opts); 833 \$ae = main::fun_cmp("o*(\$right[0]-(\$right[1]))", vars=>\$vars, params=>['o'], %opts);
834 \$res= \$ae->evaluate("\$studsplit[0]-(\$studsplit[1])"); 834 \$res= \$ae->evaluate("\$studsplit[0]-(\$studsplit[1])");
835 \$ans_hash-> setKeys('score' => \$res->{'score'}); 835 \$ans_hash-> setKeys('score' => \$res->{'score'});
836 836
837 return \$ans_hash; 837 return \$ans_hash;
838 }; 838 };
839 839
840 return \$ans_eval; 840 return \$ans_eval;
841 } 841 }

Legend:
 Removed from v.1079 changed lines Added in v.1080