[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 2687 - (download) (as text) (annotate)
Fri Aug 27 00:28:27 2004 UTC (15 years, 3 months ago) by dpvc
File size: 21305 byte(s)
Added ability to have linear adaptive parameters in the function
answer checker.  It only works for real-valued functions, though.
To use a parameter, use

       Context()->variables->add(a=>'Parameter');

and then use 'a' as a variable within your answer.  The student will
not be allowed to enter the parameter, but the professor will.

Note that the correct answer will show the full professor's answer,
including the parameters, even though the student can't type it.  Is
this the right behaviour?

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9