| … | |
… | |
| 64 | # Parse and evaluate the student answer |
64 | # Parse and evaluate the student answer |
| 65 | # |
65 | # |
| 66 | $ans->score(0); # assume failure |
66 | $ans->score(0); # assume failure |
| 67 | $ans->{student_value} = $ans->{student_formula} = Parser::Formula($ans->{student_ans}); |
67 | $ans->{student_value} = $ans->{student_formula} = Parser::Formula($ans->{student_ans}); |
| 68 | $ans->{student_value} = Parser::Evaluate($ans->{student_formula}) |
68 | $ans->{student_value} = Parser::Evaluate($ans->{student_formula}) |
| 69 | if defined($ans->{student_formula}) && $ans->{student_formula}->isConstant && |
69 | if defined($ans->{student_formula}) && $ans->{student_formula}->isConstant; |
| 70 | $ans->{correct_value}->class ne 'Formula'; |
|
|
| 71 | # |
70 | # |
| 72 | # If it parsed OK, save the output forms and check if it is correct |
71 | # If it parsed OK, save the output forms and check if it is correct |
| 73 | # otherwise report an error |
72 | # otherwise report an error |
| 74 | # |
73 | # |
| 75 | if (defined $ans->{student_value}) { |
74 | if (defined $ans->{student_value}) { |
| … | |
… | |
| 97 | my $cmp_error = $ans->{cmp_error} || 'cmp_error'; |
96 | my $cmp_error = $ans->{cmp_error} || 'cmp_error'; |
| 98 | $self->$cmp_error($ans); |
97 | $self->$cmp_error($ans); |
| 99 | } else { |
98 | } else { |
| 100 | $ans->{ans_message} = $ans->{error_message} = |
99 | $ans->{ans_message} = $ans->{error_message} = |
| 101 | "Your answer isn't ".lc($ans->{cmp_class}). |
100 | "Your answer isn't ".lc($ans->{cmp_class}). |
| 102 | " (it looks like ".lc($ans->{student_value}->showClass(1)).")" |
101 | " (it looks like ".lc($ans->{student_value}->showClass).")" |
| 103 | if !$ans->{isPreview} && $ans->{showTypeWarnings} && !$ans->{error_message}; |
102 | if !$ans->{isPreview} && $ans->{showTypeWarnings} && !$ans->{error_message}; |
| 104 | } |
103 | } |
| 105 | } |
104 | } |
| 106 | |
105 | |
| 107 | # |
106 | # |
| … | |
… | |
| 116 | # |
115 | # |
| 117 | # Class name for cmp error messages |
116 | # Class name for cmp error messages |
| 118 | # |
117 | # |
| 119 | sub cmp_class { |
118 | sub cmp_class { |
| 120 | my $self = shift; my $ans = shift; |
119 | my $self = shift; my $ans = shift; |
| 121 | my $class = $self->showClass; |
120 | my $class = $self->showClass; $class =~ s/Real //; |
|
|
121 | return $class if $class =~ m/Formula/; |
| 122 | return "an Interval or Union" if $class =~ m/Interval/i; |
122 | return "an Interval or Union" if $class =~ m/Interval/i; |
| 123 | $class =~ s/Real //; |
|
|
| 124 | return $class; |
123 | return $class; |
| 125 | } |
124 | } |
| 126 | |
125 | |
| 127 | # |
126 | # |
| 128 | # Student answer evaluation failed. |
127 | # Student answer evaluation failed. |
| … | |
… | |
| 503 | my $value = $ans->{entry_type}; |
502 | my $value = $ans->{entry_type}; |
| 504 | my $ltype = $ans->{list_type}; |
503 | my $ltype = $ans->{list_type}; |
| 505 | |
504 | |
| 506 | $value = (Value::isValue($typeMatch)? lc($typeMatch->cmp_class): 'value') |
505 | $value = (Value::isValue($typeMatch)? lc($typeMatch->cmp_class): 'value') |
| 507 | unless defined($value); |
506 | unless defined($value); |
| 508 | $value =~ s/(real|complex) //; $ans->{cmp_class} = $value; $value =~ s/^an? //; |
507 | $value =~ s/(real|complex) //; $ans->{cmp_class} = $value; |
|
|
508 | $value =~ s/^an? //; $value = 'formula' if $value =~ m/formula/; |
| 509 | $showTypeWarnings = $showHints = $showLengthHints = 0 if $ans->{isPreview}; |
509 | $showTypeWarnings = $showHints = $showLengthHints = 0 if $ans->{isPreview}; |
| 510 | |
510 | |
| 511 | # |
511 | # |
| 512 | # Get the lists of correct and student answers |
512 | # Get the lists of correct and student answers |
|
|
513 | # (split formulas that return lists or unions) |
| 513 | # |
514 | # |
| 514 | my @correct = $self->value; |
515 | my @correct = (); |
|
|
516 | if ($self->class ne 'Formula') {@correct = $self->value} |
|
|
517 | else {@correct = Value::List->splitFormula($self,$ans)} |
| 515 | my $student = $ans->{student_value}; |
518 | my $student = $ans->{student_value}; |
| 516 | my @student = ($student); |
519 | my @student = ($student); |
| 517 | if ($student->class eq $self->class && |
520 | if ($student->class eq 'Formula' && $student->type eq $self->type) { |
|
|
521 | @student = Value::List->splitFormula($student,$ans); |
|
|
522 | } elsif ($student->class ne 'Formula' && $student->class eq $self->class && |
| 518 | ($allowParens || (!$student->{open} && !$student->{close}))) { |
523 | ($allowParens || (!$student->{open} && !$student->{close}))) { |
| 519 | @student = @{$student->{data}}; |
524 | @student = @{$student->{data}}; |
| 520 | } elsif ($student->class eq 'Formula' && $student->type eq $self->type) { |
|
|
| 521 | # |
|
|
| 522 | # Convert a formula returning a list to a list of formulas |
|
|
| 523 | # |
|
|
| 524 | @student = (); my @entries; |
|
|
| 525 | if ($self->type eq 'List') {@entries = @{$student->{tree}{coords}}} |
|
|
| 526 | else {@entries = $student->{tree}->makeUnion} |
|
|
| 527 | foreach my $entry (@entries) { |
|
|
| 528 | my $v = Parser::Formula($entry); |
|
|
| 529 | $v = Parser::Evaluate($v) if (defined($v) && $v->isConstant); |
|
|
| 530 | push(@student,$v); |
|
|
| 531 | # |
|
|
| 532 | # In case there is an error evaluating the answer. |
|
|
| 533 | # (there shouldn't be, but you never know) |
|
|
| 534 | # |
|
|
| 535 | if (!defined($v)) { |
|
|
| 536 | my $cmp_error = $ans->{cmp_error} || 'cmp_error'; |
|
|
| 537 | $self->$cmp_error; return; |
|
|
| 538 | } |
|
|
| 539 | } |
525 | } |
| 540 | } |
526 | return if $ans->{split_error}; |
|
|
527 | if (scalar(@correct) == 0 && scalar(@student) == 0) {$ans->score(1); return} |
| 541 | |
528 | |
| 542 | # |
529 | # |
| 543 | # Initialize the score |
530 | # Initialize the score |
| 544 | # |
531 | # |
| 545 | my $maxscore = scalar(@correct); |
532 | my $M = scalar(@correct); |
| 546 | my $m = scalar(@student); |
533 | my $m = scalar(@student); |
| 547 | $maxscore = $m if ($m > $maxscore); |
534 | my $maxscore = ($m > $M)? $m : $M; |
| 548 | my $score = 0; my @errors; my $i = 0; |
535 | my $score = 0; my @errors; my $i = 0; |
| 549 | |
536 | |
| 550 | # |
537 | # |
| 551 | # Loop through student answers looking for correct ones |
538 | # Loop through student answers looking for correct ones |
| 552 | # |
539 | # |
| … | |
… | |
| 565 | } |
552 | } |
| 566 | } |
553 | } |
| 567 | # |
554 | # |
| 568 | # Give messages about incorrect answers |
555 | # Give messages about incorrect answers |
| 569 | # |
556 | # |
| 570 | my $nth = ''; my $class = $self->cmp_class; |
557 | my $nth = ''; my $answer = 'answer'; my $class = $self->cmp_class; |
| 571 | if (scalar(@student) > 1) { |
558 | if (scalar(@student) > 1) { |
| 572 | $nth = ' '.$self->NameForNumber($i); |
559 | $nth = ' '.$self->NameForNumber($i); |
| 573 | $class = $ans->{cmp_class}; |
560 | $class = $ans->{cmp_class}; |
|
|
561 | $answer = 'value'; |
| 574 | } |
562 | } |
| 575 | if ($showTypeWarnings && !$typeMatch->typeMatch($entry,$ans) && |
563 | if ($showTypeWarnings && !$typeMatch->typeMatch($entry,$ans) && |
| 576 | !($ans->{ignoreStrings} && $entry->class eq 'String')) { |
564 | !($ans->{ignoreStrings} && $entry->class eq 'String')) { |
| 577 | push(@errors,"Your$nth value isn't ".lc($class). |
565 | push(@errors,"Your$nth $answer isn't ".lc($class). |
| 578 | " (it looks like ".lc($entry->showClass(1)).")"); |
566 | " (it looks like ".lc($entry->showClass).")"); |
| 579 | } elsif ($showHints && $m > 1) { |
567 | } elsif ($showHints && $m > 1) { |
| 580 | push(@errors,"Your$nth $value is incorrect"); |
568 | push(@errors,"Your$nth $value is incorrect"); |
| 581 | } |
569 | } |
| 582 | } |
570 | } |
| 583 | |
571 | |
| … | |
… | |
| 587 | if ($showLengthHints) { |
575 | if ($showLengthHints) { |
| 588 | $value =~ s/ or /s or /; # fix "interval or union" |
576 | $value =~ s/ or /s or /; # fix "interval or union" |
| 589 | push(@errors,"There should be more ${value}s in your $ltype") |
577 | push(@errors,"There should be more ${value}s in your $ltype") |
| 590 | if ($score == $m && scalar(@correct) > 0); |
578 | if ($score == $m && scalar(@correct) > 0); |
| 591 | push(@errors,"There should be fewer ${value}s in your $ltype") |
579 | push(@errors,"There should be fewer ${value}s in your $ltype") |
| 592 | if ($score < $maxscore && $score == scalar($self->value)); |
580 | if ($score < $maxscore && $score == $M); |
| 593 | } |
581 | } |
| 594 | |
582 | |
| 595 | # |
583 | # |
| 596 | # Finalize the score |
584 | # Finalize the score |
| 597 | # |
585 | # |
| 598 | $score = 0 if ($score != $maxscore && !$partialCredit); |
586 | $score = 0 if ($score != $maxscore && !$partialCredit); |
| 599 | $ans->score($score/$maxscore); |
587 | $ans->score($score/$maxscore); |
| 600 | push(@errors,"Score = $ans->{score}") if $ans->{debug}; |
588 | push(@errors,"Score = $ans->{score}") if $ans->{debug}; |
| 601 | $ans->{error_message} = $ans->{ans_message} = join("\n",@errors); |
589 | $ans->{error_message} = $ans->{ans_message} = join("\n",@errors); |
|
|
590 | } |
|
|
591 | |
|
|
592 | # |
|
|
593 | # Split a formula that is a list or union into a |
|
|
594 | # list of formulas (or Value objects). |
|
|
595 | # |
|
|
596 | sub splitFormula { |
|
|
597 | my $self = shift; my $formula = shift; my $ans = shift; |
|
|
598 | my @formula; my @entries; |
|
|
599 | if ($formula->type eq 'List') {@entries = @{$formula->{tree}{coords}}} |
|
|
600 | else {@entries = $formula->{tree}->makeUnion} |
|
|
601 | foreach my $entry (@entries) { |
|
|
602 | my $v = Parser::Formula($entry); |
|
|
603 | $v = Parser::Evaluate($v) if (defined($v) && $v->isConstant); |
|
|
604 | push(@formula,$v); |
|
|
605 | # |
|
|
606 | # There shouldn't be an error evaluating the formula, |
|
|
607 | # but you never know... |
|
|
608 | # |
|
|
609 | if (!defined($v)) { |
|
|
610 | $ans->{split_error} = 1; |
|
|
611 | my $cmp_error = $ans->{cmp_error} || 'cmp_error'; |
|
|
612 | $self->$cmp_error; return; |
|
|
613 | } |
|
|
614 | } |
|
|
615 | return @formula; |
| 602 | } |
616 | } |
| 603 | |
617 | |
| 604 | # |
618 | # |
| 605 | # Return the value if it is defined, otherwise use a default |
619 | # Return the value if it is defined, otherwise use a default |
| 606 | # |
620 | # |
| … | |
… | |
| 613 | |
627 | |
| 614 | ############################################################# |
628 | ############################################################# |
| 615 | |
629 | |
| 616 | package Value::Formula; |
630 | package Value::Formula; |
| 617 | |
631 | |
| 618 | ## FIXME: Need to check types for error reporting |
632 | sub cmp_defaults { |
|
|
633 | my $self = shift; |
|
|
634 | return ( |
|
|
635 | Value::Union::cmp_defaults($self,@_), |
|
|
636 | typeMatch => Value::Formula->new("(1,2]"), |
|
|
637 | ) if $self->type eq 'Union'; |
|
|
638 | |
|
|
639 | return Value::Real::cmp_defaults($self,@_) unless $self->type eq 'List'; |
|
|
640 | |
|
|
641 | return ( |
|
|
642 | Value::List::cmp_defaults($self,@_), |
|
|
643 | typeMatch => Value::Formula->new(($self->createRandomPoints(1))[1]->[0]{data}[0]), |
|
|
644 | ); |
|
|
645 | } |
|
|
646 | |
|
|
647 | # |
|
|
648 | # Get the types from the values of the formulas |
|
|
649 | # and compare those. |
|
|
650 | # |
| 619 | sub typeMatch {1} |
651 | sub typeMatch { |
|
|
652 | my $self = shift; my $other = shift; my $ans = shift; |
|
|
653 | return 1 if $self->type eq $other->type; |
|
|
654 | my $typeMatch = ($self->createRandomPoints(1))[1]->[0]; |
|
|
655 | $other = eval {($other->createRandomPoints(1))[1]->[0]} if ($other->class eq 'Formula'); |
|
|
656 | return 1 unless defined($other); # can't really tell, so don't report type mismatch |
|
|
657 | $typeMatch->typeMatch($other,$ans); |
|
|
658 | } |
| 620 | |
659 | |
| 621 | ## FIXME: Do formula returning list as list of formulas |
|
|
| 622 | ## and formula returning union as union of formulas |
|
|
| 623 | sub cmp_equal { |
660 | sub cmp_equal { |
| 624 | my $self = shift; |
661 | my $self = shift; my $ans = shift; |
|
|
662 | # |
|
|
663 | # Get the problem's seed |
|
|
664 | # |
| 625 | $self->{context}->flags->set( |
665 | $self->{context}->flags->set( |
| 626 | random_seed => $self->getPG('$PG_original_problemSeed') |
666 | random_seed => $self->getPG('$PG_original_problemSeed') |
| 627 | ); |
667 | ); |
|
|
668 | |
|
|
669 | # |
|
|
670 | # Use the list checker if the formula is a list or union |
|
|
671 | # Otherwise use the normal checker |
|
|
672 | # |
|
|
673 | if ($self->type =~ m/^(List|Union)$/) { |
|
|
674 | Value::List::cmp_equal($self,$ans); |
|
|
675 | } else { |
| 628 | $self->SUPER::cmp_equal(@_); |
676 | $self->SUPER::cmp_equal($ans); |
|
|
677 | } |
| 629 | } |
678 | } |
| 630 | |
679 | |
| 631 | # |
680 | # |
| 632 | # Replace the ones in Value::Formula |
681 | # Replace the ones in Value::Formula |
| 633 | # |
682 | # |