[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 5440 - (download) (as text) (annotate)
Tue Aug 28 22:39:04 2007 UTC (12 years, 5 months ago) by dpvc
File size: 9488 byte(s)
Add error messages when student's answer is not linear in the constant
he or she has used.

    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 an adaptive parameter 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 $n0 = $context->variables->get("n0");
  132   $context->variables->add(n0=>'Parameter') unless $n0 and $n0->{parameter};
  133   $f->{adapt} = $f + "(n0-$n)$f->{constant}";
  134   return bless $f, $class;
  135 }
  136 
  137 ##################################################
  138 #
  139 #  Remember that compare implements the overloaded perl <=> operator,
  140 #  and $a <=> $b is -1 when $a < $b, 0 when $a == $b and 1 when $a > $b.
  141 #  In our case, we only care about equality, so we will return 0 when
  142 #  equal and other numbers to indicate the reason they are not equal
  143 #  (this can be used by the answer checker to print helpful messages)
  144 #
  145 sub compare {
  146   my ($l,$r) = @_; my $self = $l; my $context = $self->context;
  147   $r = Value::makeValue($r,context=>$context);
  148   #
  149   #  Not equal if the student value is constant or has no + C
  150   #
  151   return 2 if !Value::isFormula($r);
  152   return 3 if !defined($r->{constant});
  153   #
  154   #  If constants aren't the same, substitute the professor's in the student answer.
  155   #
  156   $r = $r->substitute($r->{constant}=>$l->{constant}) unless $r->{constant} eq $l->{constant};
  157   #
  158   #  Compare with adaptive parameters to see if $l + n0 C = $r for some n0.
  159   #
  160   return -1 unless $l->{adapt} == $r;
  161   #
  162   #  Check that n0 is non-zero (i.e., there is a multiple of C in the student answer)
  163   #  (remember: return value of 0 is equal, and non-zero is unequal)
  164   #
  165   return abs($context->variables->get("n0")->{value}) < $context->flag("zeroLevelTol");
  166 }
  167 
  168 ##################################################
  169 #
  170 #  Here we override part of the answer comparison
  171 #  routines in order to be able to generate
  172 #  helpful error messages for students when
  173 #  they leave off the + C.
  174 #
  175 
  176 #
  177 #  Show hints by default
  178 #
  179 sub cmp_defaults {((shift)->SUPER::cmp_defaults,showHints => 1, showLinearityHints => 1)};
  180 
  181 #
  182 #  Add useful messages, if the author requested them
  183 #
  184 sub cmp_postprocess {
  185   my $self = shift; my $ans = shift;
  186   $self->SUPER::cmp_postprocess($ans);
  187   return unless $ans->{score} == 0 && !$ans->{isPreview};
  188   return if $ans->{ans_message} || !$self->getFlag("showHints");
  189   my $student = $ans->{student_value};
  190   my $result = $ans->{correct_value} <=> $student;  # compare encodes the reason in the result
  191   $self->cmp_Error($ans,"Note: there is always more than one posibility") if $result == 2 || $result == 3;
  192   $self->cmp_Error($ans,"Your answer is not the most general solution")
  193     if $result == 1 || ($result == 3 && $self->removeConstant == $student);
  194   $self->cmp_Error($ans,"Your formula should be linear in the constant '$student->{constant}'")
  195     if $result == -1 && $self->getFlag("showLinearityHints") && !$student->D($student->{constant})->isConstant;
  196 }
  197 
  198 ##################################################
  199 #
  200 #  Get the name of the constant
  201 #
  202 sub constant {(shift)->{constant}}
  203 
  204 #
  205 #  Remove the constant and return a Formula object
  206 #
  207 sub removeConstant {
  208   my $self = shift;
  209   main::Formula($self->substitute($self->{constant}=>0))->reduce;
  210 }
  211 
  212 #
  213 #  Override the differentiation so that we always return
  214 #  a Formula, not a FormulaUpToConstant (we don't want to
  215 #  add the C in again).
  216 #
  217 sub D {
  218   my $self = shift;
  219   $self->removeConstant->D(@_);
  220 }
  221 
  222 ######################################################################
  223 #
  224 #  This class repalces the Parser::Variable class, and its job
  225 #  is to look for new constants that aren't in the context,
  226 #  and add them in.  This allows students to use ANY constant
  227 #  they want, and a different one from the professor.  We check
  228 #  that the student only used ONE arbitrary constant, however.
  229 #
  230 package FormulaUpToConstant::Variable;
  231 our @ISA = ('Parser::Variable');
  232 
  233 sub new {
  234   my $self = shift; my $class = ref($self) || $self;
  235   my $equation = shift; my $variables = $equation->{context}{variables};
  236   my ($name,$ref) = @_; my $def = $variables->{$name};
  237   #
  238   #  If the variable is not already in the context, add it
  239   #    and mark it as an arbitrary constant (for later reference)
  240   #
  241   if (!defined($def) && length($name) eq 1) {
  242     $equation->{context}->variables->add($name => 'Real');
  243     $equation->{context}->variables->set($name => {arbitraryConstant => 1});
  244     $def = $variables->{$name};
  245   }
  246   #
  247   #  If the variable is an arbitrary constant
  248   #    Error if we already have a constant and it's not this one.
  249   #    Save the constant so we can check with it later.
  250   #
  251   if ($def && $def->{arbitraryConstant}) {
  252     $equation->Error(["Your formula shouldn't have two arbitrary constants"],$ref)
  253       if $equation->{constant} and $name ne $equation->{constant};
  254     $equation->{constant} = $name;
  255   }
  256   #
  257   #  Do the usual Variable stuff.
  258   #
  259   $self->SUPER::new($equation,$name,$ref);
  260 }
  261 
  262 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9