[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 6262 - (download) (as text) (annotate)
Sat May 15 18:43:11 2010 UTC (9 years, 8 months ago) by gage
File size: 14038 byte(s)
 format change only.

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9