[system] / trunk / pg / macros / parserFormulaUpToConstant.pl Repository:
ViewVC logotype

View of /trunk/pg/macros/parserFormulaUpToConstant.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6204 - (download) (as text) (annotate)
Mon Feb 8 14:35:15 2010 UTC (9 years, 11 months ago) by dpvc
File size: 13536 byte(s)
Make sure variable substitution has occured BEFORE switching the contexts (so we don't get an undefined variable message); contexts must agree in order to compare the two.

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright  2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: pg/macros/parserFormulaUpToConstant.pl,v 1.23 2010/02/08 13:56:09 dpvc Exp $
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 =head1 NAME
   18 
   19 parserFormulaUpToConstant.pl - implements formulas "plus a constant".
   20 
   21 =head1 DESCRIPTION
   22 
   23 This file implements the FormulaUpToConstant object, which is
   24 a formula that is only unique up to a constant (i.e., this is
   25 an anti-derivative). Students must include the "+C" as part of
   26 their answers, but they can use any (single-letter) constant that
   27 they want, and it doesn't have to be the one the professor used.
   28 
   29 To use FormulaWithConstat objects, load this macro file at the
   30 top of your problem:
   31 
   32   loadMacros("parserFormulaUpToConstant.pl");
   33 
   34 then create a formula with constant as follows:
   35 
   36   $f = FormulaUpToConstant("sin(x)+C");
   37 
   38 Note that the C should NOT already be a variable in the Context;
   39 the FormulaUpToConstant object will handle adding it in for
   40 you.  If you don't include a constant in your formula (i.e., if
   41 all the variables that you used are already in your Context,
   42 then the FormulaUpToConstant object will add "+C" for you.
   43 
   44 The FormulaUpToConstant should work like any normal Formula,
   45 and in particular, you use $f->cmp to get its answer checker.
   46 
   47   ANS($f->cmp);
   48 
   49 Note that the FormulaUpToConstant object creates its own private
   50 copy of the current Context (so that it can add variables without
   51 affecting the rest of the problem).  You should not notice this
   52 in general, but if you need to access that context, use $f->{context}.
   53 E.g.
   54 
   55   Context($f->{context});
   56 
   57 would make the current context the one being used by the
   58 FormulaUpToConstant, while
   59 
   60   $f->{context}->variables->names
   61 
   62 would return a list of the variables in the private context.
   63 
   64 To get the name of the constant in use in the formula,
   65 use
   66 
   67   $f->constant.
   68 
   69 If you combine a FormulaUpToConstant with other formulas,
   70 the result will be a new FormulaUpToConstant object, with
   71 a new Context, and potentially a new + C added to it.  This
   72 is likely not what you want.  Instead, you should convert
   73 back to a Formula first, then combine with other objects,
   74 then convert back to a FormulaUpToConstant, if necessary.
   75 To do this, use the removeConstant() method:
   76 
   77   $f = FormulaUpToConstant("sin(x)+C");
   78   $g = Formula("cos(x)");
   79   $h = $f->removeConstant + $g;  # $h will be "sin(x)+cos(x)"
   80   $h = FormulaUpToConstant($h);  # $h will be "sin(x)+cos(x)+C"
   81 
   82 The answer evaluator by default will give "helpful" messages
   83 to the student when the "+ C" is left out.  You can turn off
   84 these messages using the showHints option to the cmp() method:
   85 
   86   ANS($f->cmp(showHints => 0));
   87 
   88 One of the hints is about whether the student's answer is linear
   89 in the arbitrary constant.  This test requires differentiating
   90 the student answer.  Since there are times when that could be
   91 problematic, you can disable that test via the showLinearityHints
   92 flag.  (Note: setting showHints to 0 also disables these hints.)
   93 
   94   ANS($f->cmp(showLinearityHints => 0));
   95 
   96 =cut
   97 
   98 loadMacros("MathObjects.pl");
   99 
  100 sub _parserFormulaUpToConstant_init {FormulaUpToConstant::Init()}
  101 
  102 package FormulaUpToConstant;
  103 @ISA = ('Value::Formula');
  104 
  105 sub Init {
  106   main::PG_restricted_eval('sub FormulaUpToConstant {FormulaUpToConstant->new(@_)}');
  107 }
  108 
  109 #
  110 #  Create an instance of a FormulaUpToConstant.  If no constant
  111 #  is supplied, we add C ourselves.
  112 #
  113 sub new {
  114   my $self = shift; my $class = ref($self) || $self;
  115   #
  116   #  Copy the context (so we can modify it) and
  117   #  replace the usual Variable object with our own.
  118   #
  119   my $context = (Value::isContext($_[0]) ? shift : $self->context)->copy;
  120   $context->{parser}{Variable} = 'FormulaUpToConstant::Variable';
  121   #
  122   #  Create a formula from the user's input.
  123   #
  124   my $f = main::Formula($context,@_);
  125   #
  126   #  If it doesn't have a constant already, add one.
  127   #  (should check that C isn't already in use, and look
  128   #   up the first free name, but we'll cross our fingers
  129   #   for now.  Could look through the defined variables
  130   #   to see if there is already an arbitraryConstant
  131   #   and use that.)
  132   #
  133   unless ($f->{constant}) {$f = $f + "C", $f->{constant} = "C"}
  134   #
  135   #  Check that the formula is linear in C.
  136   #
  137   my $n = $f->D($f->{constant});
  138   Value->Error("Your formula isn't linear in the arbitrary constant '%s'",$f->{constant})
  139     unless $n->isConstant;
  140   #
  141   #  Make a version with adaptive parameters for use in the
  142   #  comparison later on.  We could like n00*C, but already have $n
  143   #  copies of C, so remove them.  That way, n00 will be 0 when there
  144   #  are no C's in the student answer during the adaptive comparison.
  145   #  (Again, should really check that n00 is not in use already)
  146   #
  147   my $n00 = $context->variables->get("n00");
  148   $context->variables->add(n00=>'Parameter') unless $n00 and $n00->{parameter};
  149   my $n01 = $context->variables->get("n01");
  150   $context->variables->add(n01=>'Parameter') unless $n01 and $n01->{parameter};
  151   $f->{adapt} = $f + "(n00-$n)$f->{constant} + n01";
  152 
  153   return bless $f, $class;
  154 }
  155 
  156 ##################################################
  157 #
  158 #  Remember that compare implements the overloaded perl <=> operator,
  159 #  and $a <=> $b is -1 when $a < $b, 0 when $a == $b and 1 when $a > $b.
  160 #  In our case, we only care about equality, so we will return 0 when
  161 #  equal and other numbers to indicate the reason they are not equal
  162 #  (this can be used by the answer checker to print helpful messages)
  163 #
  164 sub compare {
  165   my ($l,$r) = @_; my $self = $l; my $context = $self->context;
  166   $r = Value::makeValue($r,context=>$context) unless Value::isValue($r);
  167   #
  168   #  Not equal if the student value is constant or has no + C
  169   #
  170   return 2 if !Value::isFormula($r);
  171   return 3 if !defined($r->{constant});
  172   #
  173   #  If constants aren't the same, substitute the professor's in the student answer.
  174   #
  175   $r = $r->substitute($r->{constant}=>$l->{constant}) unless $r->{constant} eq $l->{constant};
  176   $r->context($context) unless $r->context == $context;
  177 
  178   #
  179   #  Compare with adaptive parameters to see if $l + n00 C = $r for some n0.
  180   #
  181   my $adapt = $l->adapt;
  182   my $equal = Parser::Eval(sub {$adapt == $r});
  183   $self->{adapt} = $self->{adapt}->inherit($adapt);            # save the adapted value's flags
  184   $self->{adapt}{test_values} = $adapt->{test_values};         #  (these two are removed by inherit)
  185   $self->{adapt}{test_adapt} = $adapt->{test_adapt};
  186   $_[1]->{test_values} = $r->{test_values};            # save these in student answer for diagnostics
  187   return -1 unless $equal;
  188   #
  189   #  Check that n00 is non-zero (i.e., there is a multiple of C in the student answer)
  190   #  (remember: return value of 0 is equal, and non-zero is unequal)
  191   #
  192   return abs($context->variables->get("n00")->{value}) < $context->flag("zeroLevelTol");
  193 }
  194 
  195 #
  196 #  Return the {adapt} formula with test points adjusted
  197 #
  198 sub adapt {
  199   my $self = shift;
  200   return $self->adjustInherit($self->{adapt});
  201 }
  202 
  203 #
  204 #  Inherit from the main FormulaUpToConstant, but
  205 #  adjust the test points to include the constants
  206 #
  207 sub adjustInherit {
  208   my $self = shift;
  209   my $f = shift->inherit($self);
  210   delete $f->{adapt}; delete $f->{constant};
  211   foreach my $id ('test_points','test_at') {
  212     if (defined $f->{$id}) {
  213       $f->{$id} = [$f->{$id}->value] if Value::isValue($f->{$id});
  214       $f->{$id} = [$f->{$id}] unless ref($f->{$id}) eq 'ARRAY';
  215       $f->{$id} = [map {
  216   (Value::isValue($_) ? [$_->value] :
  217         (ref($_) eq 'ARRAY'? $_ : [$_]))
  218       } @{$f->{$id}}];
  219       $f->{$id} = $self->addConstants($f->{$id});
  220     }
  221   }
  222   return $f;
  223 }
  224 
  225 #
  226 #  Insert dummy values for the constants for the test points
  227 #  (These are supposed to be +C, so the value shouldn't matter?)
  228 #
  229 sub addConstants {
  230   my $self = shift; my $points = shift;
  231   my @names = $self->context->variables->variables;
  232   my $variables = $self->context->{variables};
  233   my $Points = [];
  234   foreach my $p (@{$points}) {
  235     if (scalar(@{$p}) == scalar(@names)) {
  236       push (@{$Points},$p);
  237     } else {
  238       my @P = (.1) x scalar(@names); my $j = 0;
  239       foreach my $i (0..scalar(@names)-1) {
  240         if (!$variables->{$names[$i]}{arbitraryConstant}) {
  241     $P[$i] = $p->[$j] if defined $p->[$j]; $j++;
  242   }
  243       }
  244       push (@{$Points}, \@P);
  245     }
  246   }
  247   return $Points;
  248 }
  249 
  250 ##################################################
  251 #
  252 #  Here we override part of the answer comparison
  253 #  routines in order to be able to generate
  254 #  helpful error messages for students when
  255 #  they leave off the + C.
  256 #
  257 
  258 #
  259 #  Show hints by default
  260 #
  261 sub cmp_defaults {((shift)->SUPER::cmp_defaults,showHints => 1, showLinearityHints => 1)};
  262 
  263 #
  264 #  Provide diagnostics based on the adapted function used to check
  265 #  the student's answer
  266 #
  267 sub cmp_diagnostics {
  268   my $self = shift;
  269   my $adapt = $self->inherit($self->{adapt});
  270   $adapt->{test_values} = $self->{adapt}{test_values};  # these aren't copied by inherit
  271   $adapt->{test_adapt}  = $self->{adapt}{test_adapt};
  272   $adapt->SUPER::cmp_diagnostics(@_);
  273 }
  274 
  275 #
  276 #  Make it possible to graph single-variable formulas by setting
  277 #  the arbitrary constants to 0 first.
  278 #
  279 sub cmp_graph {
  280   my $self = shift; my $diagnostics = shift;
  281   my $F1 = shift; my $F2; ($F1,$F2) = @{$F1} if (ref($F1) eq 'ARRAY');
  282   my %subs; my $context = $self->context;
  283   foreach my $v ($context->variables->variables)
  284     {$subs{$v} = 0 if ($context->variables->get($v)->{arbitraryConstant})}
  285   $F1 = $F1->inherit($F1->{adapt})->substitute(%subs)->reduce;
  286   $F2 = $F2->inherit($F2->{adapt})->substitute(%subs)->reduce;
  287   $self->SUPER::cmp_graph($diagnostics,[$F1,$F2]);
  288 }
  289 
  290 #
  291 #  Add useful messages, if the author requested them
  292 #
  293 sub cmp_postprocess {
  294   my $self = shift; my $ans = shift;
  295   $self->SUPER::cmp_postprocess($ans,@_);
  296   return unless $ans->{score} == 0 && !$ans->{isPreview};
  297   return if $ans->{ans_message} || !$self->getFlag("showHints");
  298   my $student = $ans->{student_value};
  299   my $result = Parser::Eval(sub {return $ans->{correct_value} <=> $student}); # compare encodes the reason in the result
  300   $self->cmp_Error($ans,"Note: there is always more than one posibility") if $result == 2 || $result == 3;
  301   if ($result == 3) {
  302     my $context = $self->context;
  303     $context->flags->set(no_parameters=>0);
  304     $context->variables->add(x00=>'Real');
  305     my $correct = $self->removeConstant+"n01+n00x00";    # must use both parameters
  306     $result = 1 if $correct->cmp_compare($student+"x00",{});
  307     $context->variables->remove('x00');
  308     $context->flags->set(no_parameters=>1);
  309   }
  310   $self->cmp_Error($ans,"Your answer is not the most general solution") if $result == 1;
  311   $self->cmp_Error($ans,"Your formula should be linear in the constant '$student->{constant}'")
  312     if $result == -1 && $self->getFlag("showLinearityHints") && !$student->D($student->{constant})->isConstant;
  313 }
  314 
  315 ##################################################
  316 #
  317 #  Get the name of the constant
  318 #
  319 sub constant {(shift)->{constant}}
  320 
  321 #
  322 #  Remove the constant and return a Formula object
  323 #
  324 sub removeConstant {
  325   my $self = shift;
  326   return $self->adjustInherit(main::Formula($self->substitute($self->{constant}=>0))->reduce);
  327 }
  328 
  329 #
  330 #  Override the differentiation so that we always return
  331 #  a Formula, not a FormulaUpToConstant (we don't want to
  332 #  add the C in again).
  333 #
  334 sub D {
  335   my $self = shift;
  336   $self->removeConstant->D(@_);
  337 }
  338 
  339 ######################################################################
  340 #
  341 #  This class repalces the Parser::Variable class, and its job
  342 #  is to look for new constants that aren't in the context,
  343 #  and add them in.  This allows students to use ANY constant
  344 #  they want, and a different one from the professor.  We check
  345 #  that the student only used ONE arbitrary constant, however.
  346 #
  347 package FormulaUpToConstant::Variable;
  348 our @ISA = ('Parser::Variable');
  349 
  350 sub new {
  351   my $self = shift; my $class = ref($self) || $self;
  352   my $equation = shift; my $variables = $equation->{context}{variables};
  353   my ($name,$ref) = @_; my $def = $variables->{$name};
  354   #
  355   #  If the variable is not already in the context, add it
  356   #    and mark it as an arbitrary constant (for later reference)
  357   #
  358   if (!defined($def) && length($name) eq 1) {
  359     $equation->{context}->variables->add($name => 'Real');
  360     $equation->{context}->variables->set($name => {arbitraryConstant => 1});
  361     $def = $variables->{$name};
  362   }
  363   #
  364   #  If the variable is an arbitrary constant
  365   #    Error if we already have a constant and it's not this one.
  366   #    Save the constant so we can check with it later.
  367   #
  368   if ($def && $def->{arbitraryConstant}) {
  369     $equation->Error(["Your formula shouldn't have two arbitrary constants"],$ref)
  370       if $equation->{constant} and $name ne $equation->{constant};
  371     $equation->{constant} = $name;
  372   }
  373   #
  374   #  Do the usual Variable stuff.
  375   #
  376   $self->SUPER::new($equation,$name,$ref);
  377 }
  378 
  379 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9