[system] / trunk / pg / lib / Value / AnswerChecker.pm Repository:
ViewVC logotype

View of /trunk/pg/lib/Value/AnswerChecker.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2688 - (download) (as text) (annotate)
Fri Aug 27 19:42:59 2004 UTC (15 years, 3 months ago) by dpvc
File size: 21882 byte(s)
Added in functMaxConstantOfIntegration support in the adaptive
parameter answer checking.  Also added a flag to the formula answer
checker that allows checking a formula up to a constant (for
integration problems).  This really just adds a new parameter to the
context and adds that to the formula, so there is nothing deep about
this.

	ANS(Formula("2x")->cmp(upToConstant=>1));

Finally, don't look for adaptive parameters if they aren't actually
used in the professor's formula (even if they are defined).

    1 #############################################################
    2 #
    3 #  Implements the ->cmp method for Value objects.  This produces
    4 #  an answer checker appropriate for the type of object.
    5 #  Additional options can be passed to the checker to
    6 #  modify its action.
    7 #
    8 #  The individual Value packages are modified below to add the
    9 #  needed methods.
   10 #
   11 
   12 #############################################################
   13 
   14 package Value;
   15 
   16 #
   17 #  Create an answer checker for the given type of object
   18 #
   19 
   20 sub cmp_defaults {(
   21   showTypeWarnings => 1,
   22   showEqualErrors  => 1,
   23   ignoreStrings    => 1,
   24 )}
   25 
   26 sub cmp {
   27   my $self = shift;
   28   my $ans = new AnswerEvaluator;
   29   $ans->ans_hash(
   30     type => "Value (".$self->class.")",
   31     correct_ans => protectHTML($self->string),
   32     correct_value => $self,
   33     $self->cmp_defaults,
   34     @_
   35   );
   36   $ans->install_evaluator(sub {$ans = shift; $ans->{correct_value}->cmp_parse($ans)});
   37   $self->{context} = $$Value::context unless defined($self->{context});
   38   return $ans;
   39 }
   40 
   41 #
   42 #  Parse the student answer and compute its value,
   43 #    produce the preview strings, and then compare the
   44 #    student and professor's answers for equality.
   45 #
   46 sub cmp_parse {
   47   my $self = shift; my $ans = shift;
   48   #
   49   #  Do some setup
   50   #
   51   my $current = $$Value::context; # save it for later
   52   my $context = $ans->{correct_value}{context};
   53   Parser::Context->current(undef,$context); # change to correct answser's context
   54   $context->flags->set(StringifyAsTeX => 0);  # reset this, just in case.
   55   $context->flags->set(no_parameters => 1);   # don't let students enter parameters
   56   $ans->{isPreview} = $self->getPG('$inputs_ref->{previewAnswers}');
   57   $ans->{cmp_class} = $self->cmp_class($ans) unless $ans->{cmp_class};
   58 
   59   #
   60   #  Parse and evaluate the student answer
   61   #
   62   $ans->score(0);  # assume failure
   63   $ans->{student_value} = $ans->{student_formula} = Parser::Formula($ans->{student_ans});
   64   $ans->{student_value} = Parser::Evaluate($ans->{student_formula})
   65     if defined($ans->{student_formula}) && $ans->{student_formula}->isConstant;
   66 
   67   #
   68   #  If it parsed OK, save the output forms and check if it is correct
   69   #   otherwise report an error
   70   #
   71   if (defined $ans->{student_value}) {
   72     $ans->{student_value} = Value::Formula->new($ans->{student_value})
   73        unless Value::isValue($ans->{student_value});
   74     $ans->{preview_latex_string} = $ans->{student_formula}->TeX;
   75     $ans->{preview_text_string}  = protectHTML($ans->{student_formula}->string);
   76     $ans->{student_ans}          = $ans->{preview_text_string};
   77     $self->cmp_equal($ans);
   78     $self->cmp_postprocess($ans) if !$ans->{error_message};
   79   } else {
   80     $self->cmp_error($ans);
   81   }
   82   $context->flags->set(no_parameters => 0);  # let professors enter parameters
   83   Parser::Context->current(undef,$current);  # put back the old context
   84   return $ans;
   85 }
   86 
   87 #
   88 #  Check if the parsed student answer equals the professor's answer
   89 #
   90 sub cmp_equal {
   91   my $self = shift; my $ans = shift;
   92   my $correct = $ans->{correct_value};
   93   my $student = $ans->{student_value};
   94   if ($correct->typeMatch($student,$ans)) {
   95     my $equal = eval {$correct == $student};
   96     if (defined($equal) || !$ans->{showEqualErrors}) {$ans->score(1) if $equal; return}
   97     $self->cmp_error($ans);
   98   } else {
   99     return if $ans->{ignoreStrings} && (!Value::isValue($student) || $student->type eq 'String');
  100     $ans->{ans_message} = $ans->{error_message} =
  101       "Your answer isn't ".lc($ans->{cmp_class}).
  102         " (it looks like ".lc($student->showClass).")"
  103      if !$ans->{isPreview} && $ans->{showTypeWarnings} && !$ans->{error_message};
  104   }
  105 }
  106 
  107 #
  108 #  Check if types are compatible for equality check
  109 #
  110 sub typeMatch {
  111   my $self = shift;  my $other = shift;
  112   return 1 unless ref($other);
  113   $self->type eq $other->type && $other->class ne 'Formula';
  114 }
  115 
  116 #
  117 #  Class name for cmp error messages
  118 #
  119 sub cmp_class {
  120   my $self = shift; my $ans = shift;
  121   my $class = $self->showClass; $class =~ s/Real //;
  122   return $class if $class =~ m/Formula/;
  123   return "an Interval or Union" if $class =~ m/Interval/i;
  124   return $class;
  125 }
  126 
  127 #
  128 #  Student answer evaluation failed.
  129 #  Report the error, with formatting, if possible.
  130 #
  131 sub cmp_error {
  132   my $self = shift; my $ans = shift;
  133   my $context = $$Value::context;
  134   my $message = $context->{error}{message};
  135   if ($context->{error}{pos}) {
  136     my $string = $context->{error}{string};
  137     my ($s,$e) = @{$context->{error}{pos}};
  138     $message =~ s/; see.*//;  # remove the position from the message
  139     $ans->{student_ans} =
  140        protectHTML(substr($string,0,$s)) .
  141        '<SPAN CLASS="parsehilight">' .
  142          protectHTML(substr($string,$s,$e-$s)) .
  143        '</SPAN>' .
  144        protectHTML(substr($string,$e));
  145   }
  146   $self->cmp_Error($ans,$message);
  147 }
  148 
  149 #
  150 #  Set the error message
  151 #
  152 sub cmp_Error {
  153   my $self = shift; my $ans = shift;
  154   return unless scalar(@_) > 0;
  155   $ans->score(0);
  156   $ans->{ans_message} = $ans->{error_message} = join("\n",@_);
  157 }
  158 
  159 #
  160 #  filled in by sub-classes
  161 #
  162 sub cmp_postprocess {}
  163 
  164 #
  165 #  Quote HTML characters
  166 #
  167 sub protectHTML {
  168     my $string = shift;
  169     return $string if eval ('$main::displayMode') eq 'TeX';
  170     $string =~ s/&/\&amp;/g;
  171     $string =~ s/</\&lt;/g;
  172     $string =~ s/>/\&gt;/g;
  173     $string;
  174 }
  175 
  176 #
  177 #  names for numbers
  178 #
  179 sub NameForNumber {
  180   my $self = shift; my $n = shift;
  181   my $name =  ('zeroth','first','second','third','fourth','fifth',
  182                'sixth','seventh','eighth','ninth','tenth')[$n];
  183   $name = "$n-th" if ($n > 10);
  184   return $name;
  185 }
  186 
  187 #
  188 #  Get a value from the safe compartment
  189 #
  190 sub getPG {
  191   my $self = shift;
  192 #  (WeBWorK::PG::Translator::PG_restricted_eval(shift))[0];
  193   eval ('package main; '.shift);  # faster
  194 }
  195 
  196 #############################################################
  197 #############################################################
  198 
  199 package Value::Real;
  200 
  201 sub cmp_defaults {(
  202   shift->SUPER::cmp_defaults,
  203   ignoreInfinity => 1,
  204 )}
  205 
  206 sub typeMatch {
  207   my $self = shift; my $other = shift; my $ans = shift;
  208   return 1 unless ref($other);
  209   return 0 if Value::isFormula($other);
  210   return 1 if $other->type eq 'Infinity' && $ans->{ignoreInfinity};
  211   $self->type eq $other->type;
  212 }
  213 
  214 #############################################################
  215 
  216 package Value::Infinity;
  217 
  218 sub cmp_class {'a Number'};
  219 
  220 sub typeMatch {
  221   my $self = shift; my $other = shift; my $ans = shift;
  222   return 1 unless ref($other);
  223   return 0 if Value::isFormula($other);
  224   return 1 if $other->type eq 'Number';
  225   $self->type eq $other->type;
  226 }
  227 
  228 #############################################################
  229 
  230 package Value::String;
  231 
  232 sub cmp_defaults {(
  233   Value::Real->cmp_defaults,
  234   typeMatch => 'Value::Real',
  235 )}
  236 
  237 sub cmp_class {
  238   my $self = shift; my $ans = shift; my $typeMatch = $ans->{typeMatch};
  239   return 'a Word' if !Value::isValue($typeMatch) || $typeMatch->class eq 'String';
  240   return $typeMatch->cmp_class;
  241 };
  242 
  243 sub typeMatch {
  244   my $self = shift; my $other = shift; my $ans = shift;
  245   return 0 if ref($other) && Value::isFormula($other);
  246   my $typeMatch = $ans->{typeMatch};
  247   return 1 if !Value::isValue($typeMatch) || $typeMatch->class eq 'String' ||
  248                  $self->type eq $other->type;
  249   return $typeMatch->typeMatch($other,$ans);
  250 }
  251 
  252 #############################################################
  253 
  254 package Value::Point;
  255 
  256 sub cmp_defaults {(
  257   shift->SUPER::cmp_defaults,
  258   showDimensionHints => 1,
  259   showCoordinateHints => 1,
  260 )}
  261 
  262 sub typeMatch {
  263   my $self = shift; my $other = shift; my $ans = shift;
  264   return ref($other) && $other->type eq 'Point' && $other->class ne 'Formula';
  265 }
  266 
  267 #
  268 #  Check for dimension mismatch and incorrect coordinates
  269 #
  270 sub cmp_postprocess {
  271   my $self = shift; my $ans = shift;
  272   return unless $ans->{score} == 0 && !$ans->{isPreview};
  273   if ($ans->{showDimensionHints} &&
  274       $self->length != $ans->{student_value}->length) {
  275     $self->cmp_Error($ans,"The dimension of your result is incorrect"); return;
  276   }
  277   if ($ans->{showCoordinateHints}) {
  278     my @errors;
  279     foreach my $i (1..$self->length) {
  280       push(@errors,"The ".$self->NameForNumber($i)." coordinate is incorrect")
  281   if ($self->{data}[$i-1] != $ans->{student_value}{data}[$i-1]);
  282     }
  283     $self->cmp_Error($ans,@errors); return;
  284   }
  285 }
  286 
  287 #############################################################
  288 
  289 package Value::Vector;
  290 
  291 sub cmp_defaults {(
  292   shift->SUPER::cmp_defaults,
  293   showDimensionHints => 1,
  294   showCoordinateHints => 1,
  295   promotePoints => 0,
  296   parallel => 0,
  297   sameDirection => 0,
  298 )}
  299 
  300 sub typeMatch {
  301   my $self = shift; my $other = shift; my $ans = shift;
  302   return 0 unless ref($other) && $other->class ne 'Formula';
  303   return $other->type eq 'Vector' ||
  304      ($ans->{promotePoints} && $other->type eq 'Point');
  305 }
  306 
  307 #
  308 #  check for dimension mismatch
  309 #        for parallel vectors, and
  310 #        for incorrect coordinates
  311 #
  312 sub cmp_postprocess {
  313   my $self = shift; my $ans = shift;
  314   return unless $ans->{score} == 0;
  315   if (!$ans->{isPreview} && $ans->{showDimensionHints} &&
  316       $self->length != $ans->{student_value}->length) {
  317     $self->cmp_Error($ans,"The dimension of your result is incorrect"); return;
  318   }
  319  if ($ans->{parallel} &&
  320      $self->isParallel($ans->{student_value},$ans->{sameDirection})) {
  321    $ans->score(1); return;
  322  }
  323   if (!$ans->{isPreview} && $ans->{showCoordinateHints}) {
  324     my @errors;
  325     foreach my $i (1..$self->length) {
  326       push(@errors,"The ".$self->NameForNumber($i)." coordinate is incorrect")
  327   if ($self->{data}[$i-1] != $ans->{student_value}{data}[$i-1]);
  328     }
  329     $self->cmp_Error($ans,@errors); return;
  330   }
  331 }
  332 
  333 
  334 
  335 #############################################################
  336 
  337 package Value::Matrix;
  338 
  339 sub cmp_defaults {(
  340   shiftf->SUPER::cmp_defaults,
  341   showDimensionHints => 1,
  342   showEqualErrors => 0,
  343 )}
  344 
  345 sub typeMatch {
  346   my $self = shift; my $other = shift; my $ans = shift;
  347   return 0 unless ref($other) && $other->class ne 'Formula';
  348   return $other->type eq 'Matrix' ||
  349     ($other->type =~ m/^(Point|list)$/ &&
  350      $other->{open}.$other->{close} eq $self->{open}.$self->{close});
  351 }
  352 
  353 sub cmp_postprocess {
  354   my $self = shift; my $ans = shift;
  355   return unless $ans->{score} == 0 &&
  356     !$ans->{isPreview} && $ans->{showDimensionHints};
  357   my @d1 = $self->dimensions; my @d2 = $ans->{student_value}->dimensions;
  358   if (scalar(@d1) != scalar(@d2)) {
  359     $self->cmp_Error($ans,"Matrix dimension is not correct");
  360     return;
  361   } else {
  362     foreach my $i (0..scalar(@d1)-1) {
  363       if ($d1[$i] != $d2[$i]) {
  364   $self->cmp_Error($ans,"Matrix dimension is not correct");
  365   return;
  366       }
  367     }
  368   }
  369 }
  370 
  371 #############################################################
  372 
  373 package Value::Interval;
  374 
  375 sub cmp_defaults {(
  376   shift->SUPER::cmp_defaults,
  377   showEndpointHints => 1,
  378   showEndTypeHints => 1,
  379 )}
  380 
  381 sub typeMatch {
  382   my $self = shift; my $other = shift;
  383   return 0 unless ref($other) && $other->class ne 'Formula';
  384   return $other->length == 2 &&
  385          ($other->{open} eq '(' || $other->{open} eq '[') &&
  386          ($other->{close} eq ')' || $other->{close} eq ']')
  387      if $other->type =~ m/^(Point|List)$/;
  388   $other->type =~ m/^(Interval|Union)$/;
  389 }
  390 
  391 #
  392 #  Check for wrong enpoints and wrong type of endpoints
  393 #
  394 sub cmp_postprocess {
  395   my $self = shift; my $ans = shift;
  396   return unless $ans->{score} == 0 && !$ans->{isPreview};
  397   my $other = $ans->{student_value};
  398   return unless $other->class eq 'Interval';
  399   my @errors;
  400   if ($ans->{showEndpointHints}) {
  401     push(@errors,"Your left endpoint is incorrect")
  402       if ($self->{data}[0] != $other->{data}[0]);
  403     push(@errors,"Your right endpoint is incorrect")
  404       if ($self->{data}[1] != $other->{data}[1]);
  405   }
  406   if (scalar(@errors) == 0 && $ans->{showEndTypeHints}) {
  407     push(@errors,"The type of interval is incorrect")
  408       if ($self->{open}.$self->{close} ne $other->{open}.$other->{close});
  409   }
  410   $self->cmp_Error($ans,@errors);
  411 }
  412 
  413 #############################################################
  414 
  415 package Value::Union;
  416 
  417 sub typeMatch {
  418   my $self = shift; my $other = shift;
  419   return 0 unless ref($other) && $other->class ne 'Formula';
  420   return $other->length == 2 &&
  421          ($other->{open} eq '(' || $other->{open} eq '[') &&
  422          ($other->{close} eq ')' || $other->{close} eq ']')
  423      if $other->type =~ m/^(Point|List)$/;
  424   $other->type =~ m/^(Interval|Union)/;
  425 }
  426 
  427 #
  428 #  Use the List checker for unions, in order to get
  429 #  partial credit.  Set the various types for error
  430 #  messages.
  431 #
  432 sub cmp_defaults {(
  433   Value::List::cmp_defaults(@_),
  434   typeMatch => 'Value::Interval',
  435   list_type => 'an interval or union',
  436   entry_type => 'an interval',
  437 )}
  438 
  439 sub cmp_equal {Value::List::cmp_equal(@_)}
  440 
  441 #############################################################
  442 
  443 package Value::List;
  444 
  445 sub cmp_defaults {
  446   my $self = shift;
  447   return (
  448     Value::Real->cmp_defaults,
  449     showHints => undef,
  450     showLengthHints => undef,
  451     showParenHints => undef,
  452 #    partialCredit => undef,
  453     partialCredit => 0,  #  only allow this once WW can deal with partial credit
  454     ordered => 0,
  455     entry_type => undef,
  456     list_type => undef,
  457     typeMatch => Value::makeValue($self->{data}[0]),
  458     requireParenMatch => 1,
  459     removeParens => 1,
  460    );
  461 }
  462 
  463 #
  464 #  Match anything but formulas
  465 #
  466 sub typeMatch {return !ref($other) || $other->class ne 'Formula'}
  467 
  468 #
  469 #  Handle removal of outermost parens in correct answer.
  470 #
  471 sub cmp {
  472   my $self = shift;
  473   my $cmp = $self->SUPER::cmp(@_);
  474   if ($cmp->{rh_ans}{removeParens}) {
  475     $self->{open} = $self->{close} = '';
  476     $cmp->ans_hash(correct_ans => $self->stringify);
  477   }
  478   return $cmp;
  479 }
  480 
  481 sub cmp_equal {
  482   my $self = shift; my $ans = shift;
  483   $ans->{showPartialCorrectAnswers} = $self->getPG('$showPartialCorrectAnswers');
  484 
  485   #
  486   #  get the paramaters
  487   #
  488   my $showTypeWarnings = $ans->{showTypeWarnings};
  489   my $showHints        = getOption($ans,'showHints');
  490   my $showLengthHints  = getOption($ans,'showLengthHints');
  491   my $showParenHints   = getOption($ans,'showLengthHints');
  492   my $partialCredit    = getOption($ans,'partialCredit');
  493   my $ordered = $ans->{ordered};
  494   my $requireParenMatch = $ans->{requireParenMatch};
  495   my $typeMatch = $ans->{typeMatch};
  496   my $value     = $ans->{entry_type};
  497   my $ltype     = $ans->{list_type} || lc($self->type);
  498 
  499   $value = (Value::isValue($typeMatch)? lc($typeMatch->cmp_class): 'value')
  500     unless defined($value);
  501   $value =~ s/(real|complex) //; $ans->{cmp_class} = $value;
  502   $value =~ s/^an? //; $value = 'formula' if $value =~ m/formula/;
  503   $ltype =~ s/^an? //;
  504   $showTypeWarnings = $showHints = $showLengthHints = 0 if $ans->{isPreview};
  505 
  506   #
  507   #  Get the lists of correct and student answers
  508   #   (split formulas that return lists or unions)
  509   #
  510   my @correct = (); my ($cOpen,$cClose);
  511   if ($self->class ne 'Formula') {
  512     @correct = $self->value;
  513     $cOpen = $ans->{correct_value}{open}; $cClose = $ans->{correct_value}{close};
  514   } else {
  515     @correct = Value::List->splitFormula($self,$ans);
  516     $cOpen = $self->{tree}{open}; $cClose = $self->{tree}{close};
  517   }
  518   my $student = $ans->{student_value}; my @student = ($student);
  519   my ($sOpen,$sClose) = ('','');
  520   if (Value::isFormula($student) && $student->type eq $self->type) {
  521     @student = Value::List->splitFormula($student,$ans);
  522     $sOpen = $student->{tree}{open}; $sClose = $student->{tree}{close};
  523   } elsif ($student->class ne 'Formula' && $student->class eq $self->type) {
  524     @student = @{$student->{data}};
  525     $sOpen = $student->{open}; $sClose = $student->{close};
  526   }
  527   return if $ans->{split_error};
  528   #
  529   #  Check for parenthesis match
  530   #
  531   if ($requireParenMatch && ($sOpen ne $cOpen || $sClose ne $cClose)) {
  532     if ($showParenHints && !($ans->{ignoreStrings} && $student->type eq 'String')) {
  533       my $message = "The parentheses for your $ltype ";
  534       if (($cOpen || $cClose) && ($sOpen || $sClose))
  535                                 {$message .= "are of the wrong type"}
  536       elsif ($sOpen || $sClose) {$message .= "should be removed"}
  537       else                      {$message .= "are missing"}
  538       $self->cmp_Error($ans,$message) unless $ans->{isPreview};
  539     }
  540     return;
  541   }
  542   #
  543   #  Check for empty lists
  544   #
  545   if (scalar(@correct) == 0 && scalar(@student) == 0) {$ans->score(1); return}
  546 
  547   #
  548   #  Initialize the score
  549   #
  550   my $M = scalar(@correct);
  551   my $m = scalar(@student);
  552   my $maxscore = ($m > $M)? $m : $M;
  553   my $score = 0; my @errors; my $i = 0;
  554 
  555   #
  556   #  Loop through student answers looking for correct ones
  557   #
  558   ENTRY: foreach my $entry (@student) {
  559     $i++;
  560     $entry = Value::makeValue($entry);
  561     $entry = Value::Formula->new($entry) if !Value::isValue($entry);
  562     if ($ordered) {
  563       if (eval {shift(@correct) == $entry}) {$score++; next ENTRY}
  564     } else {
  565       foreach my $k (0..$#correct) {
  566   if (eval {$correct[$k] == $entry}) {
  567     splice(@correct,$k,1);
  568     $score++; next ENTRY;
  569   }
  570       }
  571     }
  572     #
  573     #  Give messages about incorrect answers
  574     #
  575     my $nth = ''; my $answer = 'answer';
  576     my $class = $ans->{list_type} || $self->cmp_class;
  577     if (scalar(@student) > 1) {
  578       $nth = ' '.$self->NameForNumber($i);
  579       $class = $ans->{cmp_class};
  580       $answer = 'value';
  581     }
  582     if ($showTypeWarnings && !$typeMatch->typeMatch($entry,$ans) &&
  583   !($ans->{ignoreStrings} && $entry->class eq 'String')) {
  584       push(@errors,"Your$nth $answer isn't ".lc($class).
  585      " (it looks like ".lc($entry->showClass).")");
  586     } elsif ($showHints && $m > 1) {
  587       push(@errors,"Your$nth $value is incorrect");
  588     }
  589   }
  590 
  591   #
  592   #  Give hints about extra or missing answsers
  593   #
  594   if ($showLengthHints) {
  595     $value =~ s/ or /s or /; # fix "interval or union"
  596     push(@errors,"There should be more ${value}s in your $ltype")
  597       if ($score == $m && scalar(@correct) > 0);
  598     push(@errors,"There should be fewer ${value}s in your $ltype")
  599       if ($score < $maxscore && $score == $M);
  600   }
  601 
  602   #
  603   #  Finalize the score
  604   #
  605   $score = 0 if ($score != $maxscore && !$partialCredit);
  606   $ans->score($score/$maxscore);
  607   push(@errors,"Score = $ans->{score}") if $ans->{debug};
  608   $ans->{error_message} = $ans->{ans_message} = join("\n",@errors);
  609 }
  610 
  611 #
  612 #  Split a formula that is a list or union into a
  613 #    list of formulas (or Value objects).
  614 #
  615 sub splitFormula {
  616   my $self = shift; my $formula = shift; my $ans = shift;
  617   my @formula; my @entries;
  618   if ($formula->type eq 'List') {@entries = @{$formula->{tree}{coords}}}
  619       else {@entries = $formula->{tree}->makeUnion}
  620   foreach my $entry (@entries) {
  621     my $v = Parser::Formula($entry);
  622        $v = Parser::Evaluate($v) if (defined($v) && $v->isConstant);
  623     push(@formula,$v);
  624     #
  625     #  There shouldn't be an error evaluating the formula,
  626     #    but you never know...
  627     #
  628     if (!defined($v)) {$ans->{split_error} = 1; $self->cmp_error; return}
  629   }
  630   return @formula;
  631 }
  632 
  633 #
  634 #  Return the value if it is defined, otherwise use a default
  635 #
  636 sub getOption {
  637   my $ans = shift; my $name = shift;
  638   my $value = $ans->{$name};
  639   return $value if defined($value);
  640   return $ans->{showPartialCorrectAnswers};
  641 }
  642 
  643 #############################################################
  644 
  645 package Value::Formula;
  646 
  647 sub cmp_defaults {
  648   my $self = shift;
  649 
  650   return (
  651     Value::Union::cmp_defaults($self,@_),
  652     typeMatch => Value::Formula->new("(1,2]"),
  653   ) if $self->type eq 'Union';
  654 
  655   my $type = $self->type;
  656   $type = ($self->isComplex)? 'Complex': 'Real' if $type eq 'Number';
  657   $type = 'Value::'.$type.'::';
  658 
  659   return (&{$type.'cmp_defaults'}($self,@_), upToConstant => 0)
  660     if defined(%$type) && $self->type ne 'List';
  661 
  662   return (
  663     Value::List::cmp_defaults($self,@_),
  664     removeParens => $self->{autoFormula},
  665     typeMatch => Value::Formula->new(($self->createRandomPoints(1))[1]->[0]{data}[0]),
  666   );
  667 }
  668 
  669 #
  670 #  Get the types from the values of the formulas
  671 #     and compare those.
  672 #
  673 sub typeMatch {
  674   my $self = shift; my $other = shift; my $ans = shift;
  675   return 1 if $self->type eq $other->type;
  676   my $typeMatch = ($self->createRandomPoints(1))[1]->[0];
  677   $other = eval {($other->createRandomPoints(1))[1]->[0]} if Value::isFormula($other);
  678   return 1 unless defined($other); # can't really tell, so don't report type mismatch
  679   $typeMatch->typeMatch($other,$ans);
  680 }
  681 
  682 #
  683 #  Handle removal of outermost parens in a list.
  684 #
  685 sub cmp {
  686   my $self = shift;
  687   my $cmp = $self->SUPER::cmp(@_);
  688   if ($cmp->{rh_ans}{removeParens} && $self->type eq 'List') {
  689     $self->{tree}{open} = $self->{tree}{close} = '';
  690     $cmp->ans_hash(correct_ans => $self->stringify);
  691   }
  692   if ($cmp->{rh_ans}{upToConstant}) {
  693     my $current = Parser::Context->current();
  694     my $context = $self->{context} = $self->{context}->copy;
  695     Parser::Context->current(undef,$context);
  696     $context->{_variables}->{pattern} = $context->{_variables}->{namePattern} =
  697       'C0|' . $context->{_variables}->{pattern};
  698     $context->update; $context->variables->add('C0' => 'Parameter');
  699     $cmp->ans_hash(correct_value => Value::Formula->new('C0')+$self);
  700     Parser::Context->current(undef,$current);
  701   }
  702   return $cmp;
  703 }
  704 
  705 sub cmp_equal {
  706   my $self = shift; my $ans = shift;
  707   #
  708   #  Get the problem's seed
  709   #
  710   $self->{context}->flags->set(
  711     random_seed => $self->getPG('$PG_original_problemSeed')
  712   );
  713 
  714   #
  715   #  Use the list checker if the formula is a list or union
  716   #    Otherwise use the normal checker
  717   #
  718   if ($self->type =~ m/^(List|Union)$/) {
  719     Value::List::cmp_equal($self,$ans);
  720   } else {
  721     $self->SUPER::cmp_equal($ans);
  722   }
  723 }
  724 
  725 sub cmp_postprocess {
  726   my $self = shift; my $ans = shift;
  727   return unless $ans->{score} == 0 && !$ans->{isPreview};
  728   return if $ans->{ans_message} || !$ans->{showDimensionHints};
  729   my $other = $ans->{student_value};
  730   return unless $other->type =~ m/^(Point|Vector|Matrix)$/;
  731   return unless $self->type  =~ m/^(Point|Vector|Matrix)$/;
  732   return if Parser::Item::typeMatch($self->typeRef,$other->typeRef);
  733   $self->cmp_Error($ans,"The dimension of your result is incorrect");
  734 }
  735 
  736 #############################################################
  737 
  738 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9