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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5991 - (download) (as text) (annotate)
Thu Feb 5 14:59:02 2009 UTC (10 years, 10 months ago) by dpvc
File size: 13978 byte(s)
Changed wording of a warning message

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright  2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: pg/macros/contextLimitedPolynomial.pl,v 1.21 2007/11/06 16:19:49 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 contextLimitedPolynomial.pl - Allow only entry of polynomials.
   20 
   21 =head1 DESCRIPTION
   22 
   23 Implements a context in which students can only enter (expanded)
   24 polynomials (i.e., sums of multiples of powers of x).
   25 
   26 Select the context using:
   27 
   28   Context("LimitedPolynomial");
   29 
   30 If you set the "singlePowers" flag, then only one monomial of each
   31 degree can be included in the polynomial:
   32 
   33   Context("LimitedPolynomial")->flags->set(singlePowers=>1);
   34 
   35 There is also a strict limited context that does not allow
   36 operations even within the coefficients.  Select it using:
   37 
   38   Context("LimitedPolynomial-Strict");
   39 
   40 In addition to disallowing operations within the coefficients,
   41 this context does not reduce constant operations (since they are
   42 not allowed), and sets the singlePowers flag automatically.  In
   43 addition, it disables all the functions, though they can be
   44 re-enabled, if needed.
   45 
   46 =cut
   47 
   48 loadMacros("MathObjects.pl");
   49 
   50 sub _contextLimitedPolynomial_init {LimitedPolynomial::Init()}; # don't load it again
   51 
   52 ##################################################
   53 #
   54 #  Handle common checking for BOPs
   55 #
   56 package LimitedPolynomial::BOP;
   57 
   58 #
   59 #  Do original check and then if the operands are numbers, its OK.
   60 #  Otherwise, do an operator-specific check for if the polynomial is OK.
   61 #  Otherwise report an error.
   62 #
   63 sub _check {
   64   my $self = shift;
   65   my $super = ref($self); $super =~ s/LimitedPolynomial/Parser/;
   66   &{$super."::_check"}($self);
   67   if (LimitedPolynomial::isConstant($self->{lop}) &&
   68       LimitedPolynomial::isConstant($self->{rop})) {
   69     $self->checkStrict if $self->context->flag("strictCoefficients");
   70     return;
   71   }
   72   return if $self->checkPolynomial;
   73   $self->Error("Your answer doesn't look like a polynomial");
   74 }
   75 
   76 #
   77 #  filled in by subclasses
   78 #
   79 sub checkPolynomial {return 0}
   80 
   81 #
   82 #  Check that the exponents of a monomial are OK
   83 #  and record the new exponent array
   84 #
   85 sub checkExponents {
   86   my $self = shift;
   87   my ($l,$r) = ($self->{lop},$self->{rop});
   88   LimitedPolynomial::markPowers($l);
   89   LimitedPolynomial::markPowers($r);
   90   my $exponents = $self->{exponents} = $r->{exponents};
   91   delete $r->{exponents}; delete $r->{powers};
   92   if ($l->{exponents}) {
   93     my $single = $self->context->flag('singlePowers');
   94     foreach my $i (0..scalar(@{$exponents})-1) {
   95       $self->Error("A variable can appear only once in each term of a polynomial")
   96   if $exponents->[$i] && $l->{exponents}[$i] && $single;
   97       $exponents->[$i] += $l->{exponents}[$i];
   98     }
   99   }
  100   delete $l->{exponents}; delete $l->{powers};
  101   $self->{isPower} = 1; $self->{isPoly} = $l->{isPoly};
  102   return 1;
  103 }
  104 
  105 #
  106 #  Check that the powers of combined monomials are OK
  107 #  and record the new power list
  108 #
  109 sub checkPowers {
  110   my $self = shift;
  111   my ($l,$r) = ($self->{lop},$self->{rop});
  112   my $single = $self->context->flag('singlePowers');
  113   LimitedPolynomial::markPowers($l);
  114   LimitedPolynomial::markPowers($r);
  115   $self->{isPoly} = 1;
  116   $self->{powers} = $l->{powers} || {}; delete $l->{powers};
  117   return 1 unless $r->{powers};
  118   foreach my $n (keys(%{$r->{powers}})) {
  119     $self->Error("Simplified polynomials can have at most one term of each degree")
  120       if $self->{powers}{$n} && $single;
  121     $self->{powers}{$n} = 1;
  122   }
  123   delete $r->{powers};
  124   return 1;
  125 }
  126 
  127 #
  128 #  Report an error when both operands are constants
  129 #  and strictCoefficients is in effect.
  130 #
  131 sub checkStrict {
  132   my $self = shift;
  133   $self->Error("Can't use '%s' between constants",$self->{bop});
  134 }
  135 
  136 ##################################################
  137 
  138 package LimitedPolynomial;
  139 
  140 #
  141 #  Mark a variable as having power 1
  142 #  Mark a number as being present (when strict coefficients are used)
  143 #  Mark a monomial as having its given powers
  144 #
  145 sub markPowers {
  146   my $self = shift;
  147   if ($self->class eq 'Variable') {
  148     my $vIndex = LimitedPolynomial::getVarIndex($self);
  149     $self->{index} = $vIndex->{$self->{name}};
  150     $self->{exponents} = [(0) x scalar(keys %{$vIndex})];
  151     $self->{exponents}[$self->{index}] = 1;
  152   } elsif ($self->class eq 'Number') {
  153     my $vIndex = LimitedPolynomial::getVarIndex($self);
  154     $self->{exponents} = [(0) x scalar(keys %{$vIndex})];
  155   }
  156   if ($self->{exponents}) {
  157     my $power = join(',',@{$self->{exponents}});
  158     $self->{powers}{$power} = 1;
  159   }
  160 }
  161 
  162 #
  163 #  Get a hash of variable names that point to indices
  164 #  within the array of powers for a monomial
  165 #
  166 sub getVarIndex {
  167   my $self = shift;
  168   my $equation = $self->{equation};
  169   if (!$equation->{varIndex}) {
  170     $equation->{varIndex} = {}; my $i = 0;
  171     foreach my $v ($equation->{context}->variables->names)
  172       {$equation->{varIndex}{$v} = $i++}
  173   }
  174   return $equation->{varIndex};
  175 }
  176 
  177 #
  178 #  Check for a constant expression
  179 #
  180 sub isConstant {
  181   my $self = shift;
  182   return 1 if $self->{isConstant} || $self->class eq 'Constant';
  183   return scalar(keys(%{$self->getVariables})) == 0;
  184 }
  185 
  186 ##############################################
  187 #
  188 #  Now we get the individual replacements for the operators
  189 #  that we don't want to allow.  We inherit everything from
  190 #  the original Parser::BOP class, and just add the
  191 #  polynomial checks here.  Note that checkpolynomial
  192 #  only gets called if at least one of the terms is not
  193 #  a number.
  194 #
  195 
  196 package LimitedPolynomial::BOP::add;
  197 our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::add);
  198 
  199 sub checkPolynomial {
  200   my $self = shift;
  201   my ($l,$r) = ($self->{lop},$self->{rop});
  202   $self->Error("Addition is allowed only between monomials") if $r->{isPoly};
  203   $self->checkPowers;
  204 }
  205 
  206 ##############################################
  207 
  208 package LimitedPolynomial::BOP::subtract;
  209 our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::subtract);
  210 
  211 sub checkPolynomial {
  212   my $self = shift;
  213   my ($l,$r) = ($self->{lop},$self->{rop});
  214   $self->Error("Subtraction is allowed only between monomials") if $r->{isPoly};
  215   $self->checkPowers;
  216 }
  217 
  218 ##############################################
  219 
  220 package LimitedPolynomial::BOP::multiply;
  221 our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::multiply);
  222 
  223 sub checkPolynomial {
  224   my $self = shift;
  225   my ($l,$r) = ($self->{lop},$self->{rop});
  226   my $lOK = (LimitedPolynomial::isConstant($l) || $l->{isPower} ||
  227        $l->class eq 'Variable' || ($l->{isPoly} && $l->{isPoly} == 2));
  228   my $rOK = ($r->{isPower} || $r->class eq 'Variable');
  229   return $self->checkExponents if $lOK and $rOK;
  230   $self->Error("Coefficients must come before variables in a polynomial")
  231     if LimitedPolynomial::isConstant($r) && ($l->{isPower} || $l->class eq 'Variable');
  232   $self->Error("Multiplication can only be used between coefficients and variables");
  233 }
  234 
  235 sub checkStrict {
  236   my $self = shift;
  237   $self->Error("You can only use '%s' between coefficents and variables in a polynomial",$self->{bop});
  238 }
  239 
  240 ##############################################
  241 
  242 package LimitedPolynomial::BOP::divide;
  243 our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::divide);
  244 
  245 sub checkPolynomial {
  246   my $self = shift;
  247   my ($l,$r) = ($self->{lop},$self->{rop});
  248   $self->Error("In a polynomial, you can only divide by numbers")
  249     unless LimitedPolynomial::isConstant($r);
  250   $self->Error("You can only divide a single term by a number")
  251     if $l->{isPoly} && $l->{isPoly} == 1;
  252   $self->{isPoly} = $l->{isPoly};
  253   $self->{powers} = $l->{powers}; delete $l->{powers};
  254   $self->{exponents} = $l->{exponents}; delete $l->{exponents};
  255   return 1;
  256 }
  257 
  258 sub checkStrict {
  259   my $self = shift;
  260   $self->Error("You can only use '%s' to form numeric fractions",$self->{bop}) if $self->{lop}->class eq 'BOP';
  261 }
  262 
  263 ##############################################
  264 
  265 package LimitedPolynomial::BOP::power;
  266 our @ISA = qw(LimitedPolynomial::BOP Parser::BOP::power);
  267 
  268 sub checkPolynomial {
  269   my $self = shift;
  270   my ($l,$r) = ($self->{lop},$self->{rop});
  271   $self->Error("You can only raise a variable to a power in a polynomial")
  272     unless $l->class eq 'Variable';
  273   $self->Error("Exponents must be constant in a polynomial")
  274     unless LimitedPolynomial::isConstant($r);
  275   my $n = Parser::Evaluate($r);
  276   $r->Error($$Value::context->{error}{message}) if $$Value::context->{error}{flag};
  277   $n = $n->value;
  278   $self->Error("Exponents must be positive integers in a polynomial")
  279     unless $n > 0 && $n == int($n);
  280   LimitedPolynomial::markPowers($l);
  281   $self->{exponents} = $l->{exponents}; delete $l->{exponents};
  282   foreach my $i (@{$self->{exponents}}) {$i = $n if $i}
  283   $self->{isPower} = 1;
  284   return 1;
  285 }
  286 
  287 sub checkStrict {
  288   my $self = shift;
  289   $self->Error("You can only use powers of a variable in a polynomial");
  290 }
  291 
  292 ##############################################
  293 ##############################################
  294 #
  295 #  Now we do the same for the unary operators
  296 #
  297 
  298 package LimitedPolynomial::UOP;
  299 
  300 sub _check {
  301   my $self = shift;
  302   my $super = ref($self); $super =~ s/LimitedPolynomial/Parser/;
  303   &{$super."::_check"}($self);
  304   my $op = $self->{op};
  305   return if LimitedPolynomial::isConstant($op);
  306   $self->Error("You can only use '%s' with monomials",$self->{def}{string})
  307     if $op->{isPoly};
  308   $self->{isPoly} = 2;
  309   $self->{powers} = $op->{powers}; delete $op->{powers};
  310   $self->{exponents} = $op->{exponents}; delete $op->{exponents};
  311 }
  312 
  313 sub checkPolynomial {return 0}
  314 
  315 ##############################################
  316 
  317 package LimitedPolynomial::UOP::plus;
  318 our @ISA = qw(LimitedPolynomial::UOP Parser::UOP::plus);
  319 
  320 ##############################################
  321 
  322 package LimitedPolynomial::UOP::minus;
  323 our @ISA = qw(LimitedPolynomial::UOP Parser::UOP::minus);
  324 
  325 ##############################################
  326 ##############################################
  327 #
  328 #  Don't allow absolute values
  329 #
  330 
  331 package LimitedPolynomial::List::AbsoluteValue;
  332 our @ISA = qw(Parser::List::AbsoluteValue);
  333 
  334 sub _check {
  335   my $self = shift;
  336   $self->SUPER::_check;
  337   return if LimitedPolynomial::isConstant($self->{coords}[0]);
  338   $self->Error("Can't use absolute values in polynomials");
  339 }
  340 
  341 ##############################################
  342 ##############################################
  343 #
  344 #  Only allow numeric function calls
  345 #
  346 
  347 package LimitedPolynomial::Function;
  348 
  349 sub _check {
  350   my $self = shift;
  351   my $super = ref($self); $super =~ s/LimitedPolynomial/Parser/;
  352   &{$super."::_check"}($self);
  353   my $arg = $self->{params}->[0];
  354   return if LimitedPolynomial::isConstant($arg);
  355   $self->Error("Function '%s' can only be used with numbers",$self->{name});
  356 }
  357 
  358 
  359 package LimitedPolynomial::Function::numeric;
  360 our @ISA = qw(LimitedPolynomial::Function Parser::Function::numeric);
  361 
  362 package LimitedPolynomial::Function::trig;
  363 our @ISA = qw(LimitedPolynomial::Function Parser::Function::trig);
  364 
  365 package LimitedPolynomial::Function::hyperbolic;
  366 our @ISA = qw(LimitedPolynomial::Function Parser::Function::hyperbolic);
  367 
  368 ##############################################
  369 ##############################################
  370 
  371 package LimitedPolynomial;
  372 
  373 sub Init {
  374   #
  375   #  Build the new context that calls the
  376   #  above classes rather than the usual ones
  377   #
  378 
  379   my $context = $main::context{LimitedPolynomial} = Parser::Context->getCopy("Numeric");
  380   $context->{name} = "LimitedPolynomial";
  381   $context->operators->set(
  382      '+' => {class => 'LimitedPolynomial::BOP::add'},
  383      '-' => {class => 'LimitedPolynomial::BOP::subtract'},
  384      '*' => {class => 'LimitedPolynomial::BOP::multiply'},
  385     '* ' => {class => 'LimitedPolynomial::BOP::multiply'},
  386     ' *' => {class => 'LimitedPolynomial::BOP::multiply'},
  387      ' ' => {class => 'LimitedPolynomial::BOP::multiply'},
  388      '/' => {class => 'LimitedPolynomial::BOP::divide'},
  389     ' /' => {class => 'LimitedPolynomial::BOP::divide'},
  390     '/ ' => {class => 'LimitedPolynomial::BOP::divide'},
  391      '^' => {class => 'LimitedPolynomial::BOP::power'},
  392     '**' => {class => 'LimitedPolynomial::BOP::power'},
  393     'u+' => {class => 'LimitedPolynomial::UOP::plus'},
  394     'u-' => {class => 'LimitedPolynomial::UOP::minus'},
  395   );
  396   #
  397   #  Remove these operators and functions
  398   #
  399   $context->lists->set(
  400     AbsoluteValue => {class => 'LimitedPolynomial::List::AbsoluteValue'},
  401   );
  402   $context->operators->undefine('_','!','U');
  403   $context->functions->disable("atan2");
  404   #
  405   #  Hook into the numeric, trig, and hyperbolic functions
  406   #
  407   foreach ('ln','log','log10','exp','sqrt','abs','int','sgn') {
  408     $context->functions->set(
  409       "$_"=>{class => 'LimitedPolynomial::Function::numeric'}
  410     );
  411   }
  412   foreach ('sin','cos','tan','sec','csc','cot',
  413            'asin','acos','atan','asec','acsc','acot') {
  414     $context->functions->set(
  415        "$_"=>{class => 'LimitedPolynomial::Function::trig'},
  416        "${_}h"=>{class => 'LimitedPolynomial::Function::hyperbolic'}
  417     );
  418   }
  419   #
  420   #  Don't convert -ax-b to -(ax+b), or -ax+b to b-ax, etc.
  421   #
  422   $context->reduction->set("(-x)-y"=>0,"(-x)+y"=>0);
  423 
  424   #
  425   #  A context where coefficients can't include operations
  426   #
  427   $context = $main::context{"LimitedPolynomial-Strict"} = $context->copy;
  428   $context->flags->set(strictCoefficients=>1, singlePowers=>1, reduceConstants=>0);
  429   $context->functions->disable("All");  # can be re-enabled if needed
  430 
  431   main::Context("LimitedPolynomial");  ### FIXME:  probably should require author to set this explicitly
  432 }
  433 
  434 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9