[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 5551 - (download) (as text) (annotate)
Tue Oct 2 20:48:05 2007 UTC (12 years, 2 months ago) by sh002i
File size: 9595 byte(s)
improved formatting for docs -- these were in pod sections but were all
formatted as verbatim sections, and i moved them into normal paragraphs,
lists, etc. should make things more readable from the web.

    1 =head1 NAME
    2 
    3 parserFormulaUpToConstant.pl - implements formulas "plus a constant".
    4 
    5 =head1 DESCRIPTION
    6 
    7 This file implements the FormulaUpToConstant object, which is
    8 a formula that is only unique up to a constant (i.e., this is
    9 an anti-derivative). Students must include the "+C" as part of
   10 their answers, but they can use any (single-letter) constant that
   11 they want, and it doesn't have to be the one the professor used.
   12 
   13 To use FormulaWithConstat objects, load this macro file at the
   14 top of your problem:
   15 
   16     loadMacros("parserFormulaUpToConstant.pl");
   17 
   18 then create a formula with constant as follows:
   19 
   20     $f = FormulaUpToConstant("sin(x)+C");
   21 
   22 Note that the C should NOT already be a variable in the Context;
   23 the FormulaUpToConstant object will handle adding it in for
   24 you.  If you don't include a constant in your formula (i.e., if
   25 all the variables that you used are already in your Context,
   26 then the FormulaUpToConstant object will add "+C" for you.
   27 
   28 The FormulaUpToConstant should work like any normal Formula,
   29 and in particular, you use $f->cmp to get its answer checker.
   30 
   31     ANS($f->cmp);
   32 
   33 Note that the FormulaUpToConstant object creates its only private
   34 copy of the current Context (so that it can add variables without
   35 affecting the rest of the problem).  You should not notice this
   36 in general, but if you need to access that context, use $f->{context}.
   37 E.g.
   38 
   39     Context($f->{context});
   40 
   41 would make the current context the one being used by the
   42 FormulaUpToConstant, while
   43 
   44     $f->{context}->variables->names
   45 
   46 would return a list of the variables in the private context.
   47 
   48 To get the name of the constant in use in the formula,
   49 use
   50 
   51     $f->constant.
   52 
   53 If you combine a FormulaUpToConstant with other formulas,
   54 the result will be a new FormulaUpToConstant object, with
   55 a new Context, and potentially a new + C added to it.  This
   56 is likely not what you want.  Instead, you should convert
   57 back to a Formula first, then combine with other objects,
   58 then convert back to a FormulaUpToConstant, if necessary.
   59 To do this, use the removeConstant() method:
   60 
   61     $f = FormulaUpToConstant("sin(x)+C");
   62     $g = Formula("cos(x)");
   63     $h = $f->removeConstant + $g;  # $h will be "sin(x)+cos(x)"
   64     $h = FormulaUpToConstant($h);  # $h will be "sin(x)+cos(x)+C"
   65 
   66 The answer evaluator by default will give "helpful" messages
   67 to the student when the "+ C" is left out.  You can turn off
   68 these messages using the showHints option to the cmp() method:
   69 
   70     ANS($f->cmp(showHints => 0));
   71 
   72 One of the hints is about whether the student's answer is linear
   73 in the arbitrary constant.  This test requires differentiating
   74 the student answer.  Since there are times when that could be
   75 problematic, you can disable that test via the showLinearityHints
   76 flag.  (Note: setting showHints to 0 also disables these hints.)
   77 
   78     ANS($f->cmp(showLinearityHints => 0));
   79 
   80 =cut
   81 
   82 loadMacros("MathObjects.pl");
   83 
   84 sub _parserFormulaUpToConstant_init {FormulaUpToConstant::Init()}
   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     my $context = $self->context;
  196     $context->flags->set(no_parameters=>0);
  197     $context->variables->add(x00=>'Real');
  198     $result = 1 if $self->removeConstant+"n01+n00x00" == $student+"x00"; # must use both parameters
  199     $context->variables->remove('x00');
  200     $context->flags->set(no_parameters=>1);
  201   }
  202   $self->cmp_Error($ans,"Your answer is not the most general solution") if $result == 1;
  203   $self->cmp_Error($ans,"Your formula should be linear in the constant '$student->{constant}'")
  204     if $result == -1 && $self->getFlag("showLinearityHints") && !$student->D($student->{constant})->isConstant;
  205 }
  206 
  207 ##################################################
  208 #
  209 #  Get the name of the constant
  210 #
  211 sub constant {(shift)->{constant}}
  212 
  213 #
  214 #  Remove the constant and return a Formula object
  215 #
  216 sub removeConstant {
  217   my $self = shift;
  218   main::Formula($self->substitute($self->{constant}=>0))->reduce;
  219 }
  220 
  221 #
  222 #  Override the differentiation so that we always return
  223 #  a Formula, not a FormulaUpToConstant (we don't want to
  224 #  add the C in again).
  225 #
  226 sub D {
  227   my $self = shift;
  228   $self->removeConstant->D(@_);
  229 }
  230 
  231 ######################################################################
  232 #
  233 #  This class repalces the Parser::Variable class, and its job
  234 #  is to look for new constants that aren't in the context,
  235 #  and add them in.  This allows students to use ANY constant
  236 #  they want, and a different one from the professor.  We check
  237 #  that the student only used ONE arbitrary constant, however.
  238 #
  239 package FormulaUpToConstant::Variable;
  240 our @ISA = ('Parser::Variable');
  241 
  242 sub new {
  243   my $self = shift; my $class = ref($self) || $self;
  244   my $equation = shift; my $variables = $equation->{context}{variables};
  245   my ($name,$ref) = @_; my $def = $variables->{$name};
  246   #
  247   #  If the variable is not already in the context, add it
  248   #    and mark it as an arbitrary constant (for later reference)
  249   #
  250   if (!defined($def) && length($name) eq 1) {
  251     $equation->{context}->variables->add($name => 'Real');
  252     $equation->{context}->variables->set($name => {arbitraryConstant => 1});
  253     $def = $variables->{$name};
  254   }
  255   #
  256   #  If the variable is an arbitrary constant
  257   #    Error if we already have a constant and it's not this one.
  258   #    Save the constant so we can check with it later.
  259   #
  260   if ($def && $def->{arbitraryConstant}) {
  261     $equation->Error(["Your formula shouldn't have two arbitrary constants"],$ref)
  262       if $equation->{constant} and $name ne $equation->{constant};
  263     $equation->{constant} = $name;
  264   }
  265   #
  266   #  Do the usual Variable stuff.
  267   #
  268   $self->SUPER::new($equation,$name,$ref);
  269 }
  270 
  271 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9