[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 5461 - (download) (as text) (annotate)
Thu Sep 6 00:19:23 2007 UTC (12 years, 4 months ago) by dpvc
File size: 9787 byte(s)
Make error messages more consistent when student's answer is off by a constant.

    1 loadMacros("MathObjects.pl");
    2 
    3 sub _parserFormulaUpToConstant_init {FormulaUpToConstant::Init()}
    4 
    5 =head1 FormulaUpToConstant();
    6 
    7  ######################################################################
    8  #
    9  #  This file implements the FormulaUpToConstant object, which is
   10  #  a formula that is only unique up to a constant (i.e., this is
   11  #  an anti-derivative). Students must include the "+C" as part of
   12  #  their answers, but they can use any (single-letter) constant that
   13  #  they want, and it doesn't have to be the one the professor used.
   14  #
   15  #  To use FormulaWithConstat objects, load this macro file at the
   16  #  top of your problem:
   17  #
   18  #    loadMacros("parserFormulaUpToConstant.pl");
   19  #
   20  #  then create a formula with constant as follows:
   21  #
   22  #    $f = FormulaUpToConstant("sin(x)+C");
   23  #
   24  #  Note that the C should NOT already be a variable in the Context;
   25  #  the FormulaUpToConstant object will handle adding it in for
   26  #  you.  If you don't include a constant in your formula (i.e., if
   27  #  all the variables that you used are already in your Context,
   28  #  then the FormulaUpToConstant object will add "+C" for you.
   29  #
   30  #  The FormulaUpToConstant should work like any normal Formula,
   31  #  and in particular, you use $f->cmp to get its answer checker.
   32  #
   33  #    ANS($f->cmp);
   34  #
   35  #  Note that the FormulaUpToConstant object creates its only private
   36  #  copy of the current Context (so that it can add variables without
   37  #  affecting the rest of the problem).  You should not notice this
   38  #  in general, but if you need to access that context, use $f->{context}.
   39  #  E.g.
   40  #
   41  #    Context($f->{context});
   42  #
   43  #  would make the current context the one being used by the
   44  #  FormulaUpToConstant, while
   45  #
   46  #    $f->{context}->variables->names
   47  #
   48  #  would return a list of the variables in the private context.
   49  #
   50  #  To get the name of the constant in use in the formula,
   51  #  use
   52  #
   53  #    $f->constant.
   54  #
   55  #  If you combine a FormulaUpToConstant with other formulas,
   56  #  the result will be a new FormulaUpToConstant object, with
   57  #  a new Context, and potentially a new + C added to it.  This
   58  #  is likely not what you want.  Instead, you should convert
   59  #  back to a Formula first, then combine with other objects,
   60  #  then convert back to a FormulaUpToConstant, if necessary.
   61  #  To do this, use the removeConstant() method:
   62  #
   63  #    $f = FormulaUpToConstant("sin(x)+C");
   64  #    $g = Formula("cos(x)");
   65  #    $h = $f->removeConstant + $g;  # $h will be "sin(x)+cos(x)"
   66  #    $h = FormulaUpToConstant($h);  # $h will be "sin(x)+cos(x)+C"
   67  #
   68  #  The answer evaluator by default will give "helpful" messages
   69  #  to the student when the "+ C" is left out.  You can turn off
   70  #  these messages using the showHints option to the cmp() method:
   71  #
   72  #    ANS($f->cmp(showHints => 0));
   73  #
   74  #  One of the hints is about whether the student's answer is linear
   75  #  in the arbitrary constant.  This test requires differentiating
   76  #  the student answer.  Since there are times when that could be
   77  #  problematic, you can disable that test via the showLinearityHints
   78  #  flag.  (Note: setting showHints to 0 also disables these hints.)
   79  #
   80  #    ANS($f->cmp(showLinearityHints => 0));
   81  #
   82  ######################################################################
   83 
   84 =cut
   85 
   86 package FormulaUpToConstant;
   87 @ISA = ('Value::Formula');
   88 
   89 sub Init {
   90   main::PG_restricted_eval('sub FormulaUpToConstant {FormulaUpToConstant->new(@_)}');
   91 }
   92 
   93 #
   94 #  Create an instance of a FormulaUpToConstant.  If no constant
   95 #  is supplied, we add C ourselves.
   96 #
   97 sub new {
   98   my $self = shift; my $class = ref($self) || $self;
   99   #
  100   #  Copy the context (so we can modify it) and
  101   #  replace the usual Variable object with our own.
  102   #
  103   my $context = (Value::isContext($_[0]) ? shift : $self->context)->copy;
  104   $context->{parser}{Variable} = 'FormulaUpToConstant::Variable';
  105   #
  106   #  Create a formula from the user's input.
  107   #
  108   my $f = main::Formula($context,@_);
  109   #
  110   #  If it doesn't have a constant already, add one.
  111   #  (should check that C isn't already in use, and look
  112   #   up the first free name, but we'll cross our fingers
  113   #   for now.  Could look through the defined variables
  114   #   to see if there is already an arbitraryConstant
  115   #   and use that.)
  116   #
  117   unless ($f->{constant}) {$f = $f + "C", $f->{constant} = "C"}
  118   #
  119   #  Check that the formula is linear in C.
  120   #
  121   my $n = $f->D($f->{constant});
  122   Value->Error("Your formula isn't linear in the arbitrary constant '%s'",$f->{constant})
  123     unless $n->isConstant;
  124   #
  125   #  Make a version with adaptive parameters for use in the
  126   #  comparison later on.  We could like n0*C, but already have $n
  127   #  copies of C, so remove them.  That way, n0 will be 0 when there
  128   #  are no C's in the student answer during the adaptive comparison.
  129   #  (Again, should really check that n0 is not in use already)
  130   #
  131   my $n00 = $context->variables->get("n00");
  132   $context->variables->add(n00=>'Parameter') unless $n00 and $n00->{parameter};
  133   my $n01 = $context->variables->get("n01");
  134   $context->variables->add(n01=>'Parameter') unless $n01 and $n01->{parameter};
  135   $f->{adapt} = $f + "(n00-$n)$f->{constant} + n01";
  136   return bless $f, $class;
  137 }
  138 
  139 ##################################################
  140 #
  141 #  Remember that compare implements the overloaded perl <=> operator,
  142 #  and $a <=> $b is -1 when $a < $b, 0 when $a == $b and 1 when $a > $b.
  143 #  In our case, we only care about equality, so we will return 0 when
  144 #  equal and other numbers to indicate the reason they are not equal
  145 #  (this can be used by the answer checker to print helpful messages)
  146 #
  147 sub compare {
  148   my ($l,$r) = @_; my $self = $l; my $context = $self->context;
  149   $r = Value::makeValue($r,context=>$context);
  150   #
  151   #  Not equal if the student value is constant or has no + C
  152   #
  153   return 2 if !Value::isFormula($r);
  154   return 3 if !defined($r->{constant});
  155   #
  156   #  If constants aren't the same, substitute the professor's in the student answer.
  157   #
  158   $r = $r->substitute($r->{constant}=>$l->{constant}) unless $r->{constant} eq $l->{constant};
  159   #
  160   #  Compare with adaptive parameters to see if $l + n0 C = $r for some n0.
  161   #
  162   return -1 unless $l->{adapt} == $r;
  163   #
  164   #  Check that n0 is non-zero (i.e., there is a multiple of C in the student answer)
  165   #  (remember: return value of 0 is equal, and non-zero is unequal)
  166   #
  167   return abs($context->variables->get("n00")->{value}) < $context->flag("zeroLevelTol");
  168 }
  169 
  170 ##################################################
  171 #
  172 #  Here we override part of the answer comparison
  173 #  routines in order to be able to generate
  174 #  helpful error messages for students when
  175 #  they leave off the + C.
  176 #
  177 
  178 #
  179 #  Show hints by default
  180 #
  181 sub cmp_defaults {((shift)->SUPER::cmp_defaults,showHints => 1, showLinearityHints => 1)};
  182 
  183 #
  184 #  Add useful messages, if the author requested them
  185 #
  186 sub cmp_postprocess {
  187   my $self = shift; my $ans = shift;
  188   $self->SUPER::cmp_postprocess($ans);
  189   return unless $ans->{score} == 0 && !$ans->{isPreview};
  190   return if $ans->{ans_message} || !$self->getFlag("showHints");
  191   my $student = $ans->{student_value};
  192   my $result = $ans->{correct_value} <=> $student;  # compare encodes the reason in the result
  193   $self->cmp_Error($ans,"Note: there is always more than one posibility") if $result == 2 || $result == 3;
  194   if ($result == 3) {
  195     $self->context->flags->set(no_parameters=>0);
  196     $result = 1 if $self->removeConstant+"n01+n00x" == $student+"x"; # must use both parameters
  197     $self->context->flags->set(no_parameters=>1);
  198   }
  199   $self->cmp_Error($ans,"Your answer is not the most general solution") if $result == 1;
  200   $self->cmp_Error($ans,"Your formula should be linear in the constant '$student->{constant}'")
  201     if $result == -1 && $self->getFlag("showLinearityHints") && !$student->D($student->{constant})->isConstant;
  202 }
  203 
  204 ##################################################
  205 #
  206 #  Get the name of the constant
  207 #
  208 sub constant {(shift)->{constant}}
  209 
  210 #
  211 #  Remove the constant and return a Formula object
  212 #
  213 sub removeConstant {
  214   my $self = shift;
  215   main::Formula($self->substitute($self->{constant}=>0))->reduce;
  216 }
  217 
  218 #
  219 #  Override the differentiation so that we always return
  220 #  a Formula, not a FormulaUpToConstant (we don't want to
  221 #  add the C in again).
  222 #
  223 sub D {
  224   my $self = shift;
  225   $self->removeConstant->D(@_);
  226 }
  227 
  228 ######################################################################
  229 #
  230 #  This class repalces the Parser::Variable class, and its job
  231 #  is to look for new constants that aren't in the context,
  232 #  and add them in.  This allows students to use ANY constant
  233 #  they want, and a different one from the professor.  We check
  234 #  that the student only used ONE arbitrary constant, however.
  235 #
  236 package FormulaUpToConstant::Variable;
  237 our @ISA = ('Parser::Variable');
  238 
  239 sub new {
  240   my $self = shift; my $class = ref($self) || $self;
  241   my $equation = shift; my $variables = $equation->{context}{variables};
  242   my ($name,$ref) = @_; my $def = $variables->{$name};
  243   #
  244   #  If the variable is not already in the context, add it
  245   #    and mark it as an arbitrary constant (for later reference)
  246   #
  247   if (!defined($def) && length($name) eq 1) {
  248     $equation->{context}->variables->add($name => 'Real');
  249     $equation->{context}->variables->set($name => {arbitraryConstant => 1});
  250     $def = $variables->{$name};
  251   }
  252   #
  253   #  If the variable is an arbitrary constant
  254   #    Error if we already have a constant and it's not this one.
  255   #    Save the constant so we can check with it later.
  256   #
  257   if ($def && $def->{arbitraryConstant}) {
  258     $equation->Error(["Your formula shouldn't have two arbitrary constants"],$ref)
  259       if $equation->{constant} and $name ne $equation->{constant};
  260     $equation->{constant} = $name;
  261   }
  262   #
  263   #  Do the usual Variable stuff.
  264   #
  265   $self->SUPER::new($equation,$name,$ref);
  266 }
  267 
  268 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9